Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -4,54 +4,27 @@ # build is for Unix platforms with GNU Make 3.81+. all: .NOTPARALLEL: # stop subdir makes and reconfigure from launching # multiple times concurrently. include config.make -ShakeNMake.CISH_SOURCES := $(wildcard *.c) $(wildcard $(SRC.DIR)/*.c) +ShakeNMake.CISH_SOURCES := $(wildcard *.c) # Subdir cleanup rules and deps list must come before shakenmake.make is included # or they must be set up manually afterwards... -clean-.: clean-doc clean-fnc clean-bindings -distclean-.: distclean-doc distclean-fnc distclean-bindings +clean-.: clean-fnc clean-src +distclean-.: distclean-fnc distclean-src include shakenmake.make MAIN_MAKEFILES := $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) @AUTODEPS@ -AUTOCONFIG_H := @srcdir@/include/fossil-scm/autoconfig.h -#SRCDIR := @top_srcdir@/src -SRCDIR := @srcdir@/src define CALL.SUBDIR endef SUBDIRS := src fnc $(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS))) #$(eval $(call ShakeNMake.CALL.SUBDIR,src)) subdir-fnc: subdir-src all: subdir-src subdir-fnc - -ifeq (1,@LIBFOSSIL_STATIC@) - THELIB.LIB := libfossil$(ShakeNMake.EXTENSIONS.LIB) -else - THELIB.LIB := -endif -ifeq (1,@LIBFOSSIL_SHARED@) - THELIB.DLL := libfossil$(ShakeNMake.EXTENSIONS.DLL) -else - THELIB.DLL := -endif - -ifneq (,$(THELIB.DLL)$(THELIB.LIB)) -src/$(THELIB.DLL): - $(MAKE) -C src -src/$(THELIB.LIB): - $(MAKE) -C src -$(THELIB.DLL): src/$(THELIB.DLL) - ln -sf src/$(THELIB.DLL) . -$(THELIB.LIB): src/$(THELIB.LIB) - ln -sf src/$(THELIB.LIB) . -all: $(THELIB.DLL) $(THELIB.LIB) -CLEAN_FILES += $(wildcard libfossil.so libfossil.dll libfossil.a) -endif DISTCLEAN_FILES += config.make ######################################################################## # Other stuff... @@ -64,22 +37,13 @@ endif install: all @echo 'No installation rules yet.' -DISTCLEAN_FILES += $(AUTOCONFIG_H) Makefile config.log autosetup/jimsh0 \ +DISTCLEAN_FILES += Makefile config.log autosetup/jimsh0 \ $(wildcard compile_commands/*) compile_commands.json+ -# automake compatibility. do nothing for all these targets -#EMPTY_AUTOMAKE_TARGETS := dvi pdf ps info html tags ctags mostlyclean maintainer-clean check installcheck installdirs \ -# install-pdf install-ps install-info install-html -install-dvi uninstall install-exec install-data distdir -#.PHONY: $(EMPTY_AUTOMAKE_TARGETS) -#$(EMPTY_AUTOMAKE_TARGETS): - -## @top_srcdir@/Makefile.in: # b/c AUTODEPS contains this name (it probably shouldn't) -#$(FSL.OBJ): @AUTODEPS@ @top_srcdir@/Makefile @top_srcdir@/config.make - # Reconfigure if needed ifeq ($(findstring clean,$(MAKECMDGOALS)),) @top_srcdir@/config.make: @AUTODEPS@ @top_srcdir@/config.make.in @@AUTOREMAKE@ @top_srcdir@/Makefile: @AUTODEPS@ @top_srcdir@/Makefile.in Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -1,256 +1,27 @@ # vim:se syn=tcl: # use cc cc-shared cc-lib options { - shared=1 => "Disable build of a shared library." no-debug=0 => "Disable debug build options." - amal => "Generates a conservative config file for the amalgamation build." - static=1 => "Disable build of a static library." loud=0 => "Enables 'loud' build mode." } # autosetup interceps 'debug' and 'enable-debug' flags :/ # prefix:=[get-env HOME /usr/local] -> "Installation prefix." -define FSL_PACKAGE_NAME "libfossil" -define FSL_LIBRARY_VERSION 0.0.1-alphabeta - -######################################################################## -# See if we can get the fossil schema version from the current -# checkout. If so, use that one, otherwise fall back to some hard-coded -# default. -set auxSchema {} -set contentSchema {} - -set fossilBin [find-an-executable fossil] -if {[string length $fossilBin] > 0} { - puts "Found fossil binary: $fossilBin" - catch { - set auxSchema [string trimright \ - [exec echo \ - {SELECT value FROM config WHERE name='aux-schema';} \ - "|" $fossilBin sqlite3] ] - set contentSchema [string trimright \ - [exec echo \ - {SELECT value FROM config WHERE name='content-schema';} \ - "|" $fossilBin sqlite3] ] - } ex -# puts "exception=$ex" -} - -#set uname [exec $fossilBin version -v "|" grep "Schema version"] -#puts "uname=$uname" -#return - -if {[string length $auxSchema] == 16} { - puts "Got aux-schema value from current repo: $auxSchema" - puts "Got content-schema value from current repo: $contentSchema" -} else { - set auxSchema "2015-01-24" - # "2011-04-25 19:50" - set contentSchema 2 - puts "Using hard-coded aux-schema: $auxSchema" - puts "Using hard-coded content-schema: $contentSchema" -} -define FSL_AUX_SCHEMA $auxSchema -define FSL_CONTENT_SCHEMA $contentSchema - -######################################################################## -# Grab the code version and timestamp from manifest.uuid and manifest -# files... -# -# This requires that the repo is NOT generating delta manifests. As -# of 2020-03, the libfossil server uses the forbid-delta-manifest -# setting. -set timestamp [clock format [clock seconds] \ - -gmt 1 -format "%Y-%m-%d %H:%M"] -set fp [open "manifest.uuid" r] -set libVersionHash [string trimright [read $fp]] -close $fp -set fp [open "manifest"] -# Get C-card: -gets $fp -# Get D-card: -gets $fp line -close $fp -unset fp -set manifestTimestamp [string map {"D " "" T " "} $line] -unset line -puts "libVersionHash = $libVersionHash" -puts "manifestTimestamp = $manifestTimestamp" - -set FSL_PLATFORM_CONFIG_H " -\#define FSL_LIB_VERSION_HASH \"$libVersionHash\" -\#define FSL_LIB_VERSION_TIMESTAMP \"$manifestTimestamp UTC\" -\#define FSL_LIB_CONFIG_TIME \"$timestamp GMT\" -\#if defined(_MSC_VER) -\#define FSL_PLATFORM_OS \"windows\" -\#define FSL_PLATFORM_IS_WINDOWS 1 -\#define FSL_PLATFORM_IS_UNIX 0 -\#define FSL_PLATFORM_PLATFORM \"windows\" -\#define FSL_PLATFORM_PATH_SEPARATOR \";\" -\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\" -\/* define a __func__ compatibility macro *\/ -\#if _MSC_VER < 1500 /* (vc9.0; dev studio 2008) */ -/* sorry; cant do much better than nothing at all on those earlier ones */ -\#define __func__ \"(func)\" -\#else -\#define __func__ __FUNCTION__ -\#endif -/* for the time being at least, don't complain about there being secure crt alternatives: */ -\#ifndef _CRT_SECURE_NO_WARNINGS -\#define _CRT_SECURE_NO_WARNINGS -\#endif -/* for the time being at least, don't complain about using POSIX names instead of ISO C++: */ -\#pragma warning ( disable : 4996 ) -/* for the time being at least, suppresss some int conversion warnings */ -\#pragma warning ( disable : 4244 ) /*'fsl_size_t' to 'int'; this masks other problems that should be fixed*/ -\#pragma warning ( disable : 4761 ) /*'integral size mismatch in argument'; more size_t problems*/ -\#pragma warning ( disable : 4267 ) /*'size_t' to 'int'; crops up especially in 64-bit builds*/ -/* these were extracted from fossil's unistd.h */ -\#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) -\#include -\#elif defined(__MINGW32__) -\#define FSL_PLATFORM_OS \"mingw\" -\#define FSL_PLATFORM_IS_WINDOWS 1 -\#define FSL_PLATFORM_IS_UNIX 0 -\#define FSL_PLATFORM_PLATFORM \"windows\" -\#define FSL_PLATFORM_PATH_SEPARATOR \";\" -\#define FSL_CHECKOUTDB_NAME \"./.fslckout\" -\#elif defined(__CYGWIN__) -\#define FSL_PLATFORM_OS \"cygwin\" -\#define FSL_PLATFORM_IS_WINDOWS 0 -\#define FSL_PLATFORM_IS_UNIX 1 -\#define FSL_PLATFORM_PLATFORM \"unix\" -\#define FSL_PLATFORM_PATH_SEPARATOR \":\" -\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\" -\#else -\#define FSL_PLATFORM_OS \"unknown\" -\#define FSL_PLATFORM_IS_WINDOWS 0 -\#define FSL_PLATFORM_IS_UNIX 1 -\#define FSL_PLATFORM_PLATFORM \"unix\" -\#define FSL_PLATFORM_PATH_SEPARATOR \":\" -\#define FSL_CHECKOUTDB_NAME \"./.fslckout\" -\#endif -" - -if {[opt-bool amal]} { - puts "Generating conservative config for the amalgamation build..." - set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_ - set ofile libfossil-config.h - set out [open $ofile w] - puts $out "\#if !defined($incGuard) -\#define $incGuard 1 -\#define FSL_AUX_SCHEMA \"$auxSchema\" -\#define FSL_CONTENT_SCHEMA \"$contentSchema\" -\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\" -\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\" -/* Tweak the following for your system... */ -\#if !defined(HAVE_COMPRESS) -\# define HAVE_COMPRESS 1 -\#endif -\#if !defined(HAVE_DLFCN_H) -\# define HAVE_DLFCN_H 0 -\#endif -\#if !defined(HAVE_DLOPEN) -\# define HAVE_DLOPEN 0 -\#endif -\#if !defined(HAVE_GETADDRINFO) -\# define HAVE_GETADDRINFO 0 -\#endif -\#if !defined(HAVE_INET_NTOP) -\# define HAVE_INET_NTOP 0 -\#endif -\#if !defined(HAVE_INTTYPES_H) -\# define HAVE_INTTYPES_H 0 -\#endif -\#if !defined(HAVE_LIBDL) -\# define HAVE_LIBDL 0 -\#endif -\#if !defined(HAVE_LIBLTDL) -\# define HAVE_LIBLTDL 0 -\#endif -\#if !defined(_WIN32) -\#if !defined(HAVE_LSTAT) -\# define HAVE_LSTAT 1 -\#endif -\#if !defined(HAVE_LTDL_H) -\# define HAVE_LTDL_H 0 -\#endif -\#if !defined(HAVE_LT_DLOPEN) -\# define HAVE_LT_DLOPEN 0 -\#endif -\#if !defined(HAVE_OPENDIR) -\# define HAVE_OPENDIR 1 -\#endif -\#if !defined(HAVE_PIPE) -\# define HAVE_PIPE 1 -\#endif -\#if !defined(HAVE_STAT) -\# define HAVE_STAT 1 -\#endif -\#if !defined(HAVE_STDINT_H) -\# define HAVE_STDINT_H 0 -\#endif -\#if !defined(_DEFAULT_SOURCE) -\# define _DEFAULT_SOURCE 1 -\#endif -\#if !defined(_XOPEN_SOURCE) -\# define _XOPEN_SOURCE 500 -\#endif -\#else -\#if !defined(HAVE_LSTAT) -\# define HAVE_LSTAT 0 -\#endif -\#if !defined(HAVE_LTDL_H) -\# define HAVE_LTDL_H 0 -\#endif -\#if !defined(HAVE_LT_DLOPEN) -\# define HAVE_LT_DLOPEN 0 -\#endif -\#if !defined(HAVE_OPENDIR) -\# define HAVE_OPENDIR 1 -\#endif -\#if !defined(HAVE_PIPE) -\# define HAVE_PIPE 0 -\#endif -\#if !defined(HAVE_STAT) -\# define HAVE_STAT 0 -\#endif -\#if !defined(HAVE_STDINT_H) -\# define HAVE_STDINT_H 0 -\#endif -\#endif -/* _WIN32 */ - -$FSL_PLATFORM_CONFIG_H - -\#endif -/* $incGuard */ -" - close $out - puts "Generated $ofile." - return -} -# end of --amal bootstrap config generation - -cc-check-c11 +#cc-check-c11 cc-check-sizeof "void *" if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib compress z]} { user-error "Missing functional zlib" } cc-check-function-in-lib iconv iconv -#if {![cc-check-functions iconv] && -# ![cc-check-function-in-lib iconv iconv]} { -# user-error "Cannot find iconv(3) in libc or libiconv" -#} ######################################################################## # Checks for C99 via (__STDC_VERSION__ >= 199901L). Returns 1 if so, 0 # 0 if not. proc cc-check-c99 {} { @@ -277,44 +48,10 @@ if {![cc-check-c99]} { user-error "As of 2021-02-21, libfossil requires C99." } define CC_FLAG_C99 $CC_FLAG_C99 -######################################################################## -# Module loader is currently used only by the s2 binding. -define HAVE_LIBLTDL 0 -define HAVE_LIBDL 0 -define LDFLAGS_MODULE_LOADER "" -define FSL_ENABLE_MODULE_LOADER 0 -if {[cc-check-includes ltdl.h] && [cc-check-function-in-lib lt_dlopen ltdl]} { - define HAVE_LIBLTDL 1 - define LDFLAGS_MODULE_LOADER "-lltdl" - define FSL_ENABLE_MODULE_LOADER 1 -} elseif {[cc-with {-includes dlfcn.h} { - cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} { - msg-result "This system can use dlopen() w/o -ldl" - define HAVE_LIBDL 1 - define LDFLAGS_MODULE_LOADER "" - define FSL_ENABLE_MODULE_LOADER 1 -} elseif {[cc-check-includes dlfcn.h]} { - msg-result "Found dlfcn.h." - define HAVE_LIBDL 1 - if {[cc-check-function-in-lib dlopen dl]} { - msg-result "Found libdl." - define LDFLAGS_MODULE_LOADER "-ldl" - } else { - msg-result "No libdl found. Assuming dlopen is built-in." - define LDFLAGS_MODULE_LOADER "" - } - define FSL_ENABLE_MODULE_LOADER 1 -} - -if {![get-define FSL_ENABLE_MODULE_LOADER]} { - msg-result {No usable module loading library found. No worries, because we won't have a module system yet. :-D} -} else { - msg-result {Found a module loader. Now we just need something to do with it.} -} ######################################################################## # A proxy for cc-check-function-in-lib which "undoes" any changes that # routine makes to the LIBS define. proc my-check-function-in-lib {function libs {otherlibs {}}} { @@ -322,26 +59,10 @@ set found [cc-check-function-in-lib $function $libs $otherlibs] define LIBS $_LIBS return $found } -######################################################################## -# readline is only used (if at all) by s2sh. If it's not available, we -# fall back to a tree-local copy of the BSD-licensed linenoise editing -# library. -if {[cc-check-includes readline/readline.h] && - [my-check-function-in-lib readline readline]} { - define FSL_ENABLE_READLINE 1 - define FSL_ENABLE_LINENOISE 0 - msg-result "Enabling libreadline for f-s2sh line editing." -} else { - msg-result "libreadline not found. f-s2sh will use the linenoise line editor." - define FSL_ENABLE_READLINE 0 - define FSL_ENABLE_LINENOISE 1 - define lib_readline "" -} - # cc-check-functions getcwd fopen cc-check-functions opendir stat pipe inet_ntop getaddrinfo #msg-result [cc-check-functions lstat] if {[cc-check-functions lstat]} { @@ -371,10 +92,102 @@ set cFlags "$cFlags -O2" } else { msg-result "Debug build enabled. Use --no-debug to build in non-debug mode." set cFlags "$cFlags -g -DDEBUG -O0" } + + +set pcBin [find-an-executable pkg-config] +if {"" eq $pcBin} { + puts {pkg-config not found, so making some guesses about +available packages.} +} +######################################################################## +# Curses! +set LIB_CURSES "" +set CFLAGS_CURSES "" +puts "Looking for \[n]curses..." +if {"" ne $pcBin && $::tcl_platform(os)!="Darwin"} { +# Some macOS pkg-config configurations alter library search paths, which make +# the compiler unable to find lib iconv, so don't use pkg-config on macOS. + set np "" + foreach p {ncursesw ncurses} { + if {[catch {exec $pcBin --exists $p}]} { + continue + } + set np $p + puts "Using pkg-config curses package \[$p]" + break + } + if {"" ne $np} { + set ppanel "" + if {"ncursesw" eq $np} { + if {![catch {exec $pcBin --exists panelw}]} { + set ppanel panelw + } + } + if {"" eq $ppanel && ![catch {exec $pcBin --exists panel}]} { + set ppanel panel + } + set CFLAGS_CURSES [exec $pcBin --cflags $np] + set LIB_CURSES [exec $pcBin --libs $np] + if {"" eq $ppanel} { + # Apparently Mac brew has pkg-config for ncursesw but not + # panel/panelw, but hard-coding -lpanel seems to work on + # that platform. + append LIB_CURSES " -lpanel" + } else { + append LIB_CURSES " " [exec $pcBin --libs $ppanel] + # append CFLAGS_CURSES " " [exec $pcBin --cflags $ppanel] + # ^^^^ appending the panel cflags will end up duplicating + # at least one -D flag from $np's cflags, leading to + # "already defined" errors at compile-time. Sigh. Note, however, + # that $ppanel's cflags have flags which $np's do not, so we + # may need to include those flags anyway and manually perform + # surgery on the list to remove dupes. Sigh. + } + } +} + +if {"" eq $LIB_CURSES} { + puts "Guessing curses location (will fail for exotic locations)..." + define HAVE_CURSES_H [cc-check-includes curses.h] + if {[get-define HAVE_CURSES_H]} { + # Linux has -lncurses, BSD -lcurses. Both have + msg-result "Found curses.h" + if {[my-check-function-in-lib waddnwstr ncursesw]} { + msg-result "Found -lncursesw" + set LIB_CURSES "-lncursesw -lpanelw" + } elseif {[my-check-function-in-lib initscr ncurses]} { + msg-result "Found -lncurses" + set LIB_CURSES "-lncurses -lpanel" + } elseif {[my-check-function-in-lib initscr curses]} { + msg-result "Found -lcurses" + set LIB_CURSES "-lcurses -lpanel" + } + } +} +if {"" eq $LIB_CURSES} { + user-error "!Curses! Foiled again!" +} else { + puts { + ************************************************************ + If your build fails due to missing functions such as + waddwstr(), make sure you have the ncursesW development + package installed. Some platforms combine the "w" and non-w + curses builds and some don't. + + The package may have a name such as libncursesw5-dev or + some such. + ************************************************************ + } +} +define LIB_CURSES $LIB_CURSES +define CFLAGS_CURSES $CFLAGS_CURSES +unset LIB_CURSES +unset CFLAGS_CURSES + puts "Checking for compile_commands.json support..." if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} { msg-result "Compiler supports compile_commands.json." define MAKE_COMPILATION_DB yes @@ -382,44 +195,18 @@ msg-result "Compiler does not support compile_commands.json." define MAKE_COMPILATION_DB no } define CFLAGS $cFlags - -if {[opt-bool shared]} { - msg-result "Enabling build of shared library." - define LIBFOSSIL_SHARED 1 -} else { - define LIBFOSSIL_SHARED 0 - msg-result "Disabling build of shared library." -} - -if {[opt-bool static]} { - msg-result "Enabling build of static library." - define LIBFOSSIL_STATIC 1 -} else { - define LIBFOSSIL_STATIC 0 - msg-result "Disabling build of static library." -} - if {[opt-bool loud]} { define BUILD_QUIETLY 0 puts "Using 'loud' build mode." } else { define BUILD_QUIETLY 1 puts "Enabling quiet build mode. Use --loud to enable loud mode." } - -set dotBin [find-an-executable dot] -if {[string length $dotBin]} { - define DOXYGEN_HAVE_DOT YES - msg-result "Adding HAVE_DOT=YES to doxyfile." -} else { - define DOXYGEN_HAVE_DOT NO -} - # Creates mkefile(-like) file $name from $name.in but explicitly makes # the output read-only, to avoid inadvertent editing (who, me?). proc makeFromDotIn {name} { catch { exec chmod u+w $name } @@ -428,15 +215,12 @@ } # Each generated Makefile requires an input file with a .in extension: set makefiles { config.make Makefile - doc/Doxyfile src/Makefile fnc/Makefile - bindings/Makefile - bindings/cpp/Makefile } foreach {f} $makefiles { makeFromDotIn $f } @@ -446,112 +230,5 @@ make-config-header include/fossil-scm/autoconfig.h \ -none {DOXYGEN_*} \ -bare {HAVE_* FSL_ENABLE_* _DEFAULT_SOURCE _XOPEN_SOURCE} \ -str {FSL_* PACKAGE_*} } - -######################################################################## -# Generate our autconf header by hand to allow finer control -# over the structure.... -set confH include/fossil-scm/autoconfig.h -set incGuard _NET_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_ -if {[opt-bool amal]} { - set confH libfossil-config.h - set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_ -} -puts "Generating config header $confH" - set out [open $confH w] - puts $out "\#if !defined($incGuard) -\#define $incGuard 1 -\#define FSL_AUX_SCHEMA \"$auxSchema\" -\#define FSL_CONTENT_SCHEMA \"$contentSchema\" -\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\" -\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\" -\#define FSL_SHA1_HARDENED 1 -/* Tweak the following for your system... */ -\#if !defined(HAVE_GETADDRINFO) -\# define HAVE_GETADDRINFO [get-define HAVE_GETADDRINFO 0] -\#endif -\#if !defined(HAVE_INET_NTOP) -\# define HAVE_INET_NTOP [get-define HAVE_INET_NTOP 0] -\#endif -\#if !defined(_WIN32) -\#if !defined(HAVE_DLFCN_H) -\# define HAVE_DLFCN_H [get-define HAVE_DLFCN_H 0] -\#endif -\#if !defined(HAVE_DLOPEN) -\# define HAVE_DLOPEN [get-define HAVE_DLOPEN 0] -\#endif -\#if !defined(HAVE_LIBDL) -\# define HAVE_LIBDL [get-define HAVE_LIBDL 0] -\#endif -\#if !defined(HAVE_LIBLTDL) -\# define HAVE_LIBLTDL [get-define HAVE_LIBLTDL 0] -\#endif -\#if !defined(HAVE_LSTAT) -\# define HAVE_LSTAT [get-define HAVE_LSTAT 1] -\#endif -\#if !defined(HAVE_LTDL_H) -\# define HAVE_LTDL_H [get-define HAVE_LTDL_H 0] -\#endif -\#if !defined(HAVE_LT_DLOPEN) -\# define HAVE_LT_DLOPEN [get-define HAVE_LT_DLOPEN 0] -\#endif -\#if !defined(HAVE_OPENDIR) -\# define HAVE_OPENDIR [get-define HAVE_OPENDIR 1] -\#endif -\#if !defined(HAVE_PIPE) -\# define HAVE_PIPE [get-define HAVE_PIPE 1] -\#endif -\#if !defined(HAVE_STAT) -\# define HAVE_STAT [get-define HAVE_STAT 1] -\#endif -\#if !defined(_DEFAULT_SOURCE) -\# define _DEFAULT_SOURCE [get-define _DEFAULT_SOURCE 1] -\#endif -\#if !defined(_XOPEN_SOURCE) -\# define _XOPEN_SOURCE [get-define _XOPEN_SOURCE 500] -\#endif -\#else -/* _WIN32: */ -\#if !defined(HAVE_DLFCN_H) -\# define HAVE_DLFCN_H 0 -\#endif -\#if !defined(HAVE_DLOPEN) -\# define HAVE_DLOPEN 0 -\#endif -\#if !defined(HAVE_LIBDL) -\# define HAVE_LIBDL 0 -\#endif -\#if !defined(HAVE_LIBLTDL) -\# define HAVE_LIBLTDL 0 -\#endif -\#if !defined(HAVE_LSTAT) -\# define HAVE_LSTAT 0 -\#endif -\#if !defined(HAVE_LTDL_H) -\# define HAVE_LTDL_H 0 -\#endif -\#if !defined(HAVE_LT_DLOPEN) -\# define HAVE_LT_DLOPEN 0 -\#endif -\#if !defined(HAVE_OPENDIR) -\# define HAVE_OPENDIR 1 -\#endif -\#if !defined(HAVE_PIPE) -\# define HAVE_PIPE 0 -\#endif -\#if !defined(HAVE_STAT) -\# define HAVE_STAT 0 -\#endif -\#endif -/*_WIN32*/ - -$FSL_PLATFORM_CONFIG_H - -\#endif -/* $incGuard */ -" - close $out - puts "Generated $confH." - return -} DELETED bindings/Makefile Index: bindings/Makefile ================================================================== --- bindings/Makefile +++ bindings/Makefile @@ -1,18 +0,0 @@ -#!/usr/bin/make # help out emacs -all: -SUBDIRS := -# Subdir cleanup rules and deps list must come before shakenmake.make is included -# or they must be set up manually afterwards... -ifneq (,$(CXX)) -clean-.: clean-cpp -distclean-.: distclean-cpp -SUBDIRS += cpp -all: subdir-cpp -endif - -clean-.: clean-s2 -distclean-.: distclean-s2 -include ../subdir-inc.make -SUBDIRS += s2 -$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS))) -all: subdir-s2 DELETED bindings/Makefile.in Index: bindings/Makefile.in ================================================================== --- bindings/Makefile.in +++ bindings/Makefile.in @@ -1,18 +0,0 @@ -#!/usr/bin/make # help out emacs -all: -SUBDIRS := -# Subdir cleanup rules and deps list must come before shakenmake.make is included -# or they must be set up manually afterwards... -ifneq (,$(CXX)) -clean-.: clean-cpp -distclean-.: distclean-cpp -SUBDIRS += cpp -all: subdir-cpp -endif - -clean-.: clean-s2 -distclean-.: distclean-s2 -include ../subdir-inc.make -SUBDIRS += s2 -$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS))) -all: subdir-s2 DELETED bindings/cpp/Context.cpp Index: bindings/cpp/Context.cpp ================================================================== --- bindings/cpp/Context.cpp +++ bindings/cpp/Context.cpp @@ -1,291 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" -#include - -/* only for debugging */ -#include -#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " - -namespace fsl { - - Context::~Context(){ - if(this->ownsCx){ - fsl_cx_finalize(this->f); - } - } - - void Context::setup(fsl_cx_init_opt const * opt){ - //this->f = fsl_cx_malloc(); - //if(!this->f) throw OOMException(); - assert(!this->f); - int const rc = fsl_cx_init(&this->f, opt); - if(rc){ - fsl_error err = fsl_error_empty; - if(this->f){ - fsl_error_move( &this->f->error, &err ); - fsl_cx_finalize(this->f); - this->f = NULL; - }else{ - fsl_error_set( &err, rc, - "fsl_cx_init() failed with code %s", - fsl_rc_cstr(rc) ); - } - throw Exception(err); - } - } - - Context::Context() - : f(NULL), - ownsCx(true), - dbCkout(), - dbRe(), - dbMain() - { - this->setup(NULL); - } - - Context::Context(fsl_cx_init_opt const & opt) - : f(NULL), - ownsCx(true), - dbCkout(), - dbRe(), - dbMain() - { - this->setup(&opt); - } - - Context::Context(fsl_cx * f, bool ownsHandle) - : f(f), - ownsCx(ownsHandle), - dbCkout(), - dbRe(), - dbMain() - { - - } - - Context::operator fsl_cx * () throw(){ - return this->f; - } - - Context::operator fsl_cx const * () const throw() { - return this->f; - } - - void Context::propagateError() const{ - if(this->f){ - fsl_error const * err = fsl_cx_err_get_e(this->f); - if(err->code) throw Exception(err); - } - } - - void Context::assertRC(char const * context, int rc) const{ - if(rc){ - this->propagateError(); - throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc)); - } - } - - void Context::assertHasRepo(){ - assert(this->f); - if(!fsl_needs_repo(this->f)){ - fsl_error const * err = fsl_cx_err_get_e(this->f); - assert(err->code); - throw Exception(err); - } - } - - void Context::assertHasCheckout(){ - assert(this->f); - if(!fsl_needs_ckout(this->f)){ - fsl_error const * err = fsl_cx_err_get_e(this->f); - assert(err->code); - throw Exception(err); - } - } - - fsl_cx * Context::handle() throw(){ - return this->f; - } - - fsl_cx const * Context::handle() const throw(){ - return this->f; - } - - Db & Context::db() throw() { - if(!this->dbMain.handle()){ - fsl_db * db = fsl_cx_db(*this); - if(db) this->dbMain.handle(db, false); - } - return this->dbMain; - } - - - Db & Context::dbRepo() throw() { - if(!this->dbRe.handle()){ - fsl_db * db = fsl_cx_db_repo(*this); - if(db) this->dbRe.handle(db, false); - } - return this->dbRe; - } - - Db & Context::dbCheckout() throw() { - if(!this->dbCkout.handle()){ - fsl_db * db = fsl_cx_db_ckout(*this); - if(db) this->dbCkout.handle(db, false); - } - return this->dbCkout; - } - - Context & Context::openCheckout( char const * dirName ){ - this->assertRC( "openCheckout()", - fsl_ckout_open_dir(this->f, dirName, true) ); - return *this; - } - - Context & Context::openRepo( char const * dbName ){ - this->assertRC( "openRepo()", - fsl_repo_open(this->f, dbName) ); - return *this; - } - - Context & Context::closeDbs() throw(){ - fsl_cx_close_dbs(this->f) - /* - Reminder to self: the 3 fsl_cx db handles are stored as - complete fsl_db instances (not pointers) in fsl_cx, with the - exception of the "main" db, which is just a pointer to one of - the other 3. What does that mean? It means that when we use - fsl_cx_close_dbs(), this->dbRe and friends will (if - initialized) still be pointing to those pointers... which are - (due to internal details) actually still valid, they just - refer to closed fsl_db handles. - - That's actually good for us here, except that certain - combinations of C-level ops "might" get our checkout/repo db - pointers cross a bit. - */ - ; - assert(!this->dbRe.ownsHandle()); - assert(!this->dbMain.ownsHandle()); - assert(!this->dbCkout.ownsHandle()); - this->dbRe.close(); - this->dbCkout.close(); - this->dbMain.close(); - if(this->dbRe.handle()){ - assert(!this->dbRe.handle()->dbh); - } - return *this; - } - - bool Context::ownsHandle() const throw(){ - return this->ownsCx; - } - - std::string Context::ridToArtifactUuid(fsl_id_t rid, - fsl_satype_e type){ - this->assertHasRepo(); - fsl_uuid_str uuid = fsl_rid_to_artifact_uuid(*this, rid, type); - if(!uuid){ - this->propagateError(); - throw Exception(FSL_RC_NOT_FOUND, - "Could not resolve RID %" FSL_ID_T_PFMT - " as artifact type %s.", - (fsl_id_t)rid, fsl_satype_cstr(type)); - } - std::string const & rc = uuid; - fsl_free(uuid); - return rc; - } - - std::string Context::ridToUuid(fsl_id_t rid){ - this->assertHasRepo(); - fsl_uuid_str uuid = fsl_rid_to_uuid(*this, rid); - if(!uuid){ - this->propagateError(); - throw Exception(FSL_RC_NOT_FOUND, "Could not resolve RID %" FSL_ID_T_PFMT ".", - (fsl_id_t)rid); - } - std::string const & rc = uuid; - fsl_free(uuid); - return rc; - } - - std::string Context::symToUuid(char const * symbolicName, - fsl_id_t * rid, - fsl_satype_e type){ - this->assertHasRepo(); - fsl_uuid_str uuid = NULL; - int const rc = fsl_sym_to_uuid(*this, symbolicName, type, &uuid, rid); - if(rc){ - this->propagateError(); - throw Exception(rc); - } - std::string const & rv = uuid; - fsl_free(uuid); - return rv; - } - - - fsl_id_t Context::symToRid(char const * symbolicName, fsl_satype_e type){ - this->assertHasRepo(); - fsl_id_t rv = 0; - int const rc = fsl_sym_to_rid(*this, symbolicName, type, &rv); - if(rc){ - this->propagateError(); - throw Exception(rc); - } - assert(rv>0); - return rv; - } - - fsl_id_t Context::symToRid(std::string const & symbolicName, - fsl_satype_e type){ - return this->symToRid( symbolicName.c_str(), type ); - } - - Context::Transaction::Transaction(Context &cx) - : tr( cx.db() ), - level(tr.level()){ - } - - Context::Transaction::~Transaction() throw(){ - if(this->level) this->tr.rollback(); - } - - void Context::Transaction::commit(){ - if(this->level){ - this->level = 0; - this->tr.commit(); - }else{ - throw Exception(FSL_RC_MISUSE, - "commit() called multiple times."); - } - } - - Context & Context::getContent( fsl_id_t rid, Buffer & dest ){ - int const rc = fsl_content_get( *this, rid, dest ); - if(rc){ - this->propagateError(); - throw Exception(rc); - } - return *this; - } - - Context & Context::getContent( char const * sym, Buffer & dest ){ - int const rc = fsl_content_get_sym( *this, sym, dest ); - if(rc){ - this->propagateError(); - throw Exception(rc); - } - return *this; - } - - Context & Context::getContent( std::string const & sym, Buffer & dest ){ - return this->getContent( sym.c_str(), dest ); - } - -} // namespace fsl - -#undef CERR DELETED bindings/cpp/Db.cpp Index: bindings/cpp/Db.cpp ================================================================== --- bindings/cpp/Db.cpp +++ bindings/cpp/Db.cpp @@ -1,564 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" -#include - -/* only for debugging */ -#include -#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " - -namespace fsl { - Stmt::~Stmt() throw(){ - fsl_stmt_finalize(*this); - } - - Stmt::Stmt(Db & db) throw() : - db(db), - stmt(fsl_stmt_empty){ - } - - Stmt::operator fsl_stmt * () throw(){ - return &this->stmt; - } - - Stmt::operator fsl_stmt const * () const throw() { - return &this->stmt; - } - - - void Stmt::assertPrepared() const{ - if(!this->stmt.stmt){ - throw Exception(FSL_RC_MISUSE, - "Statement is not prepared."); - } - assert(this->stmt.db); - } - - void Stmt::assertRange(short col, short base) const{ - this->assertPrepared(); - char const * errMsg = NULL; - if(col<0){ - errMsg = "column/parameter index %d is invalid"; - }else if(base){ - assert(1==base); - if(!col || col > fsl_stmt_param_count(*this)){ - errMsg = "parameter index %d is out of range"; - } - }else{ - assert(0==base); - if(col >= fsl_stmt_col_count(*this)){ - errMsg = "column index %d is out of range"; - } - } - if(errMsg) throw Exception(FSL_RC_RANGE, errMsg, col+base); - } - - void Stmt::propagateError() const{ - this->db.propagateError(); - } - - void Stmt::assertRC(char const * context, int rc) const{ - if(rc){ - this->propagateError(); - throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc)); - } - } - - Stmt & Stmt::prepare(std::string const & sql){ - return this->prepare("%s", sql.c_str()); - } - - Stmt & Stmt::prepare(Buffer const & sql){ - return this->prepare("%s", sql.c_str()); - } - - Stmt & Stmt::prepare(char const * sql, ... ){ - va_list vargs; - if(this->stmt.stmt) throw Exception(FSL_RC_MISUSE, - "Statement is prepared and not " - "yet finalized. Cowardly refusing " - "to re-prepare() non-finalized statement: %s", - this->sql()); - int rc = 0; - va_start(vargs,sql); - rc = fsl_db_preparev( this->db.handle(), *this, - sql, vargs ); - va_end(vargs); - if(rc){ - this->propagateError(); - throw Exception(rc,"SQL preparation failed for: %s", sql); - }else{ - assert(this->stmt.db == this->db.handle()); - return *this; - } - } - - int Stmt::stepCount() const throw(){ - return this->stmt.rowCount; - } - - int Stmt::paramCount() const throw(){ - return this->stmt.paramCount; - } - - int Stmt::columnCount() const throw(){ - return this->stmt.colCount; - } - - char const * Stmt::sql() const throw() { - return stmt.stmt - ? fsl_buffer_cstr(&stmt.sql) - : NULL; - } - - fsl_stmt * Stmt::handle() throw(){ - return &this->stmt; - } - - fsl_stmt const * Stmt::handle() const throw() { - return &this->stmt; - } - - Stmt & Stmt::reset(bool resetStepCounterToo){ - this->assertRC("reset()", - fsl_stmt_reset2(*this, - resetStepCounterToo)); - return *this; - } - - Stmt & Stmt::finalize() throw(){ - fsl_stmt_finalize(*this); - return *this; - } - - bool Stmt::step(){ - this->assertPrepared(); - int const rc = fsl_stmt_step(*this); - switch(rc){ - case FSL_RC_STEP_ROW: return true; - case FSL_RC_STEP_DONE: return false; - default: - this->propagateError(); - throw Exception(rc,"No idea what went wrong."); - } - } - - Stmt & Stmt::stepExpectDone(){ - if(this->step()){ - throw Exception(FSL_RC_ERROR, - "Expecting statement to return no rows: %s", - this->sql()); - } - return *this; - } - - int32_t Stmt::getInt32(short col){ - this->assertRange(col, 0); - return fsl_stmt_g_int32( *this, col ); - } - - int64_t Stmt::getInt64(short col){ - this->assertRange(col, 0); - return fsl_stmt_g_int64( *this, col ); - } - - double Stmt::getDouble(short col){ - this->assertRange(col, 0); - return fsl_stmt_g_double( *this, col ); - } - - fsl_id_t Stmt::getId(short col){ - this->assertRange(col, 0); - return fsl_stmt_g_id( *this, col ); - } - - char const * Stmt::columnName(short col){ - this->assertRange(col, 0); - return fsl_stmt_col_name(*this, col); - } - - char const * Stmt::getText(short col, fsl_size_t * length){ - this->assertRange(col, 0); - return fsl_stmt_g_text(*this, col, length); - } - - void const * Stmt::getBlob(short col, fsl_size_t * length){ - void const * v = NULL; - this->assertRange(col, 0); - fsl_stmt_get_blob(*this, col, &v, length); - return v; - } - - Stmt & Stmt::bind(short col){ - this->assertRange(col, 1); - this->assertRC("bind NULL", - fsl_stmt_bind_null(*this, col)); - return *this; - } - - Stmt & Stmt::bind(short col, int32_t v){ - this->assertRange(col, 1); - this->assertRC("bind int32", - fsl_stmt_bind_int32(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(short col, int64_t v){ - this->assertRange(col, 1); - this->assertRC("bind int64", - fsl_stmt_bind_int64(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(short col, double v){ - this->assertRange(col, 1); - this->assertRC("bind double", - fsl_stmt_bind_double(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(short col, char const * str, - fsl_int_t len, bool copyBytes){ - this->assertRange(col, 1); - this->assertRC("bind text", - fsl_stmt_bind_text(*this, col, str, len, copyBytes)); - return *this; - } - - Stmt & Stmt::bind(short col, std::string const & str){ - return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1); - } - - Stmt & Stmt::bind(short col, void const * v, - fsl_size_t len, bool copyBytes){ - this->assertRange(col, 1); - this->assertRC("bind blob", - fsl_stmt_bind_blob(*this, col, v, len, copyBytes)); - return *this; - } - - int Stmt::paramIndex(char const * name){ - this->assertPrepared(); - return fsl_stmt_param_index(*this, name); - } - - Stmt & Stmt::bind(char const * col){ - this->assertRC("bind NULL", - fsl_stmt_bind_null_name(*this, col)); - return *this; - } - - Stmt & Stmt::bind(char const * col, int32_t v){ - this->assertRC("bind int32", - fsl_stmt_bind_int32_name(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(char const * col, int64_t v){ - this->assertRC("bind int64", - fsl_stmt_bind_int64_name(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(char const * col, double v){ - this->assertRC("bind double", - fsl_stmt_bind_double_name(*this, col, v)); - return *this; - } - - Stmt & Stmt::bind(char const * col, char const * str, - fsl_int_t len, bool copyBytes){ - this->assertRC("bind text", - fsl_stmt_bind_text_name(*this, col, str, len, copyBytes)); - return *this; - } - - Stmt & Stmt::bind(char const * col, std::string const & str){ - return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1); - } - - Stmt & Stmt::bind(char const * col, void const * v, - fsl_size_t len, bool copyBytes){ - this->assertRC("bind blob", - fsl_stmt_bind_blob_name(*this, col, v, len, copyBytes)); - return *this; - } - - StmtBinder::~StmtBinder(){} - - StmtBinder::StmtBinder(Stmt &s) : st(s), col(0) - {} - - Stmt & StmtBinder::stmt(){ - return this->st; - } - - StmtBinder & StmtBinder::operator()(){ - st.bind(++this->col); - return *this; - } - - StmtBinder & StmtBinder::operator()(char const * v, fsl_int_t len, - bool copyBytes){ - st.bind(++this->col, v, len, copyBytes); - return *this; - } - - StmtBinder & StmtBinder::operator()(void const * v, fsl_size_t len, - bool copyBytes){ - st.bind(++this->col, v, len, copyBytes); - return *this; - } - - StmtBinder & StmtBinder::reset(bool alsoStatement) { - this->col = 0; - if(alsoStatement) this->st.reset(); - return *this; - } - - bool StmtBinder::step(){ - return this->st.step(); - } - - StmtBinder & StmtBinder::once(){ - this->st.stepExpectDone(); - return this->reset(); - } - - Db::~Db() throw(){ - this->close(); - } - - void Db::setup(){ - assert(!this->db); - this->db = fsl_db_malloc(); - if(!this->db) throw OOMException(); - this->ownsDb = true; - } - - Db::Db(): db(NULL), - ownsDb(true){ - } - - Db::Db(char const * filename, int openFlags) - : db(NULL), - ownsDb(true){ - try{ - this->open(filename, openFlags); - }catch(...){ - if(this->db) fsl_db_close(this->db); - throw; - } - } - - void Db::propagateError() const{ - if(this->db && this->db->error.code){ - throw Exception(this->db->error); - } - } - - void Db::assertRC(char const * context, int rc) const{ - if(rc){ - this->propagateError(); - throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc)); - } - } - - void Db::assertOpened() const{ - if(!this->db || !this->db->dbh){ - throw Exception(FSL_RC_MISUSE, - "Db is not opened."); - } - } - - Db::operator fsl_db * () throw(){ - return this->db; - } - - Db::operator fsl_db const * () const throw() { - return this->db; - } - - Db & Db::handle( fsl_db * db, bool ownsHandle )throw(){ - if(this->db != db){ - this->close(); - this->db = db; - this->ownsDb = ownsHandle; - } - return *this; - } - - Db & Db::close() throw(){ - if(this->db){ - if(this->ownsDb){ - fsl_db_close(*this); - } - this->db = NULL; - } - return *this; - } - - char const * Db::filename() throw(){ - return this->db - ? fsl_db_filename(*this, NULL) - : NULL; - } - - Db & Db::open(char const * filename, int openFlags){ - if(!this->db) this->setup(); - else if(this->db->dbh){ - throw Exception(FSL_RC_MISUSE, - "Db is already opened: %s", - this->filename()); - } - int const rc = fsl_db_open(*this, filename, openFlags); - if(rc){ - /* - The problem here is that open() can be called from the ctor, - and if the ctor throws then the dtor is not called, so we - have to free up this->db. Oh, wait... the open() ctor does - that for us, so this gets easier. - - But... if it throws from outside the ctor then we DO have to - clean up. - */ - Exception const & ex = this->db->error.code - ? Exception(this->db->error) - : Exception(rc,"fsl_db_open(%s) failed: %s", - filename, fsl_rc_cstr(rc)); - this->close(); - throw ex; - } - return *this; - } - - bool Db::isOpened() const throw(){ - return (this->db && this->db->dbh) ? true : false; - } - - bool Db::ownsHandle() const throw(){ - return this->ownsDb; - } - - fsl_db * Db::handle() throw(){ - return this->db; - } - - fsl_db const * Db::handle() const throw() { - return this->db; - } - - Db & Db::begin(){ - this->assertOpened(); - this->assertRC( - "begin()", - fsl_db_transaction_begin(*this) ); - return *this; - } - - Db & Db::commit(){ - this->assertOpened(); - this->assertRC( "commit()", - fsl_db_transaction_end(*this, 0) ); - return *this; - } - - Db & Db::rollback() throw() { - this->assertOpened(); - fsl_db_transaction_end(*this, 1); - return *this; - } - - Db & Db::exec(std::string const & sql){ - return this->exec("%s", sql.c_str()); - } - - Db & Db::exec(char const * sql, ...){ - this->assertOpened(); - va_list vargs; - int rc = 0; - va_start(vargs,sql); - rc = fsl_db_execv( *this, sql, vargs ); - va_end(vargs); - if(rc){ - this->propagateError(); - throw Exception(rc,"SQL execution failed for: %s", sql); - } - else return *this; - } - - Db & Db::execMulti(char const * sql, ...){ - this->assertOpened(); - va_list vargs; - int rc = 0; - va_start(vargs,sql); - rc = fsl_db_exec_multiv( *this, sql, vargs ); - va_end(vargs); - if(rc){ - this->propagateError(); - throw Exception(rc,"SQL multi-exec failed for: %s", sql); - } - else return *this; - } - - Db & Db::execMulti(std::string const & sql){ - return this->execMulti("%s", sql.c_str()); - } - - - Db & Db::attach(char const * filename, char const * label){ - this->assertRC( "attach()", fsl_db_attach(*this, filename, label) ); - return *this; - } - - Db & Db::detach(char const * label){ - this->assertRC( "detach()", fsl_db_detach(*this, label) ); - return *this; - } - - int Db::transactionLevel() const throw(){ - return this->db - ? this->db->beginCount - : 0; - } - - Db::Transaction::Transaction(Db & db) - : db(db), inTrans(false){ - db.begin(); - inTrans = db.transactionLevel(); - } - - Db::Transaction::~Transaction() throw(){ - if(inTrans) this->rollback(); - } - - void Db::Transaction::commit(){ - assert(inTrans); - if(inTrans>0){ - inTrans = 0; - db.commit(); - } - } - - void Db::Transaction::rollback() throw(){ - assert(inTrans); - if(inTrans){ -#if 1 - inTrans = 0; - db.rollback(); -#else - /* sane? */ - while(inTrans < db.transactionLevel()){ - db.rollback(); - } -#endif - } - } - - int Db::Transaction::level() const throw(){ - return db.transactionLevel(); - } - -}// namespace fsl - -#undef CERR DELETED bindings/cpp/Deck.cpp Index: bindings/cpp/Deck.cpp ================================================================== --- bindings/cpp/Deck.cpp +++ bindings/cpp/Deck.cpp @@ -1,414 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" -#include - -/* only for debugging */ -#include -#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " - -namespace fsl { - - Deck::~Deck() throw(){ - delete this->deltaBase; - if(this->ownsDeck){ - assert(this->d->allocStamp); - fsl_deck_finalize(this->d); - } - } - - void Deck::setup(fsl_deck * mf, fsl_satype_e type){ - if(!mf){ - assert(!this->d); - assert(this->ownsDeck); - this->d = fsl_deck_malloc(); - if(!this->d) throw OOMException(); - }else{ - assert(this->d == mf); - if(d && d->f && (d->f != this->cx.handle())){ - throw Exception(FSL_RC_MISUSE, - "Mis-matched fsl_cx contexts for deck."); - } - } - if(ownsDeck){ - fsl_deck_init( this->cx, d, type ); - } - } - - Deck::Deck(Context & cx, fsl_satype_e type) - : cx(cx), - d(NULL), - deltaBase(NULL), - ownsDeck(true){ - this->setup( NULL, type ); - } - - Deck::Deck(Context & cx, fsl_deck * d, bool ownsDeck) - : cx(cx), - d(d), - deltaBase(NULL), - ownsDeck(ownsDeck){ - if(!d){ - throw Exception(FSL_RC_MISUSE, - "A proxied Deck may not be NULL."); - } - this->setup( d, d->type ); - } - - void Deck::propagateError() const{ - fsl_error const * err = fsl_cx_err_get_e(this->cx); - if(err->code){ - throw Exception(err); - } - } - - void Deck::assertRC(char const * context, int rc) const{ - if(rc){ - this->propagateError(); - throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc)); - } - } - - Deck & Deck::cleanup() throw(){ - fsl_deck_clean(*this); - return *this; - } - - fsl_satype_e Deck::type() const throw(){ - return this->d->type; - } - - fsl_id_t Deck::rid() const throw(){ - return this->d->rid; - } - - fsl_uuid_cstr Deck::uuid() const throw(){ - return this->d->uuid; - } - - Deck::operator fsl_deck *() throw(){ - return this->d; - } - - Deck::operator fsl_deck const *() const throw(){ - return this->d; - } - - Context & Deck::context() throw() { return this->cx; } - Context const & Deck::context() const throw() { return this->cx; } - - fsl_deck * Deck::handle() throw(){ - return this->d; - } - - fsl_deck const * Deck::handle() const throw(){ - return this->d; - } - - bool Deck::hasAllRequiredCards() const throw(){ - return fsl_deck_has_required_cards(*this); - } - - Deck const & Deck::assertHasRequiredCards() const{ - if(!fsl_deck_has_required_cards(*this)){ - throw Exception(fsl_cx_err_get_e(this->cx)); - } - else return *this; - } - - bool Deck::cardIsLegal(char cardLetter) const throw(){ - return fsl_card_is_legal( this->d->type, cardLetter ); - } - - Deck & Deck::unshuffle(bool calcRCard){ - int const rc = fsl_deck_unshuffle(*this, calcRCard); - if(rc){ - this->propagateError(); - throw Exception(rc); - }else return *this; - } - - Deck const & Deck::output( fsl_output_f f, void * outState ) const{ - int const rc = fsl_deck_output( this->d, f, outState ); - if(rc) throw Exception(this->d->f->error); - else return *this; - } - - Deck const & Deck::output( std::ostream & os ) const{ - return this->output( fsl_output_f_std_ostream, &os ); - } - - Deck & Deck::save(bool isPrivate){ - int const rc = fsl_deck_save( *this, isPrivate ); - if(rc){ - this->propagateError(); - throw Exception(rc,"fsl_deck_save() failed: %s", - fsl_rc_cstr(rc)); - } - else return *this; - } - - Deck & Deck::load( fsl_id_t rid, fsl_satype_e type ){ - this->cleanup(); - int const rc = fsl_deck_load_rid( this->cx, *this, - rid, type ); - if(rc){ - this->propagateError(); - throw Exception(rc, "fsl_deck_load_rid() failed " - "with %s for symbol: %" FSL_ID_T_PFMT, - fsl_rc_cstr(rc), (fsl_id_t)rid); - }else return *this; - } - - Deck & Deck::load( char const * symbolicName, fsl_satype_e type){ - this->cleanup(); - int const rc = fsl_deck_load_sym( this->cx, *this, - symbolicName, type ); - if(rc){ - this->propagateError(); - throw Exception(rc, "fsl_deck_load_sym() failed " - "with %s for symbol: %s", - fsl_rc_cstr(rc), symbolicName); - }else return *this; - } - - Deck & Deck::load( std::string const & symbolicName, fsl_satype_e type){ - return this->load( symbolicName.c_str(), type ); - } - - Deck & Deck::setCardA( char const * name, - char const * tgt, - fsl_uuid_cstr uuid ){ - int const rc = fsl_deck_A_set(*this, name, tgt, uuid); - this->assertRC("fsl_deck_A_set()", rc); - return *this; - } - - Deck & Deck::setCardB(fsl_uuid_cstr uuid){ - if(this->deltaBase){ - delete this->deltaBase; - this->deltaBase = NULL; - } - int const rc = fsl_deck_B_set(*this, uuid); - this->assertRC("fsl_deck_B_add()", rc); - return *this; - } - - - Deck & Deck::setCardC( char const * comment ){ - int const rc = fsl_deck_C_set(*this, comment, -1); - this->assertRC("fsl_deck_C_set()", rc); - return *this; - } - - Deck & Deck::setCardD(double julianDay){ - int const rc = fsl_deck_D_set(*this, - julianDay<0 - ? fsl_julian_now() - : julianDay); - this->assertRC("fsl_deck_D_set()", rc); - return *this; - } - - Deck & Deck::setCardE( fsl_uuid_cstr uuid, double julian ){ - int const rc = fsl_deck_E_set(*this, - julian<0 - ? fsl_julian_now() - : julian, - uuid ); - this->assertRC("fsl_deck_E_set()", rc); - return *this; - } - - - Deck & Deck::addCardF(char const * name, - fsl_uuid_cstr uuid, - fsl_fileperm_e perm, - char const * oldName ){ - int const rc = fsl_deck_F_add(*this, name, uuid, perm, oldName); - this->assertRC("fsl_deck_F_add()", rc); - return *this; - } - - Deck & Deck::addCardJ( char isAppend, char const * key, char const * value ){ - int const rc = fsl_deck_J_add(*this, isAppend, key, value); - this->assertRC("fsl_deck_J_add()", rc); - return *this; - } - - - Deck & Deck::setCardK(fsl_uuid_cstr uuid){ - int const rc = fsl_deck_K_set(*this, uuid); - this->assertRC("fsl_deck_K_set()", rc); - return *this; - } - - Deck & Deck::setCardL( char const * title ){ - int const rc = fsl_deck_L_set(*this, title, -1); - this->assertRC("fsl_deck_L_set()", rc); - return *this; - } - - Deck & Deck::addCardM(fsl_uuid_cstr uuid){ - int const rc = fsl_deck_M_add(*this, uuid); - this->assertRC("fsl_deck_M_add()", rc); - return *this; - } - - - Deck & Deck::setCardN(char const * name){ - int const rc = fsl_deck_N_set(*this, name, -1); - this->assertRC("fsl_deck_N_set()", rc); - return *this; - } - - Deck & Deck::addCardP(fsl_uuid_cstr uuid){ - int const rc = fsl_deck_P_add(*this, uuid); - this->assertRC("fsl_deck_P_add()", rc); - return *this; - } - - Deck & Deck::addCardQ(char type, fsl_uuid_cstr target, - fsl_uuid_cstr baseline){ - int const rc = fsl_deck_Q_add(*this, type, target, baseline); - this->assertRC("fsl_deck_Q_add()", rc); - return *this; - } - - Deck & Deck::addCardT(fsl_tagtype_e tagType, - char const * name, - fsl_uuid_cstr uuid, - char const * value){ - int const rc = fsl_deck_T_add( *this, tagType, uuid, name, value ); - this->assertRC("fsl_deck_T_add()", rc); - return *this; - } - - - Deck & Deck::setCardU(char const * user){ - if(!user || !*user){ - user = fsl_cx_user_get(this->cx); - } - if(!user || !*user){ - throw Exception(FSL_RC_MISUSE, - "setCardU(): NULL/empty user name is not legal."); - } - int const rc = fsl_deck_U_set(*this, user); - this->assertRC("fsl_deck_U_set()", rc); - return *this; - } - - Deck & Deck::setCardW(char const * content, fsl_int_t len){ - int const rc = fsl_deck_W_set(*this, content, len); - this->assertRC("fsl_deck_W_set()", rc); - return *this; - } - - Deck::FCardIterator::~FCardIterator() throw() - {} - - Deck::FCardIterator::FCardIterator(Deck & d, bool skipDeletedFiles) - : d(&d), - fc(NULL), - skipDeleted(skipDeletedFiles) - { - int const rc = fsl_deck_F_rewind(d); - if(rc) throw Exception(rc, "fsl_deck_F_rewind() failed: %s", - fsl_rc_cstr(rc)); - fsl_deck_F_next(d, &this->fc); - } - - Deck::FCardIterator::FCardIterator() throw() - : d(NULL), - fc(NULL), - skipDeleted(false) - {} - - void Deck::FCardIterator::assertHasDeck(){ - if(!this->d) throw Exception(FSL_RC_MISUSE, - "Iterator requires a deck object."); - } - - Deck::FCardIterator & Deck::FCardIterator::operator++(){ - if(this->fc){ - do{ - int const rc = fsl_deck_F_next(*this->d, &this->fc); - if(rc) throw Exception(rc); - }while(skipDeleted && (this->fc && !this->fc->uuid)); - } - return *this; - } - - fsl_card_F const * Deck::FCardIterator::operator*(){ - return this->fc; - } - - fsl_card_F const * Deck::FCardIterator::operator->(){ - if(!this->fc) throw Exception(FSL_RC_MISUSE, - "Throwing to avoid " - "dereferencing a NULL fsl_card_F."); - return this->fc; - } - - - bool Deck::FCardIterator::operator==(FCardIterator const &rhs) const throw(){ - if(this->fc==rhs.fc) return true; - else if(!this->fc || !rhs.fc) return false; - return 0==fsl_strcmp(this->fc->name, rhs.fc->name); - } - - bool Deck::FCardIterator::operator!=(FCardIterator const &rhs) const throw(){ - if(this->fc==rhs.fc) return false; - else if(!this->fc || !rhs.fc) return true; - return 0!=fsl_strcmp(this->fc->name, rhs.fc->name); - } - - bool Deck::FCardIterator::operator<(FCardIterator const &rhs) const throw(){ - if(!this->fc) return rhs.fc ? true : false; - else if(!rhs.fc) return false; - return 0 > fsl_strcmp(this->fc->name, rhs.fc->name); - } - - Deck * Deck::baseline(){ - if(FSL_SATYPE_CHECKIN==this->d->type){ - if(this->deltaBase) return this->deltaBase; - else if(!this->d->B.uuid) return NULL; - else if(!this->d->B.baseline){ - int const rc = fsl_deck_F_rewind(*this); - this->assertRC("fsl_deck_rewind()", rc); - assert(this->d->B.baseline); - } - return this->deltaBase = new Deck(this->cx, - this->d->B.baseline, - false); - }else{ - return NULL; - } - } - - Deck::TCardIterator::TCardIterator(Deck & d) - : ParentType(d.handle()->T) - {} - - Deck::TCardIterator::TCardIterator() - : ParentType() - {} - - Deck::TCardIterator::~TCardIterator() throw() - {} - - fsl_card_T const * Deck::TCardIterator::operator->() const{ - fsl_card_T const * rv = this->currentValue(); - if( !rv ) throw Exception(FSL_RC_MISUSE, - "Throwing to avoid dereferencing a NULL " - "fsl_card_T."); - else return rv; - } - std::ostream & operator<<( std::ostream & os, Deck const & d ){ - d.output(os); - return os; - } - -} // namespace fsl - -#undef CERR DELETED bindings/cpp/Exception.cpp Index: bindings/cpp/Exception.cpp ================================================================== --- bindings/cpp/Exception.cpp +++ bindings/cpp/Exception.cpp @@ -1,121 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" -#include - -/* only for debugging */ -#include -#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " - -namespace fsl { - - Exception::~Exception() throw() { - fsl_error_clear(*this); - } - - Exception::Exception(Exception const &other) throw() - : err(fsl_error_empty){ - fsl_error_copy(&other.err, *this); - //CERR << "Exception("<code){ - fsl_error_copy( err, *this ); - }else{ - fsl_error_set( *this, FSL_RC_MISUSE, - "Exception(fsl_error const *) ctor passed a %s!", - err ? "fsl_error with code==0" : "NULL"); - } - } - - void Exception::error(int code, char const * fmt, va_list vargs) throw(){ - fsl_error_setv(*this, code, fmt, vargs); - //CERR << "Exception("<error(code, fmt, vargs); - va_end(vargs); - } - - Exception::Exception(int code, char const * fmt, va_list vargs) throw() - : err(fsl_error_empty){ - this->error(code, fmt, vargs); - } - - Exception::operator fsl_error * () throw(){ - return &this->err; - } - - Exception::operator fsl_error const * () const throw(){ - return &this->err; - } - - char const * Exception::messsage() const throw(){ - return this->what(); - } - - char const * Exception::what() const throw() { - return (FSL_RC_OOM==this->err.code) - ? fsl_rc_cstr(this->err.code) - : fsl_buffer_cstr(&this->err.msg); - } - - char const * Exception::codeString() const throw(){ - return fsl_rc_cstr(this->err.code); - } - - int Exception::code() const throw(){ - return this->err.code; - } - - OOMException::OOMException() throw() - : Exception(FSL_RC_OOM) - { - } - -} // namespace fsl - -#undef CERR DELETED bindings/cpp/Fossil.cpp Index: bindings/cpp/Fossil.cpp ================================================================== --- bindings/cpp/Fossil.cpp +++ bindings/cpp/Fossil.cpp @@ -1,193 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" - -/** - - */ -namespace fsl { - - - Buffer::~Buffer() throw(){ - this->clear(); - } - - Buffer::Buffer(fsl_size_t startingSize) - : buf(fsl_buffer_empty) - { - if(startingSize){ - if(fsl_buffer_reserve(&this->buf, startingSize)){ - throw OOMException(); - } - } - } - - Buffer::Buffer() - : buf(fsl_buffer_empty) - {} - - Buffer & Buffer::operator=(Buffer const & other){ - fsl_size_t const oldSize = this->buf.capacity; - this->reset(); - if((&other != this) - && !other.empty() - && fsl_buffer_append(*this, other.mem(), other.used()) - ){ - throw OOMException(); - } - /* A rather arbitrary heuristic to determine whether or not - we should free up some spce by to resizing buf() - after re-using it... */ - if((this->buf.capacity == oldSize) - && (this->buf.capacity > 20) - && (this->buf.used < (this->buf.capacity/2))){ - fsl_buffer_resize( *this, this->buf.used ); - } - return *this; - } - - Buffer::Buffer(Buffer const & other) - : buf() { - if(!other.empty() - && fsl_buffer_append(*this, other.mem(), other.used()) - ){ - throw OOMException(); - } - } - - Buffer::operator fsl_buffer *() throw(){ - return &this->buf; - } - - Buffer::operator fsl_buffer const *() const throw(){ - return &this->buf; - } - - fsl_buffer * Buffer::handle() throw(){ - return &this->buf; - } - - fsl_buffer const * Buffer::handle() const throw(){ - return &this->buf; - } - - bool Buffer::empty() const throw() { return 0==this->buf.used; } - - fsl_size_t Buffer::used() const throw() { return this->buf.used; } - - fsl_size_t Buffer::capacity() const throw() { return this->buf.capacity; } - - unsigned char const * Buffer::mem() const throw() { - return this->buf.used - ? this->buf.mem - : NULL; - } - - unsigned char * Buffer::mem() throw() { - return this->buf.used - ? this->buf.mem - : NULL; - } - - Buffer & Buffer::clear() throw() { - fsl_buffer_clear(*this); - return *this; - } - - Buffer & Buffer::reset() throw() { - fsl_buffer_reuse(*this); - return *this; - } - - Buffer & Buffer::reserve(fsl_size_t n){ - int const rc = fsl_buffer_reserve(*this, n); - if(rc) throw Exception(rc, "Buffer::reserve() failed"); - return *this; - } - - Buffer & Buffer::resize(fsl_size_t n){ - int const rc = fsl_buffer_resize(*this, n); - if(rc) throw Exception(rc, "Buffer::resize() failed"); - return *this; - } - - Buffer::iterator Buffer::begin() throw() { - return this->buf.used - ? this->buf.mem - : NULL; - } - - Buffer::iterator Buffer::end() throw() { - return this->buf.used - ? this->buf.mem + this->buf.used - : NULL; - } - - Buffer::const_iterator Buffer::begin() const throw() { - return this->buf.mem; - } - - Buffer::const_iterator Buffer::end() const throw() { - return this->buf.used - ? this->buf.mem + this->buf.used - : NULL; - } - - std::ostream & operator<<( std::ostream & os, Buffer const & b ){ - fsl_size_t n = 0; - char const * s = fsl_buffer_cstr2(b, &n); - if(n) os.write(s, n); - return os; - } - - Buffer & Buffer::appendf(char const * fmt, ...){ - va_list vargs; - int rc = 0; - va_start(vargs,fmt); - rc = fsl_buffer_appendfv(*this, fmt, vargs); - va_end(vargs); - if(rc){ - throw Exception(rc, "fsl_appendfv() failed: %s", - fsl_rc_cstr(rc)); - } - return *this; - } - - char const * Buffer::c_str() const throw(){ - return fsl_buffer_cstr(*this); - } - - void Buffer::toss(int errorCode) const{ - throw Exception(errorCode, "%s", fsl_buffer_cstr(*this)); - } - - int fsl_input_f_std_istream( void * state, void * dest, fsl_size_t * n ){ - std::istream * is = static_cast(state); - try{ - /** - We have to read byte-by-byte to fullfil the requirement that - we write the number of read bytes to *n. std::istream::read() - does not give us a (direct) way to know exactly how many - bytes were read before EOF. - */ - int rc; - fsl_size_t i; - unsigned char * out = (unsigned char *) dest; - for( i = 0; i < *n; ++i ){ - rc = is->get(); - if(is->eof()){ - *n = i; - return 0; - }else if(!is->good()){ - return FSL_RC_IO; - }else{ - *out++ = rc & 0xFF; - } - } - return 0; - }catch(...){ - return FSL_RC_IO; - } - } - -} // namespace fsl DELETED bindings/cpp/Makefile.in Index: bindings/cpp/Makefile.in ================================================================== --- bindings/cpp/Makefile.in +++ bindings/cpp/Makefile.in @@ -1,129 +0,0 @@ -all: -include ../../subdir-inc.make -#$(error $(TOP_SRCDIR)) -#CPPFLAGS += -I$(TOP_INCDIR) -#CPPFLAGS += -I$(TOP_SRCDIR)/include -CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h -CXXFLAGS += -fPIC -Wall -Werror - -#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include - -LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil -lz - -#ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS)))) -#$(LIBFOSSIL.LDFLAGS): -# $(MAKE) -C .. -#endif - -CPP.SRC := \ - Context.cpp \ - Db.cpp \ - Deck.cpp \ - Exception.cpp \ - Fossil.cpp \ - OStream.cpp - -OBJECTS := $(patsubst %.cpp,%.o,$(CPP.SRC)) - -libfossil++.DLL.OBJECTS := $(OBJECTS) -libfossil++.BIN.LDFLAGS := -lstdc++ -fPIC $(LIBFOSSIL.LDFLAGS)# -lsqlite3 -lz -libfossil++.LIB.OBJECTS := $(libfossil++.DLL.OBJECTS) -Makefile: Makefile.in -$(libfossil++.DLL.OBJECTS): Makefile -######################################################################## -# Shared lib -$(eval $(call ShakeNMake.CALL.RULES.DLLS,libfossil++)) -all: $(libfossil++.DLL) -$(libfossil++.DLL): $(libfossil++.DLL.OBJECTS) -#Static lib (don't work well with _some_ C++ template uses) -#$(eval $(call ShakeNMake.CALL.RULES.LIBS,libfossil++)) -#all: $(libfossil++.LIB) -#$(libfossil++.LIB): $(libfossil++.LIB.OBJECTS) - -test.BIN.OBJECTS := test.o -ifeq (0,1) -test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ -L. -lfossil++ @SH_LINKFLAGS@ -else -test.BIN.OBJECTS += $(libfossil++.DLL.OBJECTS) -test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ @SH_LINKFLAGS@ -endif -$(eval $(call ShakeNMake.CALL.RULES.BINS,test)) -all: $(test.BIN) - - -######################################################################## -# A quick-n-dirty amalgamation build... -INCD := $(TOP_INCDIR)/fossil-scm -AMAL_D := $(TOP_SRCDIR_REL) -AMAL_C := $(AMAL_D)/libfossil.c -AMAL_H := $(AMAL_D)/libfossil.h -AMAL_CPP := $(AMAL_D)/libfossil++.cpp -AMAL_HPP := $(AMAL_D)/libfossil++.hpp -AMAL_CONF.H := $(AMAL_D)/libfossil-config.h -CLEAN_FILES += $(AMAL_HPP) $(AMAL_CPP) - -$(AMAL_C) $(AMAL_H): - $(MAKE) -C $(TOP_SRCDIR_REL)/src amal - -AMAL_CPP.SRC := \ - $(CPP.SRC) - -# ACHTUNG: in AMAL_H.SRC, $(AMAL_CONF.H) MUST be included first. -AMAL_HPP.SRC := \ - $(INCD)/fossil.hpp - -$(AMAL_HPP.SRC): -$(AMAL_CPP.SRC): -$(AMAL_HPP): Makefile $(AMAL_HPP.SRC) -$(AMAL_CPP): Makefile $(AMAL_HPP) $(AMAL_CPP.SRC) -$(AMAL_CONF.H): $(MAIN_MAKEFILES) - @echo "Generating $@ ..." - cd $(TOP_SRCDIR_REL) && sh configure --amal - -$(AMAL_HPP): - @echo "Creating $@..." - @{ \ - echo '#if !defined(FSLPP_AMALGAMATION_BUILD)'; \ - echo '#define FSLPP_AMALGAMATION_BUILD 1'; \ - echo '#endif'; \ - echo '#include "$(notdir $(AMAL_H))"'; \ - } > $@ - @{ \ - for i in $(AMAL_HPP.SRC); do \ - echo "/* start of file $$i */"; \ - cat $$i; \ - echo "/* end of file $$i */"; \ - done; \ - } | sed \ - -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \ - -e '/[ ]*#[ ]*include[ ]*.*".*config\.h[>"]/d' \ - >> $@ - -$(AMAL_CPP): $(AMAL_HPP) - @echo "Creating $@..." - @echo '#include "$(notdir $(AMAL_HPP))"' > $@ - @{ \ - for i in $(AMAL_CPP.SRC); do \ - echo "/* start of file $$i */"; \ - cat $$i; \ - echo "/* end of file $$i */"; \ - done; \ - } | sed \ - -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h.*[>"]/d' \ - >> $@ - -AMAL_CPP_FLAGS := -I$(TOP_INCDIR) -I$(TOP_SRCDIR_REL) -I. -I$(TOP_SRCDIR_REL)/src# for sqlite3.h :/ -.PHONY: amal -amal: $(AMAL_CPP) - @ls -ls $(AMAL_CPP) $(AMAL_HPP) - @if which g++ >/dev/null; then \ - echo "Trying GCC..."; \ - g++ -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \ - fi; true - @if which clang >/dev/null; then \ - echo "Trying clang..."; \ - clang -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \ - fi; true - -# /amalgamation -######################################################################## DELETED bindings/cpp/OStream.cpp Index: bindings/cpp/OStream.cpp ================================================================== --- bindings/cpp/OStream.cpp +++ bindings/cpp/OStream.cpp @@ -1,206 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" - -/** - - */ -namespace fsl { - - - fsl_int_t fsl_appendf_f_std_ostream( void * state, - char const * data, - fsl_int_t n ){ - std::ostream * os = static_cast(state); - try{ - os->write( data, (std::streamsize)n ); - return (*os) ? 0 : FSL_RC_IO; - }catch(...){ - return FSL_RC_IO; - } - } - - int fsl_output_f_std_ostream( void * state, - void const * data, - fsl_size_t n ){ - std::ostream * os = static_cast(state); - try{ - os->write( (char const *)data, (std::streamsize)n ); - return (*os) ? 0 : FSL_RC_IO; - }catch(...){ - return FSL_RC_IO; - } - } - - - ContextOStreamBuf::~ContextOStreamBuf() throw(){ - this->sync(); - if( this->m_os ){ - this->m_os->rdbuf( this->m_old ); - } - } - - void ContextOStreamBuf::setup( fsl_cx * fx ){ - if(!fx) throw Exception(FSL_RC_MISUSE, - "ContextOStreamBuf requires " - "a non-NULL fsl_cx."); - this->f = fx; - this->setp( 0, 0 ); - this->setg( 0, 0, 0 ); - if(m_os) m_os->rdbuf( this ); - } - - ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f, std::ostream & os ) - : f(NULL), - m_os(&os), - m_old(os.rdbuf()){ - this->setup(f); - } - - ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f ) - : f(NULL), - m_os(NULL), - m_old(NULL){ - this->setup(f); - } - - ContextOStreamBuf::ContextOStreamBuf( Context & cx, std::ostream & os ) - : f(NULL), - m_os(&os), - m_old(os.rdbuf()){ - this->setup(cx.handle()); - } - - ContextOStreamBuf::ContextOStreamBuf( Context & cx ) - : f(NULL), - m_os(NULL), - m_old(NULL){ - this->setup(cx.handle()); - } - - - int ContextOStreamBuf::overflow( int c ){ - char const ch = c & 0xFF; - int const rc = fsl_output(this->f, &ch, 1); - if(rc) throw Exception(FSL_RC_IO, - "fsl_output() failed with code %d (%s)", - rc, fsl_rc_cstr(rc)); - else return 0; - } - - int ContextOStreamBuf::sync(){ - return this->f - ? fsl_flush(this->f) - : FSL_RC_IO; - } - - ContextOStream::~ContextOStream() throw(){ - delete this->sb; - } - - ContextOStream::ContextOStream( fsl_cx * f ) - :f(f), - sb(new ContextOStreamBuf(f, *this)){ - } - - ContextOStream::ContextOStream( Context & cx ) - :f(cx.handle()), - sb(new ContextOStreamBuf(f, *this)){ - } - - ContextOStream & ContextOStream::appendf(char const * fmt, ...){ - va_list vargs; - int rc = 0; - va_start(vargs,fmt); - rc = fsl_outputfv(this->f, fmt, vargs); - va_end(vargs); - if(rc){ - throw Exception(rc, "fsl_outputfv() failed: %s", - fsl_rc_cstr(rc)); - } - return *this; - } - - FslOutputFStreamBuf::~FslOutputFStreamBuf() throw(){ - if( this->m_os ){ - this->m_os->rdbuf( this->m_old ); - } - } - - void FslOutputFStreamBuf::setup( fsl_output_f f, void * state ){ - if(!f) throw Exception(FSL_RC_MISUSE, - "FslOutputFStream output function may " - "not be NULL."); - this->out = f; - this->outState = state; - this->setp( 0, 0 ); - this->setg( 0, 0, 0 ); - if(m_os) m_os->rdbuf( this ); - } - - FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state ) - : out(NULL), outState(NULL), - m_os(NULL), m_old(NULL) - { - this->setup(f, state); - } - - FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state, - std::ostream & os) - : out(NULL), outState(NULL), - m_os(&os), m_old(os.rdbuf()) - { - this->setup(f, state); - } - - int FslOutputFStreamBuf::overflow( int c ){ - char const ch = c & 0xFF; - int const rc = this->out( this->outState, &ch, 1); - if(rc) throw Exception(FSL_RC_IO, - "fsl_output_f() proxy failed with code " - "%d (%s)", rc, fsl_rc_cstr(rc)); - else return 0; - } - - int FslOutputFStreamBuf::sync(){ - return 0; - } - - FslOutputFStream::~FslOutputFStream() throw(){ - delete this->sb; - } - - FslOutputFStream::FslOutputFStream( fsl_output_f out, void * outState ) - : sb(new FslOutputFStreamBuf(out, outState, *this)){ - } - - fsl_int_t FslOutputFStream::fslAppendfF( void * state, - char const * s, fsl_int_t n ){ - FslOutputFStream * out = static_cast(state); - if(out->good()){ - out->write( s, (std::streamsize)n ); - return out->good() ? 0 : -1; - }else return -1; - } - - FslOutputFStream & FslOutputFStream::appendf(char const * fmt, ...){ - va_list vargs; - fsl_int_t rc = 0; - va_start(vargs,fmt); - rc = fsl_appendfv(FslOutputFStream::fslAppendfF, this, fmt, vargs); - va_end(vargs); - if(rc<0) throw Exception(FSL_RC_IO, - "fsl_appendfv() failed mysteriously."); - return *this; - } - - BufferOStream::BufferOStream(fsl_buffer * b) : - FslOutputFStream(fsl_output_f_buffer, b){ - if(!b) throw Exception(FSL_RC_MISUSE, - "fsl_buffer argument may not be NULL."); - } - - BufferOStream::~BufferOStream() throw(){ - } - -} // namespace fsl DELETED bindings/cpp/test.cpp Index: bindings/cpp/test.cpp ================================================================== --- bindings/cpp/test.cpp +++ bindings/cpp/test.cpp @@ -1,271 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include "fossil-scm/fossil.hpp" /* MUST come first b/c of config bits. */ -#include -#include -#include -#include -#include /* EXIT_SUCCESS, EXIT_FAILURE */ - -#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " -#define COUT std::cout << __FILE__ << ":" << std::dec << __LINE__ << " : " - -#define RCStr(C) fsl_rc_cstr(C) - -/** - A functor for use with fsl::Stmt::eachRow(). -*/ -struct RowDumper { - std::ostream & os; - char const * separator; - bool showHeader; - explicit RowDumper(std::ostream & os = std::cout) - : os(os), - separator("\t"), - showHeader(true) - {} - void operator()(fsl::Stmt &s) const{ - int i; - int const n = s.columnCount(); - if(this->showHeader && (1==s.stepCount())){ - for(i = 0; i < n; ++i ){ - this->os << (i ? this->separator : "") << s.columnName(i); - } - this->os << '\n'; - } - for(i = 0; i < n; ++i ){ - this->os << (i ? this->separator : "") << s.getText(i); - } - this->os << '\n'; - } -}; - -static void test_db_1(){ - namespace f = fsl; - char const * dbName = 0 - ? "/fail" - : ":memory:" - ; - //using fsl::Exception; - //throw Exception(FSL_RC_NYI, "Just %s", "testing"); - f::Db db; - f::Stmt st(db); - db.open(dbName, FSL_OPEN_F_RWC); - COUT << "Opened db: "< LI; - LI li; - li.push_back("4"); - li.push_back("fource. Or fice."); - b.bindList(li).insert(); - - st.finalize() - .prepare("SELECT rowid, a, b FROM t ORDER BY a") - .eachRow( RowDumper() ) - .finalize(); - - f::Buffer sb; - (sb << "SELECT ").appendf("%Q", "percent 'Q'"); - st.prepare(sb) - .eachRow( RowDumper() ); - -} - -static void test_stream_1(fsl::Context & cx){ - namespace f = fsl; - - f::ContextOStream fout(cx); - { - char const * sym = "rid:1"; - fout << "UUID of ["< "<type) - << tag->name; - } - fout << '\n'; - } - - fout << "Re-read artifact via Deck::load():\n"; - fout << f::Deck(cx).load( d.rid() ); - - f::Buffer content; - cx.getContent( d.rid(), content ); - fout << "And again via Context::getContent():\n" << content; - } - - if(0){ - fsl_id_t rid = 1; - fout << "Loading checkin #"<name); - assert(fit->name == fc->name); - } - fout << "Traversed " - < $@ -$(UNIT_MEGA2.S2): $(UNIT_SCRIPT_LIST) Makefile - @echo "Generating $@..." - @{ \ - false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \ - for i in $(UNIT_SCRIPT_LIST); do \ - echo "import(false,'$$i');"; \ - done; \ - } > $@ - -.PHONY: unit unit-proxy unit2 -unit-proxy: $(f-s2sh.BIN) $(UNIT_GENERATED) - @for i in $(UNIT_SCRIPTS_ALL); do \ - cmd="$(UNIT_RUN_CMD) -f $$i"; \ - echo "****************************** Script [$$i]"; \ - echo $$cmd; $$cmd || exit $$?; \ - echo "****************************** Done [$$i]"; \ - done - @echo "Done running through unit test scripts." - -unit: UNIT_SCRIPTS_ALL:=$(UNIT_SCRIPT_LIST) $(UNIT_GENERATED) -unit: unit-proxy -unit2: UNIT_SCRIPTS_ALL:=$(filter unit2/%,$(UNIT_SCRIPT_LIST)) -unit2: unit-proxy -.PHONY: unit-r -.PHONY: unit-rc -unit-r: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -unit-r: unit -unit-s: S2SH.SHELL.FLAGS:=--no-init-script -rc -rc -nosi -unit-s: unit -unit-rc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -unit-rc: unit -unit-rsc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -nosi -unit-rsc: unit -units: - @for i in unit unit-r unit-rc unit-s unit-rsc; do \ - echo "Making $$i ..."; \ - $(MAKE) $$i || exit $$?; \ - done -include vg.make DELETED bindings/s2/cliapp.c Index: bindings/s2/cliapp.c ================================================================== --- bindings/s2/cliapp.c +++ bindings/s2/cliapp.c @@ -1,548 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include "cliapp.h" -#include -#include -#include /* vsprintf() */ -#include /* abort() */ - -#ifndef CLIAPP_ENABLE_READLINE -# define CLIAPP_ENABLE_READLINE 0 -#endif - -#ifndef CLIAPP_ENABLE_LINENOISE -# define CLIAPP_ENABLE_LINENOISE 0 -#endif - -#if CLIAPP_ENABLE_READLINE && CLIAPP_ENABLE_LINENOISE -# error "Use *one* of CLIAPP_ENABLE_READLINE or CLIAPP_ENABLE_LINENOISE, not both." -#endif - -#if CLIAPP_ENABLE_LINENOISE -# include "linenoise/linenoise.h" -#elif CLIAPP_ENABLE_READLINE -# include "readline/readline.h" -# include "readline/history.h" -#endif - - -#if 1 -#define MARKER(pfexp) if(1) printf("%s:%d:\t",__FILE__,__LINE__); \ - if(1) printf pfexp -#else -static void noop_printf(char const * fmt, ...) {} -#define MARKER(pfexp) if(0) noop_printf pfexp -#endif -#define MESSAGE(pfexp) printf pfexp - -struct CliApp cliApp = { -0/*argc*/, 0/*argv*/, -0/*cursorNonflag*/, -0/*errMsg*/, -0/*flags*/, -vprintf/*print*/, -0/*argCallback*/, -{/*doubleDash*/ - 0/*argc*/, - 0/*argv*/ -}, -{/*lineread*/ - CLIAPP_ENABLE_LINENOISE ? 1 : (CLIAPP_ENABLE_READLINE ? 2 : 0) /*enabled*/, - 0 /* historyFile */, 0 /*needsSave*/ -} -}; - -enum { -/** Buffer size for cliapp_errmsg() */ -CLIAPP_MSGBUF_SIZE = 1024 * 4, -/** Buffer size for cliapp_process_argv() argument keys. */ -CLIAPP_ARGV_KBUF_SIZE = 1024 * 2, -/** Maximum cliapp_process_argv() arg count. */ -CLIAPP_ARGV_COUNT = (int)(1024 * 2 / sizeof(CliAppArg)) -}; - -static char cliAppErrBuf[CLIAPP_MSGBUF_SIZE] = {0}; - -static char const * cliapp_verrmsg(char const * fmt, va_list args){ - int rc; - char * buf = cliAppErrBuf; - rc = vsprintf(buf, fmt, args) - /* Noting that vsnprintf() requires C99 and s2 aims to compile in - strict C89 mode. */; - if(rc >= CLIAPP_MSGBUF_SIZE){ - fprintf(stderr,"%s:%d: Internal misuse of error message buffer. " - "Dangerous buffer overrun!\n", - __FILE__, __LINE__); - abort(); - } - return cliApp.errMsg = buf; -} -static char const * cliapp_errmsg(char const * fmt, ...){ - /** Stores error messages, at least until it's overwritten. */ - char const * rc; - va_list args; - va_start(args,fmt); - rc = cliapp_verrmsg(fmt, args); - va_end(args); - return rc; -} - -void cliapp_err_clear(){ - cliApp.errMsg = 0; - cliAppErrBuf[0] = 0; -} - -char const * cliapp_err_get(){ - return cliApp.errMsg; -} - -static const CliAppSwitch CliAppSwitch_end = CliAppSwitch_sentinel; -#define cliapp__switch_is_end(S) \ - (0==memcmp(S, &CliAppSwitch_end, sizeof(CliAppSwitch))) -int cliapp_switch_is_end(CliAppSwitch const *s){ - return cliapp__switch_is_end(s); -} - -CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg, - int alsoFlag){ - CliAppSwitch const * a = cliApp.switches; - for(; !cliapp__switch_is_end(a); ++a){ - if((alsoFlag==0 || arg->dash==a->dash) - && 0==strcmp(a->key, arg->key)){ - return a; - } - } - return 0; -} - -CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg){ - CliAppArg const * a = arg; - CliAppArg const * tail = cliApp.argv + cliApp.argc; - if(arg < cliApp.argv || arg >= tail){ - return 0; - } - for( ++a; a < tail; ++a ){ - if(a->key && 0==strcmp(a->key, arg->key)) return a; - } - return 0; -} - -CliAppArg const * cliapp_arg_nonflag(){ - CliAppArg const * p = 0; - for(; cliApp.cursorNonflag < cliApp.argc;){ - p = cliApp.argv + cliApp.cursorNonflag; - ++cliApp.cursorNonflag; - if(p->key && 0==p->dash){ - return p; - } - } - return 0; -} - -void cliapp_arg_nonflag_rewind(){ - cliApp.cursorNonflag = 1; -} - -CliAppArg const * cliapp_arg_flag(char const * key1, - char const * key2, - int *atPos){ - int i = atPos ? *atPos : 1; - assert(key1 || key2); - if(!key1 & !key2) return 0; - for( ; i < cliApp.argc; ++i ){ - CliAppArg const * p = cliApp.argv + i; - if(!p->key) continue; - else if((key1 && 0==strcmp(key1,p->key)) - || - (key2 && 0==strcmp(key2,p->key))){ - if(atPos) *atPos = i; - return p; - } - } - return 0; -} - -char const * cliapp_flag_prefix( int flag ){ - char const * flags[4] = {"+", "", "-", "--"}; - assert(flag>=-1 && flag<=2); - return flag>=-1 && flag<=2 ? flags[flag+1] : ""; -} - -void cliapp_switches_visit( CliAppSwitch_visitor_f visitor, - void * state ){ - CliAppSwitch const * s = cliApp.switches; - assert(s); - for( ; !cliapp__switch_is_end(s); ++s){ - if(visitor(s, state)) break; - } -} - -void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state, - unsigned short skipArgs ){ - int i = skipArgs; - CliAppArg const * a = i < cliApp.argc - ? &cliApp.argv[i] : NULL; - for( ; i < cliApp.argc; ++i, ++a ){ - if(a->key && visitor(a, i, state)) break; - } -} - -void cliapp_printv(char const *fmt, va_list vargs){ - if(cliApp.print){ - cliApp.print(fmt, vargs); - } -} - -void cliapp_print(char const *fmt, ...){ - if(cliApp.print){ - va_list vargs; - va_start(vargs,fmt); - cliApp.print(fmt, vargs); - va_end(vargs); - } -} - -void cliapp_warn(char const *fmt, ...){ - va_list vargs; - va_start(vargs,fmt); - vfprintf(stderr, fmt, vargs); - va_end(vargs); -} - -#if 0 -static void cliapp_perr(char const *fmt, ...){ - va_list vargs; - va_start(vargs,fmt); - cliapp_verrmsg(fmt,vargs); - va_end(vargs); - va_start(vargs,fmt); - vfprintf(stderr, fmt, vargs); - va_end(vargs); -} -#endif - -/** - The list of arguments pointed to by cliApp.argv. -*/ -static CliAppArg cliAppArgv[CLIAPP_ARGV_COUNT]; -/** - An internal buffer used to store --flag keys for those keys which - require transformation. This is: for --flag=value, we copy the - "flag" part to this buffer so we can NUL-terminate it. For flags - with no '=' we simply refer to the original argv string pointers, - as those are NUL terminated. (We "could" modify the original - globals instead, but the thought of doing so makes me a ill.) -*/ -static char cliAppKeyBuf[CLIAPP_ARGV_KBUF_SIZE] -/* Store all NUL-terminated flag keys here, stripped of their - leading dashes and terminated at their '=' (if they had - one). */; - -/** - Internal helper for cliapp_process_argv() which checks list to see - if s is contained in it. list must be terminated by a NULL pointer. -*/ -static char cliapp_check_seen( CliAppSwitch const * const * list, - CliAppSwitch const * s ){ - int i = 0; - CliAppSwitch const * p; - assert(s); - while(1){ - p = list[i++]; - if(p == s) return 1; - else if(!p) break; - } - return 0; -} - -int cliapp_process_argv(int argc, char const * const * argv, - unsigned int reserved ){ - /*static CliAppSwitch const * sPending = 0; - static CliAppArg * aPending = 0;*/ - int i, rc = 0, doubleDashPos = 0; - char * k = &cliAppKeyBuf[0]; - char const *errMsg = 0; - CliAppSwitch const * switchPendingVal = 0 - /* Switch with the CLIAPP_F_SPACE_VALUE which is expecting - a value in the next argument. */; - CliAppArg * argPendingVal = 0 - /* arg counterpart of switchPendingVal */; - CliAppSwitch const * appSwitch = 0; - CliAppSwitch const * seenList[CLIAPP_ARGV_COUNT+1] - /* switches we've seen so far, so we can check for/enforce - the CLIAPP_F_ONCE flag */; - int seenCount = 0 /* number of entries in seenList */; - if(reserved){/*unused*/} - memset(seenList, 0, sizeof(seenList)); - assert(cliApp.switches - && "cliApp.switches must be set by the client " - "before calling this."); - cliApp.argv = &cliAppArgv[0]; - memset(&cliAppArgv[0], 0, sizeof(cliAppArgv)); -#define KCHECK if(k>=&cliAppKeyBuf[0]+CLIAPP_ARGV_KBUF_SIZE) goto err_overflow -#define BADFLAG(MSG) errMsg=MSG; goto misuse - for(i = 0; i < argc && 0==doubleDashPos; ++i){ - CliAppArg * p; - char const * arg; - int dashes = 0 /* number of dashes on the current flag */; - int callbackIndex = i - /* index to pass to callbacks. Gets modified in one case */; - if(i == CLIAPP_ARGV_COUNT){ - cliapp_errmsg("Too many (%d) arguments: internal buffer " - "limit (%d) would be reached.", argc, - CLIAPP_ARGV_COUNT); - return CLIAPP_RC_RANGE; - } - appSwitch = 0; - p = cliApp.argv + i; - arg = argv[i]; - if(*arg=='+'){ - dashes = -1; - ++arg; - } - while(*arg && '-'==*arg && dashes<4){ - if(-1==dashes){ - dashes = 3 /* trigger error below */; - break; - } - ++dashes; - ++arg; - } - if(dashes>2){ - BADFLAG("Too many dashes on flag."); - } - p->dash = dashes; - p->key = dashes ? k : arg - /* dashed args get stored, without dashes, in cliAppKeyBuf so - that we can terminate them with a NUL at their '='. Args - with no dashes are referenced to as-is - there's no need to - copy them for termination purposes. We don't know, at this - point, whether a '=' is pending, but we pessimistically - (optimistically?) assume there is. - */; - while(*arg && '='!=*arg){ - if(dashes){ - *k++ = *arg++; - KCHECK; - } - else ++arg; - } - if(*arg && !*p->key){ - BADFLAG("Empty flag name."); - } - KCHECK; - if(*arg){ - assert('='==*arg); - if(-1==dashes && '='==*arg){ - BADFLAG("+flags may not have a value."); - } - p->value = ++arg; - }else if(!*p->key && 2==dashes && !doubleDashPos){ - doubleDashPos = i; - p->key = p->value = 0; - } - if(dashes){ - *k++ = 0; - } - if(doubleDashPos && !switchPendingVal){ - /* Once we've encountered --, we must not continue to process - flags because a flag after -- is typically intended for some - downstream process, not the app on whose behalf we're - running, and might semantically collide with flags from our - own app. Also, such flags might have syntaxes we cannot - handle, so even processing them as flags is potentially a - bug. Rather than process them, we point cliApp.doubleDash.* - to that state so the client can deal with it (possibly be - passing it back into this function!). - - If switchPendingVal is not NULL we need to fall through to - catch the error case of a missing value at the end of the - arguments. - */ - break; - }else if(!doubleDashPos){ - ++cliApp.argc; - } - if(0){ - MARKER(("Arg #%d %d %s %s %s\n", - i, p->dash, p->key, - p->value ? "=" : "", - p->value ? p->value : "")); - } - if(switchPendingVal){ - assert(argPendingVal); - if(p->dash){ - appSwitch = switchPendingVal /* for error string */; - BADFLAG("Got a flag while the previous flag was expecting " - "a value"); - }else{ - argPendingVal->value = p->key; - p->key = p->value = 0; - p->dash = 0; - p = argPendingVal /* for the upcoming callback(s) */; - argPendingVal = 0; - switchPendingVal = 0; - --callbackIndex; - } - } - assert(!doubleDashPos); - if(p->dash && p->key){ /* -flag/--flag/+flag */ -#if defined(DEBUG) - static int check = 0; - check = callbackIndex; - assert(cliapp_arg_flag(p->key, 0, &check)); - assert(check == callbackIndex); -#endif - appSwitch = cliapp_switch_for_arg(p, 1); - if(!appSwitch){ - BADFLAG("Unknown flag"); - } - else if((appSwitch->pflags & CLIAPP_F_ONCE) - && cliapp_check_seen(seenList, appSwitch)){ - BADFLAG("Flag may only be provided once."); - } - else if(appSwitch->value && !p->value){ - if(i==argc-1){ - BADFLAG("Flag expecting a value at the end of the arguments"); - } - switchPendingVal = appSwitch; - argPendingVal = p; - }else if(appSwitch->callback){ - rc = appSwitch->callback(callbackIndex, appSwitch, p); - if(rc) return rc; - } - if(!switchPendingVal){ - seenList[seenCount++] = appSwitch; - } - }else{ - assert(cliapp_arg_nonflag()==p); - } - if(!switchPendingVal && cliApp.argCallack){ - rc = cliApp.argCallack(callbackIndex, appSwitch, p); - if(rc) return rc; - } - }/*for(each arg)*/ - if(cliApp.argCallack){ - rc = cliApp.argCallack(i, 0, 0); - if(rc) return rc; - } - -#undef BADFLAG -#undef KCHECK - cliApp.cursorNonflag = 1 /* skip argv[0] */; - if(doubleDashPos && doubleDashPos+1dash) : "", - (CLIAPP_MSGBUF_SIZE/4), - appSwitch ? appSwitch->key : "", - /*(4)*/ - appSwitch && appSwitch->value ? "=" : "", - CLIAPP_MSGBUF_SIZE/4, - appSwitch ? appSwitch->value : ""); - return CLIAPP_RC_FLAG; - err_overflow: - cliapp_errmsg("Too many CLI flag keys: their accumulated names " - "would overrun our %d-byte buffer.", - CLIAPP_ARGV_KBUF_SIZE); - return CLIAPP_RC_RANGE; -} - -char * cliapp_lineedit_read(char const * prompt){ -#if CLIAPP_ENABLE_LINENOISE - return linenoise(prompt); -#elif CLIAPP_ENABLE_READLINE - return readline(prompt); -#else - if(prompt){/*avoid unused param warning*/} - return 0; -#endif -} - -void cliapp_lineedit_free(char * line){ -#if CLIAPP_ENABLE_LINENOISE || CLIAPP_ENABLE_READLINE - free(line); -#else - if(line){ - assert(!"Where did this memory come from?"); - } -#endif -} - -int cliapp_lineedit_load(char const * fname){ - if(!fname) fname = cliApp.lineread.historyFile; - if(!fname || !*fname) return 0; -#if CLIAPP_ENABLE_LINENOISE - return linenoiseHistoryLoad(fname); -#elif CLIAPP_ENABLE_READLINE - return read_history(fname); -#else - if(fname){/*avoid unused param warning*/} - return CLIAPP_RC_UNSUPPORTED; -#endif -} - -int cliapp_lineedit_save(char const * fname){ - int rc = 0; - if(!fname) fname = cliApp.lineread.historyFile; - if(!cliApp.lineread.needsSave - || !fname || !*fname) return 0; -#if CLIAPP_ENABLE_LINENOISE - rc = linenoiseHistorySave(fname) ? CLIAPP_RC_IO : 0; -#elif CLIAPP_ENABLE_READLINE - rc = write_history(fname) ? CLIAPP_RC_IO : 0; -#else - if(fname){/*avoid unused param warning*/} - rc = CLIAPP_RC_UNSUPPORTED; -#endif - if(!rc) cliApp.lineread.needsSave = 0; - return rc; -} - -int cliapp_lineedit_add(char const * line){ - assert(line); -#if CLIAPP_ENABLE_LINENOISE - cliApp.lineread.needsSave = 1; - linenoiseHistoryAdd(line); - return 0; -#elif CLIAPP_ENABLE_READLINE - cliApp.lineread.needsSave = 1; - add_history(line); - return 0; -#else - if(line){/*avoid unused param warning*/} - return CLIAPP_RC_UNSUPPORTED; -#endif -} - -int cliapp_repl(CliApp_repl_f f, char const * const * prompt, - int addHistoryPolicy, void * state){ - char * line; - int rc = 0; - while( !rc && (line = cliapp_lineedit_read(*prompt)) ){ - if(addHistoryPolicy<0) cliapp_lineedit_add(line); - rc = f(line, state); - if(!rc && addHistoryPolicy>0) cliapp_lineedit_add(line); - cliapp_lineedit_free(line); - } - return rc; -} - -#undef cliapp__switch_is_end DELETED bindings/s2/cliapp.h Index: bindings/s2/cliapp.h ================================================================== --- bindings/s2/cliapp.h +++ bindings/s2/cliapp.h @@ -1,623 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#ifndef NET_WH_CLIAPP_H_INCLUDED -#define NET_WH_CLIAPP_H_INCLUDED -/** - A mini-framework for handling some of the grunt work required by - CLI apps. It's main intent is to provide a halfway sane system for - handling CLI flags. It also provides an abstraction for CLI - editing, supporting either libreadline or liblinenoise, but only - supporting the most basic of editing facilities, not - library-specific customizations (e.g. custom key bindings). - - This API has no required dependencies beyond the C89 standard - libraries and requires no dynamic memory unless it's configured to - use an interactive line-reading backend. - - License: Public Domain - - Author: Stephan Beal -*/ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - Flags for use with the CliAppSwitch::pflags field to modify - how cliapp_process_argv() handles the switch. -*/ -enum cliapp_switch_flags { -/** - Indicates that a CliAppSwitch is only allowed to be provided once. - If encountered more than once by cliapp_process_argv(), an error - is triggered. -*/ -CLIAPP_F_ONCE = 1, -/** - Indicates that a given CliAppSwitch requires a value and accepts - its value either in the form (-switch=value) or (-switch value). - When such a flag is encountered, cliapp_process_argv() will report - an error if the switch has no value (even if the flag appears at - the end of the arguments list or immediately before the - special-case "--" flag). - - Without this flag, switch values are only recognized in the form - (-switch=flag). -*/ -CLIAPP_F_SPACE_VALUE = 2 -/*TODO?: CLIAPP_F_VALUE_REQUIRED = 4 (implied by CLIAPP_F_SPACE_VALUE) */ -}; - -/** - Result codes used by various library routines. -*/ -enum cliapp_rc { -/** The non-error code */ -CLIAPP_RC_OK = 0, -/** Indicates an error in flag processing. */ -CLIAPP_RC_FLAG = -1, -/** Indicates a range-related error, e.g. buffer overrun or too many - CLI arguments. */ -CLIAPP_RC_RANGE = -2, -/** Indicates that an unsupported operation was requested. */ -CLIAPP_RC_UNSUPPORTED = -3, -/** Indicates some sort of I/O error. */ -CLIAPP_RC_IO = -4 -}; - - -/** - Holds state for a single CLI argument, be it a flag or non-flag. -*/ -struct CliAppArg { - /** - Must be 1 for single-dash flags, 2 for double-dash flags, and -1 - for '+' flags, and 0 for non-flags. - */ - int dash; - /** - Fow switches, this holds the switch's key, without dashes. For - non-switches, it holds the argument's value. - **/ - char const * key; - /** - For switches, the description of their value (if any) is stored here. - For non-switches, this is 0. - */ - char const * value; - /** - Arbitrary value which may be set/used by the client. It's not - used/modifed by this API. - */ - int opaque; -}; -typedef struct CliAppArg CliAppArg; - -struct CliAppSwitch; - -/** - A callback type used by cliapp_process_argv() to notify the app - when a CLI argument is processed which matches one of the app's - defined flags. It is passed the argument index, the switch (if any) - and the argument. - - Note that when processing switches flagged with - CLIAPP_F_SPACE_VALUE, the indexes passed to this function might - have gaps, as they skip over the VAL part of (-f VAL), instead - effectively transforming that to (-f=VAL) before calling the - callback for the -f switch. - - If the switch argument is NULL, the argument will be a non-flag - value. Note that arguments starting at the special-case "--" flag - are not passed on to the callback. Instead, such arguments get - reported via the cliApp.doubleDash member. - - It gets passed two NULL values one time at the end of processing in - order to allow the client code to do any final validation. - - If it returns non-0, cliapp_process_argv() will fail and return the - callback's result. -*/ -typedef int (*CliAppSwitch_callback_f)(int ndx, struct CliAppSwitch const * appSwitch, - CliAppArg * arg); -/** - Models a single flag/switch for a CLI app. It's not called - CliAppFlag because that proved confusing together with CliAppArg. -*/ -struct CliAppSwitch { - /** - Can be used by clients to, e.g. group help items by type or - set various levels of help verbosity. - */ - int opaque; - /** - As documented for CliAppArg::dash. - */ - int dash; - /** - The flag name, without leading dashes. - */ - char const * key; - /** - A human-readable description of its expected value, or 0 if the - flag does not require a value. - - BUG? It may still be assigned a value by the caller. We - currently require that behaviour for a special-case arg handler - in s2sh2. - */ - char const * value; - /** - Brief help text. - */ - char const * brief; - /** - Optional detailed help text. - */ - char const * details; - - /** - Optional callback to be passed a CliAppArg instance after it's been - initialized and confirmed as being a valid arg (defined in - cliApp.switches). If cliApp.argCallack is also used, both - callbacks are called, but this one is called first. - */ - CliAppSwitch_callback_f callback; - - /** - Reserved for future use by the cliapp interface, e.g. marking - "has seen this flag before" in order to implement only-once - behaviour. - */ - int pflags; -}; -typedef struct CliAppSwitch CliAppSwitch; - -/** - Client-defined CliApp.argv arrays MUST end with an entry identical - to this one. The iteration-related APIs treat any entry which - memcmp()'s as equivalent to this entry as being the end of th list. - - @see cliapp_switch_is_end() -*/ -#define CliAppSwitch_sentinel {0,0,0,0,0,0,0,0} - -/** - Returns true (non-0) if the given object memcmp()'s as equivalent - to CliAppSwitch_sentinel. - */ -int cliapp_switch_is_end(CliAppSwitch const *s); - -/** - vprintf()-compatible logging/printing interface for use with - CliApp. -*/ -typedef int (*CliApp_print_f)(char const *, va_list); - -/** - A callback for use with cliapp_switches_visit(). It is passed the - switch object and an arbitrary state pointer provided by the - caller of that function. -*/ -typedef int (*CliAppSwitch_visitor_f)(CliAppSwitch const *, void *); - -/** - Global app state. This class is intended to represent a singleton, - the cliApp object. -*/ -struct CliApp { - /** - Number of arguments in this->argv. It is modified as - cliapp_process_argv() executes and only counts arguments - up to, but not including the special-case "--" flag. - */ - int argc; - - /** - Arguments processed by cliapp_process_argv(). Contains - this->argc entries. This memory is not valid until - cliapp_process_argv() has succeeded. - */ - CliAppArg * argv; - - /** - Internal cursor for traversing non-flag arguments using - cliapp_arg_nonflag(). Holds the *next* index to be used by that - function. - */ - int cursorNonflag; - - /** - May be set to an error description by certain APIs and it may - point to memory which can mutate. - */ - char const * errMsg; - - /** - Must be set up by the client *before* calling - cliapp_process_argv() and its final entry MUST be an object for - which cliapp_switch_is_end() returns true (that's how we know when - to stop processing). - */ - CliAppSwitch const * switches; - - /** - If this is non-NULL, cliapp_print() and friends will use it for - output, otherwise they will elide all output. This defaults to - vprintf(). - */ - CliApp_print_f print; - - /** - If set, it gets called each time cliapp_process_argv() processes - an argument. If it returns non-0, processing fails. - - After processing successfully completes, the callback is called - one final time with NULL arguments so that the callback can - perform any end-of-list validation or whatnot. - - Using this callback effectively turns cliapp_process_argv() into - a push parser, which turns out to be a pretty convenient way to - handle CLI flags. - */ - CliAppSwitch_callback_f argCallack; - - /** - If cliapp_process_argv() encounters the "--" flag, and additional - arguments follow it, this object gets filled out with information - about them. - - Note that encountering "--" with no following arugments is not - considered an error. - */ - struct { - /** - If cliapp_process_argv() encounters "--", this value gets set - to the number of arguments available in the original argv array - immediately following (but not including) the "--" flag - */ - int argc; - /** - If cliapp_process_argv() encounters "--", and there are - arguments after it, this value is set to the list of arguments - (from the original argv array) immediately following the "--" - flag. If "--" is not encountered, or there are no arguments - after it, this member's value is 0. - */ - char const * const * argv; - } doubleDash; - - /** - State related to interactive line-editing/reading. - */ - struct { - /** - If enabled at compile-time, this has a value of 1 (for - linenoise) or 2 (for readline), else it has a value of 0. - - To use libreadline, compile this code's C file with - CLIAPP_ENABLE_READLINE set to a true value. To use linenoise, - build with CLIAPP_ENABLE_LINENOISE set to a true value. - */ - int const enabled; - /** - If non-NULL, cliapp_lineedit_save(NULL) will use this name for - saving. - */ - char const * historyFile; - /** - Specifies whether or not the line editing history has been - modified since the last save. - - This initially has a value of 0 and it gets set to non-0 if - cliapp_lineedit_add() is called. - */ - int needsSave; - } lineread; -}; - -/** - Behold! The One True Instance of CliApp! -*/ -extern struct CliApp cliApp; - -/** - Visits all switches in cliApp.switches, calling - visitor(theSwitch,state) for each one. If the visitor returns - non-0, visitation halts without an error. - - It stops iterating when it encounters an entry for which - cliapp_switch_is_end() returns true. -*/ -void cliapp_switches_visit( CliAppSwitch_visitor_f visitor, - void * state ); - -/** - Callback signature for use with cliapp_args_visit(). - - It gets passed the CLI argument, the index of that argument in - cliApp.argv, and an optional client-specified state pointer. -*/ -typedef int (*CliAppArg_visitor_f)(CliAppArg const *, int ndx, void *); - -/** - Visits all args in cliApp.argv, calling visitor(theSwitch,itsIndex,state) - for each one. If skipArgs is greater than 0, that many are skipped - over before visiting. Behaviour is undefined if a visitor modifies - cliApp.argv or cliApp.argc. If the visitor returns non-0, - visitation halts without an error. - - CliAppArg entries with a NULL key are skipped over, under the assumption - that the client app has marked them as "removed". -*/ -void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state, - unsigned short skipArgs ); - -/** - Initializes the argument-processing parts of the cliApp global - object with. It is intended to be passed the conventional argc/argv - arguments which are passed to the application's main(). - - The final parameter is reserved for future use in providing flags - to change this function's behaviour. A value of 0 is reserved as - meaning "the default behaviour." - - cliApp.switches must have been assigned to non-NULL before calling - this, or behaviour is undefined. If any given switch has a callback - assigned to it, it will be called when that switch is processed, - and processing fails if it returns non-0. (Potential TODO: allow a - NULL switches value to simply treat all flags a known switches.) - - If cliApp.argCallack is not-NULL, it is called for every - argument. It will be passed the CLI argument and, if it's a flag, - its corresponding CliAppSwitch instance (extracted from - cliApp.switches). For non-flag arguments, a NULL CliAppSwitch is - passed to it. If it returns non-0, processing fails. If processes - completes successfully, the callback is called one additional time - with NULL pointer values to indicate that the end has been - reached. This can be used to handle post-argument cleanup, perform - app-specific argument validation, or similar. - - If callbacks are set both on the switch and cliApp, both are called - in that order, but only the cliApp callback is called one final - time after processing is done. - - If this function returns 0, the client may manipulate the contents - of cliApp.argv, within reason, but must be certain to keep - cliApp.argc in sync with that list's entries. - - On error a non-0 code is returned, either propagated from a - callback or (if the error originates from this function) an entry - from the cliapp_rc enum. In the latter case, cliapp_err_get() will - contain information about why it failed. - - Encountering an argument which is neither a non-flag nor a flag - defined in cliApp.switches results in an error. - - Quirks: - - - Arguments after "--" are NOT processed by this - function. Processing them would be a bug-in-waiting because those - flags might collide with app-level flags and/or require syntaxes - which this code treats as an error, e.g. using three dashes instead - of 1 or 2. Instead, if "--" is encounter, cliApp.doubleDash is - populated with information about the flags so the client may deal - with them (which might mean passing them back into this routine!). - - - All argv-related cliApp state is reset on each call, so if this - function is called multiple times, any client-side pointers - referring to cliApp's state may then point to different information - than they expect and/or may become stale pointers. (cliApp-held - data, e.g. cliApp.argv, keeps the same pointers but re-populates - the state, but the lifetime of external pointers, - e.g. cliApp.doubleDash.argv, is client-dependent.) -*/ -int cliapp_process_argv(int argc, char const * const * argv, - unsigned int reserved); - -/** - If cliApp.print is not NULL, this passes on its arguments to that - function, else this is a no-op. -*/ -void cliapp_printv(char const *fmt, va_list); - -/** - Elipses-args form of cliapp_printv(). -*/ -void cliapp_print(char const *fmt, ...); - -/** - Outputs a printf-formatted message to stderr. -*/ -void cliapp_warn(char const *fmt, ...); - -/** - Returns the next entry in cliApp.argv which is a non-flag argument, - skipping over argv[0]. Returns 0 when the end of the list is - reached. -*/ -CliAppArg const * cliapp_arg_nonflag(); - -/** - Resets the traversal of cliapp_arg_nonflag() to start from - the beginning. -*/ -void cliapp_arg_nonflag_rewind(); - -/** - If the given argument matches an app-configured flag, that flag is - returned, else 0 is returned. - - If alsoFlag is true, the first argument and the corresponding - switch must also have matching flag values to be considered a - match. -*/ -CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg, - int alsoFlag); - -/** - Searches for a flag matching one of the given keys. Each entry - in cliApp.argv is checked, in order, against both of the given - keys, in the order they are provided. - - The conventional way to call it is to pass the short-form flag, - then the long-form flag, but that's just a convention. - - Either of the first two arguments may be NULL but both may not be - NULL. - - If the 3rd parameter is not NULL then: - - 1) *atPos indicates an index position to start the search at. (Note - that it should initially be 1, not 0, in order to skip over the - app's name, stored in argv[0].) - - 2) If non-NULL is returned, *atPos is set to the index at which the - argument was found. If NULL is returned, *argPos is not modified. - - Thus atPos can be used to iterate through multiple copies of a - flag, noting that its value points to the index at which the - previous entry was found, so needs to be incremented by 1 before - each subsequent iteration - - On a match, the corresponding CliAppArg is returned, else 0 is - returned. -*/ -CliAppArg const * cliapp_arg_flag(char const * key1, char const * key2, - int * atPos); - -/** - Given a flag value for a CliAppArg or CliAppSwitch, this - returns a prefix string depending on that value: - - 1 = "-", 2 = "--", 3 = "+" - - Anything else = "". The returned bytes are static. -*/ -char const * cliapp_flag_prefix( int flag ); - -/** - Given a CliAppArg, presumably one from cliapp_arg_flag() or - cliapp_arg_nonflag(), this searches for the next argument with the - same key. - - If the given argument is from outside cliApp.argv's memory range, - or is the last element in that list, 0 is returned. - - Bug? For non-flag arguments this does not update the internal - non-flag traversal cursor. -*/ -CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg); - -/** - Clears any error state in the cliApp object. -*/ -void cliapp_err_clear(); - -/** - If cliApp has a current error message set, it is returned, else 0 - is returned. The memory is static and its contents may be modified - by any calls into this API. -*/ -char const * cliapp_err_get(); - - -/** - Tries to save the line-editing history to the given filename, or to - cliApp.lineedit.historyFile if fname is NULL. If both are NULL or - empty, or if cliApp.lineedit.needsSave is 0, this is a no-op and - returns 0. Returns CLIAPP_RC_UNSUPPORTED if line-editing is not - enabled. -*/ -int cliapp_lineedit_save(char const * fname); - -/** - Adds the given line to the line-edit history. If this function - returns 0, it also sets cliApp.lineedit.needsSave to a non-0 value. - - Returns 0 on success or CLIAPP_RC_UNSUPPORTED if line-editing is - not enabled. -*/ -int cliapp_lineedit_add(char const * line); - -/** - Tries to load the line-editing history from the given filename, or - to cliApp.lineedit.historyFile if fname is NULL. If both are NULL - or empty, this is a no-op. Returns CLIAPP_RC_UNSUPPORTED if - line-editing is not enabled. If the underlying line-editing backend - returns an error, CLIAPP_RC_IO is returned, under the assumption - that there was a problem with reading the file (e.g. unreadable), - as opposed to an allocation error or similar. -*/ -int cliapp_lineedit_load(char const * fname); - -/** - If cliApp.lineedit.enabled is true, this function passes its - argument to free(3), else it will (in debug builds) trigger an - assert if passed non-NULL. This must be called once for each line - fetched via cliapp_lineedit_read(). -*/ -void cliapp_lineedit_free(char * line); - -/** - If line-editing is enabled, this reads a single line using that - back-end and returns the new string, which must be passed to - cliapp_lineedit_free() after the caller is done with it. - - Returns 0 if line-editing is not enabled or if the caller taps the - platform's EOF sequence (Ctrl-D on Unix) at the start of the - line. Returns an empty string if the user simply taps ENTER. - - TODO: if no line-editing backend is built in, fall back to fgets() - on stdin. It ain't pretty, but it'll do in a pinch. -*/ -char * cliapp_lineedit_read(char const * prompt); - -/** - Callback type for use with cliapp_repl(). -*/ -typedef int (*CliApp_repl_f)(char const * line, void * state); - -/** - Enters a REPL (Read, Eval, Print Loop). Each iteration does - the following: - - 1) Fetch an input line using cliapp_lineedit_read(), passing it - *prompt. If that returns NULL, this function returns 0. - - 2) If addHistoryPolicy is <0 then the read line is added to the - history. - - 3) Calls callback(theReadLine, state). - - 4) If (3) returns 0 and addHistoryPolicy is >0, the read line - is added to the history. - - 5) Passes the read line to cliapp_lineedit_free(). - - 6) If (3) returns non-0, this function returns that value. - - Notes: - - - The prompt is a pointer to a pointer so that the caller may - modify it between loop iterations. This function derefences *prompt - on each iteration. - - - An addHistoryPolicy of 0 means that this function will not - automatically add input lines to the history. The callback is free - to do so. - - - This function never passes a NULL line value to the callback but - it may pass an empty line. -*/ -int cliapp_repl(CliApp_repl_f callback, char const * const * prompt, - int addHistoryPolicy, void * state); - -#ifdef __cplusplus -} -#endif -#endif /* NET_WH_CLIAPP_H_INCLUDED */ - DELETED bindings/s2/config.h Index: bindings/s2/config.h ================================================================== --- bindings/s2/config.h +++ bindings/s2/config.h @@ -1,5 +0,0 @@ -#define S2_OS_UNIX 1 -#define S2_HAVE_REALPATH 1 -#define S2_HAVE_STAT 1 -#define S2_HAVE_CHDIR 1 -#define CWAL_OBASE_ISA_HASH 1 DELETED bindings/s2/f-s2sh.s2 Index: bindings/s2/f-s2sh.s2 ================================================================== --- bindings/s2/f-s2sh.s2 +++ bindings/s2/f-s2sh.s2 @@ -1,152 +0,0 @@ -/* - Initialization script for s2sh. If it is named the same as the binary - (minus any ".exe" extension), with a ".s2" extension (and in the same directory), - s2sh will autoload this file at startup and fail if processing it fails. -*/ - -assert Fossil; -assert Fossil.Context; - -assert s2 && 'function' === typename s2.loadModule; -/** - An s2.loadModule() proxy which uses a PathFinder - instance to search for DLLs. The name argument must be the - base name part, optionally with a partial leading (sub-)path. - - The dest argument is passed as the last argument to s2.loadModule(), - and is returned to the caller on success. Well-behaved modules will - install their features in that object. - - If the S2_MODULE_PATH and/or S2_MODULE_EXTENSION environment - variables are set, they are treated as a semicolon- or - colon-separated list of directories resp. file extensions. If not - specified, some default set is used. - -*/ -s2.loadModule2 = function(name, dest = {}){ - affirm 'string' === typename name; - const fn = pf.search( name ); - fn || throw "Cannot find '". - concat(name, "' in search path ", pf.prefix.toJSONString()); - //print("Importing",name, '==>', fn); - return loadModule.call(this, realpath ? realpath(fn) : fn, dest); -}.importSymbols({ - loadModule: s2.loadModule, - realpath: s2.io ? s2.io.realpath : undefined, - pf: s2.PathFinder.new( - // Directories... - ('string' === typename (var s = s2.getenv('S2_MODULE_PATH'))) - ? s.split(s.indexOf(';') >= 0 ? ';' : ':') - : ['.'], - // Extensions... - ('string' === typename (s = s2.getenv('S2_MODULE_EXTENSIONS'))) - ? s.split(s.indexOf(';') >= 0 ? ';' : ':') - : ['.so','.dll'] - ) -}); - - -/** - For the given container, v, this displays (via s2.io.output() a - listing of its properties. Intended for getting an overview of an - object's API. -*/ -s2.vls = proc(v,label){ - label && print(label); - typeinfo(iscontainer v) && this.eachProperty.call(v,eachProp); -}.importSymbols({ - eachProp:proc(k,v){ - print('\t'+typeinfo(name k), k, '=', typeinfo(name v), v); - } -}); - -/** - For the given container, v, this displays (via s2.io.output() a - tree listing, recursively, of its properties. Intended for getting - an overview of an object's API. - - If includeProto is truthy, v.prototype is also dumped. If it is - less than 0, that is done recursively for all entries and their - prototypes. -*/ -s2.vtree = proc vtree(v,label,includeProto){ - typeinfo(iscontainer v) || return; - if(!typeinfo(isstring label) && undefined===includeProto){ - includeProto= label; - label = undefined; - } - const doMyProto = includeProto; - includeProto < 0 || (includeProto=0); - //print(__FLC,'argv =',argv); - label ?: (label = "%1$p".applyFormat(v)); - label && out(buf.toString(),label," [type: ",typeinfo(name v),'] ==>\n'); - ++buf.level; - buf.length(buf.level*4); - buf.fill(' '); - var ex = catch{ - // ^^^^ s2 bug: braces around this catch should not be necessary. - // Symptom: if the first if() passes then the 'else' is getting seen - // after catch resolves. - if(typeinfo(isarray v)){ - //out(' array ',v.toJSONString(0,true/*output cycles as strings*/),'\n'); - v.eachIndex(eachIndex); - }else{ - buf.prototype.eachProperty.call(v,eachProp); - } - }; - ex = catch if(!ex && v.prototype){ - doMyProto - ? vtree(v.prototype,label?label+'.prototype':0, includeProto) - : out(buf.toString(),label ? label+'.' : '', - 'prototype ==> [type: ', - typeinfo(name v.prototype),']\n'); - }; - --buf.level; - buf.length(buf.level * 4); - ex && throw ex; -}.importSymbols({ - buf: eval { - var b = new s2.Buffer(20); - b.level = 0; - b; - }, - out: s2.io.output, - eachProp:proc(k,v){ - if(typeinfo(isfunction v)){ - out(buf.toString(), (label ? label+'.'+k :k),'()\n'); - } - else if(typeinfo(iscontainer v) - && typeinfo(isfunction v.mayIterate)){ - v.mayIterate() - ? vtree(v,(label ? label+'.'+k : k), includeProto) - : out(buf.toString(),(label ? label+'.'+k : k), - " = \n".applyFormat(v)); - }else{ - out(buf.toString(), (label ? label+'.'+k :k), - ' = ', typeinfo(name v), ' ', v, '\n'); - } - }, - eachIndex:proc(v,k){ - if(typeinfo(isfunction v)){ - out(buf.toString(), (label ? label+'['+k+']','()\n')); - } - else if(typeinfo(iscontainer v) - && typeinfo(isfunction v.mayIterate)){ - v.mayIterate() - ? vtree(v,(label ? label+'['+k+']' : k), includeProto) - : out(buf.toString(),(label ? label+'['+k+']' : k), - " = \n".applyFormat(v)); - }else{ - out(buf.toString(), (label ? label+'['+k+']' :k), - ' = ', typeinfo(name v), ' ', v, '\n'); - } - } -}); - -/** - Add Fossil.require(), used in loading Fossil-aware modules. -*/ -Fossil.require = import( false, 'require.d/require.s2' ); - -// The rest of the initialization happens via here: -Fossil.require(['nocache!fsl/extendFossil'],proc(){}); DELETED bindings/s2/fslcgi Index: bindings/s2/fslcgi ================================================================== --- bindings/s2/fslcgi +++ bindings/s2/fslcgi @@ -1,63 +0,0 @@ -#!/bin/sh -######################################################################## -# Intended to be run as a CGI script. It is currently hard-coded to -# live in same dir as s2sh and the ./fslcgi.d directory and f-s2sh -# binary. -# -# A sample Apache vhost for this script looks like: -# -# -# ServerName s2.local -# ScriptAlias /cgi-bin/ /path/to/libfossil/s2 -# DocumentRoot /path/to/libfossil/s2 -# Options +FollowSymLinks -# -# -# AllowOverride All -# Options +ExecCGI +Indexes -# Order allow,deny -# Allow from all -# Require all granted -# DirectoryIndex fslcgi -# SetHandler cgi-script -# -######################################################################## - -S2_HOME=`dirname "$0"` -# PROXY_SCRIPT is the script-side entry point for the CGI app -PROXY_SCRIPT=$0.s2 -# Interpreter-level flags for s2sh: -S2_SHELL_FLAGS="-S -a -R" -S2_SHELL=$S2_HOME/f-s2sh -#S2_RC_DIR = "resource dir" for cgi bits. Passed via the --resource-dir CLI flag. -S2_RC_DIR=${S2_HOME}/fslcgi.d - -S2_MODULE_PATH=".:$S2_HOME:${S2_RC_DIR}/lib" -S2_INCLUDES_PATH=".:$S2_HOME:${S2_RC_DIR}/include" -#NYI: S2_AUTOLOAD_MODULES="module1,module2,module3" - -export S2_MODULE_PATH S2_INCLUDES_PATH S2_AUTOLOAD_MODULES - -#export S2_HOME - -# For libfossil.so: -LD_LIBRARY_PATH="${S2_HOME}/..:${LD_LIBRARY_PATH}" -export LD_LIBRARY_PATH - -# Tell the script world which repository file to use... -if [ -d ~/fossil ]; then - S2_CGI_REPO="~/fossil/libfossil.fsl" - true -else - S2_CGI_REPO="$S2_HOME/../../libfossil.fsl" - #S2_CGI_REPO="-R=$S2_HOME/../../fossil.fsl" -fi - -#S2_CGI_REP0='' -# Misc flags intended for consumption by scripts... -S2_REPO_URL=http://fossil.wanderinghorse.net/repos/libfossil/ -#S2_CGI_REPO=-C -S2_SCRIPT_FLAGS="--repo-db=${S2_CGI_REPO} --repo-url=${S2_REPO_URL} --resource-dir=${S2_RC_DIR}" -#S2_SCRIPT_FLAGS="${S2_SCRIPT_FLAGS} --repo-url=http://url-to-your/repo[.cgi]" - -exec $S2_SHELL ${S2_SHELL_FLAGS} -f $PROXY_SCRIPT -- $S2_SCRIPT_FLAGS DELETED bindings/s2/fslcgi.d/init.s2 Index: bindings/s2/fslcgi.d/init.s2 ================================================================== --- bindings/s2/fslcgi.d/init.s2 +++ bindings/s2/fslcgi.d/init.s2 @@ -1,197 +0,0 @@ -/** - Post-bootstrap initialization code for s2's fslcgi. Must live in the - root of "resource directory", but that (with sufficient hacking) may - live outside of the web-root. -*/ -affirm 'undefined' !== typename $CGI /* expecting CGI module to be loaded under this name */; -affirm Fossil.require; - - -$CGI.config = { - resourceDir: Fossil.file.canonicalName(__FILEDIR,true), - cgiRoot: '' -}; -scope { - const C = $CGI; - var uri = s2.getenv('SCRIPT_NAME'); - // Set up cgiRoot (the "standard" way of getting the root dir - // for link-building purposes). This kludgery is not generic... - if(uri) { C.config.cgiRoot = uri + '/' } - else{ - uri = s2.getenv('REQUEST_URI'); - C.request.ENV || (C.request.REQUEST_URI = uri); - if(uri){ - var head = uri.split('fslcgi',2).0;//, head = adj.0; - C.config.cgiRoot = head ||| '/empty'; - //('/' === head) ? head : head + '/'; //.concat('fslcgi', '/'); - } - } - - C.config.localServerMode = - (var serverName = s2.getenv('SERVER_NAME')) - && (0=0, - optStr = this.objToUrlOpt(urlOpt, !hasQ ); - return fmt.applyFormat( - $CGI.config.cgiRoot, - $CGI.urlencode(path)+(optStr ? hasQ ? '&' + optStr : optStr : ''), - label); - }.importSymbols({ - fmt: "%3$s" - }); -}; - - -$CGI.resourcePath = proc(name){ - return this.config.resourceDir + name; -}; - -$CGI.resolveResource = proc(name){ - const path = this.config.resourceDir + name; - return Fossil.file.isFile(path) ? path : undefined; -}; - -// incomplete... not yet sure what i want. -$CGI.request.pathInfoList = scope { - var ps, p = s2.getenv('PATH_INFO'); - if(p && '/'!==p){ - ps = p.split('/'); - // Trim leading/trailing null entries caused - // by leading/trailing slashes: - ps.0 || ps.shift(); - ps[ps.length()-1] || ps.pop(); - } - ps; -}; - -if(0){ // only for testing - $CGI.setContentType('application/json'); - print({request: $CGI.request, - config: $CGI.config}.toJSONString(2)); -}else if(1){ - const c = $CGI; - c.setContentType('text/plain'); - print("$CGI sanity checks..."); - c << 'Fossil.time.cpuTime() says: ' - << Fossil.time.cpuTime() - << '\n' - ; - - const f = c.fossilInstance; - f.db || f.openCheckout(); - c << 'Fossil context: ' << f << ' repo db: ' << f.db.repo.filename << '\n'; - c << "pathInfoList = "<" - << c.resourcePath('foo/bar.baz') - << '\n'; - c << "resolveResource('init.s2')==>" - << c.resolveResource('init.s2') - << '\n'; - c << "resolveResource('x')==>" - << c.resolveResource('x') - << '\n'; - - if(1) { - c << '$CGI.config = ' << $CGI.config.toJSONString(2) << '\n'; - } - - c << "Most recent timeline event: " - << f.db.selectRow('SELECT * FROM event ORDER BY mtime DESC') - .toJSONString(2) - << '\n'; - - if(0){ - // Workaround: c is a Native, and those are rejected - // by the C-level toJSON bits: - var obj = {}; - c.eachProperty(proc(k,v){obj[k]=v}); - c << '$CGI properties:\n' << obj.toJSONString(4) << '\n'; - } - if(1){ - c << 'objToUrlOpt: ' - << c.util.objToUrlOpt({a:3,c:'a b'}) - << '\n'; - c << 'objToUrlOpt again (should be empty): ' - << c.util.objToUrlOpt({},true) - << '\n'; - c << 'absoluteLink: ' - << c.util.absoluteLink('foo/bar/baz?x=y', 'link', {a:1,b:'hi !'}) - << '\n'; - c << 'absoluteLink again: ' - << c.util.absoluteLink('foo/bar/', 'link', {a:1,b:'hi !'}) - << '\n'; - c << 'Fossil.artifactTypes = '< 1; $ob.pop(), --obLevel) {} - if(obLevel){ $ob.clear() } - else { $ob.push() /* so our headers get sent properly */ } - if($CGI.httpStatus() < 500){ - $CGI.httpStatus(500, "Caught Exception"); - } - $CGI.scrubException && (err = $CGI.scrubException(err)); - $CGI.setContentType('application/json'); - print({exception:err}.toJSONString(-1)); -} -$CGI.send() /* will flush all $ob buffers after emiting headers */; DELETED bindings/s2/linenoise/linenoise.c Index: bindings/s2/linenoise/linenoise.c ================================================================== --- bindings/s2/linenoise/linenoise.c +++ bindings/s2/linenoise/linenoise.c @@ -1,1625 +0,0 @@ -/* linenoise.c -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. - * - * You can find the latest source code at: - * - * http://github.com/msteveb/linenoise - * (forked from http://github.com/antirez/linenoise) - * - * Does a number of crazy assumptions that happen to be true in 99.9999% of - * the 2010 UNIX computers around. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis - * Copyright (c) 2011, Steve Bennett - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * ------------------------------------------------------------------------ - * - * References: - * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html - * - * Bloat: - * - Completion? - * - * Unix/termios - * ------------ - * List of escape sequences used by this program, we do everything just - * a few sequences. In order to be so cheap we may have some - * flickering effect with some slow terminal, but the lesser sequences - * the more compatible. - * - * EL (Erase Line) - * Sequence: ESC [ n K - * Effect: if n is 0 or missing, clear from cursor to end of line - * Effect: if n is 1, clear from beginning of line to cursor - * Effect: if n is 2, clear entire line - * - * CUF (CUrsor Forward) - * Sequence: ESC [ n C - * Effect: moves cursor forward of n chars - * - * CR (Carriage Return) - * Sequence: \r - * Effect: moves cursor to column 1 - * - * The following are used to clear the screen: ESC [ H ESC [ 2 J - * This is actually composed of two sequences: - * - * cursorhome - * Sequence: ESC [ H - * Effect: moves the cursor to upper left corner - * - * ED2 (Clear entire screen) - * Sequence: ESC [ 2 J - * Effect: clear the whole screen - * - * == For highlighting control characters, we also use the following two == - * SO (enter StandOut) - * Sequence: ESC [ 7 m - * Effect: Uses some standout mode such as reverse video - * - * SE (Standout End) - * Sequence: ESC [ 0 m - * Effect: Exit standout mode - * - * == Only used if TIOCGWINSZ fails == - * DSR/CPR (Report cursor position) - * Sequence: ESC [ 6 n - * Effect: reports current cursor position as ESC [ NNN ; MMM R - * - * win32/console - * ------------- - * If __MINGW32__ is defined, the win32 console API is used. - * This could probably be made to work for the msvc compiler too. - * This support based in part on work by Jon Griffiths. - */ - -#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */ -#include -#include -#define USE_WINCONSOLE -#ifdef __MINGW32__ -#define HAVE_UNISTD_H -#else -/* Microsoft headers don't like old POSIX names */ -#define strdup _strdup -#define snprintf _snprintf -#endif -#else -#include -#include -#include -#define USE_TERMIOS -#define HAVE_UNISTD_H -#endif - -#ifdef HAVE_UNISTD_H -#include -#endif -#include -#include -#include -#include -#include -#include -#include - -#include "linenoise.h" -#include "utf8.h" - -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 - -#define ctrl(C) ((C) - '@') - -/* Use -ve numbers here to co-exist with normal unicode chars */ -enum { - SPECIAL_NONE, - SPECIAL_UP = -20, - SPECIAL_DOWN = -21, - SPECIAL_LEFT = -22, - SPECIAL_RIGHT = -23, - SPECIAL_DELETE = -24, - SPECIAL_HOME = -25, - SPECIAL_END = -26, - SPECIAL_INSERT = -27, - SPECIAL_PAGE_UP = -28, - SPECIAL_PAGE_DOWN = -29 -}; - -static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; -static int history_len = 0; -static char **history = NULL; - -/* Structure to contain the status of the current (being edited) line */ -struct current { - char *buf; /* Current buffer. Always null terminated */ - int bufmax; /* Size of the buffer, including space for the null termination */ - int len; /* Number of bytes in 'buf' */ - int chars; /* Number of chars in 'buf' (utf-8 chars) */ - int pos; /* Cursor position, measured in chars */ - int cols; /* Size of the window, in chars */ - const char *prompt; - char *capture; /* Allocated capture buffer, or NULL for none. Always null terminated */ -#if defined(USE_TERMIOS) - int fd; /* Terminal fd */ -#elif defined(USE_WINCONSOLE) - HANDLE outh; /* Console output handle */ - HANDLE inh; /* Console input handle */ - int rows; /* Screen rows */ - int x; /* Current column during output */ - int y; /* Current row */ -#endif -}; - -static int fd_read(struct current *current); -static int getWindowSize(struct current *current); - -void linenoiseHistoryFree(void) { - if (history) { - int j; - - for (j = 0; j < history_len; j++) - free(history[j]); - free(history); - history = NULL; - history_len = 0; - } -} - -#if defined(USE_TERMIOS) -static void linenoiseAtExit(void); -static struct termios orig_termios; /* in order to restore at exit */ -static int rawmode = 0; /* for atexit() function to check if restore is needed*/ -static int atexit_registered = 0; /* register atexit just 1 time */ - -static const char *unsupported_term[] = {"dumb","cons25",NULL}; - -static int isUnsupportedTerm(void) { - char *term = getenv("TERM"); - - if (term) { - int j; - for (j = 0; unsupported_term[j]; j++) { - if (strcmp(term, unsupported_term[j]) == 0) { - return 1; - } - } - } - return 0; -} - -static int enableRawMode(struct current *current) { - struct termios raw; - - current->fd = STDIN_FILENO; - current->cols = 0; - - if (!isatty(current->fd) || isUnsupportedTerm() || - tcgetattr(current->fd, &orig_termios) == -1) { -fatal: - errno = ENOTTY; - return -1; - } - - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = 1; - } - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) { - goto fatal; - } - rawmode = 1; - return 0; -} - -static void disableRawMode(struct current *current) { - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1) - rawmode = 0; -} - -/* At exit we'll try to fix the terminal to the initial conditions. */ -static void linenoiseAtExit(void) { - if (rawmode) { - tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios); - } - linenoiseHistoryFree(); -} - -/* gcc/glibc insists that we care about the return code of write! - * Clarification: This means that a void-cast like "(void) (EXPR)" - * does not work. - */ -#define IGNORE_RC(EXPR) if (EXPR) {} - -/* This is fdprintf() on some systems, but use a different - * name to avoid conflicts - */ -static void fd_printf(int fd, const char *format, ...) -{ - va_list args; - char buf[64]; - int n; - - va_start(args, format); - n = vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - IGNORE_RC(write(fd, buf, n)); -} - -static void clearScreen(struct current *current) -{ - fd_printf(current->fd, "\x1b[H\x1b[2J"); -} - -static void cursorToLeft(struct current *current) -{ - fd_printf(current->fd, "\r"); -} - -static int outputChars(struct current *current, const char *buf, int len) -{ - return write(current->fd, buf, len); -} - -static void outputControlChar(struct current *current, char ch) -{ - fd_printf(current->fd, "\x1b[7m^%c\x1b[0m", ch); -} - -static void eraseEol(struct current *current) -{ - fd_printf(current->fd, "\x1b[0K"); -} - -static void setCursorPos(struct current *current, int x) -{ - fd_printf(current->fd, "\r\x1b[%dC", x); -} - -/** - * Reads a char from 'fd', waiting at most 'timeout' milliseconds. - * - * A timeout of -1 means to wait forever. - * - * Returns -1 if no char is received within the time or an error occurs. - */ -static int fd_read_char(int fd, int timeout) -{ - struct pollfd p; - unsigned char c; - - p.fd = fd; - p.events = POLLIN; - - if (poll(&p, 1, timeout) == 0) { - /* timeout */ - return -1; - } - if (read(fd, &c, 1) != 1) { - return -1; - } - return c; -} - -/** - * Reads a complete utf-8 character - * and returns the unicode value, or -1 on error. - */ -static int fd_read(struct current *current) -{ -#ifdef USE_UTF8 - char buf[4]; - int n; - int i; - int c; - - if (read(current->fd, &buf[0], 1) != 1) { - return -1; - } - n = utf8_charlen(buf[0]); - if (n < 1 || n > 3) { - return -1; - } - for (i = 1; i < n; i++) { - if (read(current->fd, &buf[i], 1) != 1) { - return -1; - } - } - buf[n] = 0; - /* decode and return the character */ - utf8_tounicode(buf, &c); - return c; -#else - return fd_read_char(current->fd, -1); -#endif -} - -static int countColorControlChars(const char* prompt) -{ - /* ANSI color control sequences have the form: - * "\x1b" "[" [0-9;]* "m" - * We parse them with a simple state machine. - */ - - enum { - search_esc, - expect_bracket, - expect_trail - } state = search_esc; - int len = 0, found = 0; - char ch; - - /* XXX: Strictly we should be checking utf8 chars rather than - * bytes in case of the extremely unlikely scenario where - * an ANSI sequence is part of a utf8 sequence. - */ - while ((ch = *prompt++) != 0) { - switch (state) { - case search_esc: - if (ch == '\x1b') { - state = expect_bracket; - } - break; - case expect_bracket: - if (ch == '[') { - state = expect_trail; - /* 3 for "\e[ ... m" */ - len = 3; - break; - } - state = search_esc; - break; - case expect_trail: - if ((ch == ';') || ((ch >= '0' && ch <= '9'))) { - /* 0-9, or semicolon */ - len++; - break; - } - if (ch == 'm') { - found += len; - } - state = search_esc; - break; - } - } - - return found; -} - -/** - * Stores the current cursor column in '*cols'. - * Returns 1 if OK, or 0 if failed to determine cursor pos. - */ -static int queryCursor(int fd, int* cols) -{ - /* control sequence - report cursor location */ - fd_printf(fd, "\x1b[6n"); - - /* Parse the response: ESC [ rows ; cols R */ - if (fd_read_char(fd, 100) == 0x1b && - fd_read_char(fd, 100) == '[') { - - int n = 0; - while (1) { - int ch = fd_read_char(fd, 100); - if (ch == ';') { - /* Ignore rows */ - n = 0; - } - else if (ch == 'R') { - /* Got cols */ - if (n != 0 && n < 1000) { - *cols = n; - } - break; - } - else if (ch >= 0 && ch <= '9') { - n = n * 10 + ch - '0'; - } - else { - break; - } - } - return 1; - } - - return 0; -} - -/** - * Updates current->cols with the current window size (width) - */ -static int getWindowSize(struct current *current) -{ - struct winsize ws; - - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) { - current->cols = ws.ws_col; - return 0; - } - - /* Failed to query the window size. Perhaps we are on a serial terminal. - * Try to query the width by sending the cursor as far to the right - * and reading back the cursor position. - * Note that this is only done once per call to linenoise rather than - * every time the line is refreshed for efficiency reasons. - * - * In more detail, we: - * (a) request current cursor position, - * (b) move cursor far right, - * (c) request cursor position again, - * (d) at last move back to the old position. - * This gives us the width without messing with the externally - * visible cursor position. - */ - - if (current->cols == 0) { - int here; - - current->cols = 80; - - /* (a) */ - if (queryCursor (current->fd, &here)) { - /* (b) */ - fd_printf(current->fd, "\x1b[999C"); - - /* (c). Note: If (a) succeeded, then (c) should as well. - * For paranoia we still check and have a fallback action - * for (d) in case of failure.. - */ - if (!queryCursor (current->fd, ¤t->cols)) { - /* (d') Unable to get accurate position data, reset - * the cursor to the far left. While this may not - * restore the exact original position it should not - * be too bad. - */ - fd_printf(current->fd, "\r"); - } else { - /* (d) Reset the cursor back to the original location. */ - if (current->cols > here) { - fd_printf(current->fd, "\x1b[%dD", current->cols - here); - } - } - } /* 1st query failed, doing nothing => default 80 */ - } - - return 0; -} - -/** - * If escape (27) was received, reads subsequent - * chars to determine if this is a known special key. - * - * Returns SPECIAL_NONE if unrecognised, or -1 if EOF. - * - * If no additional char is received within a short time, - * 27 is returned. - */ -static int check_special(int fd) -{ - int c = fd_read_char(fd, 50); - int c2; - - if (c < 0) { - return 27; - } - - c2 = fd_read_char(fd, 50); - if (c2 < 0) { - return c2; - } - if (c == '[' || c == 'O') { - /* Potential arrow key */ - switch (c2) { - case 'A': - return SPECIAL_UP; - case 'B': - return SPECIAL_DOWN; - case 'C': - return SPECIAL_RIGHT; - case 'D': - return SPECIAL_LEFT; - case 'F': - return SPECIAL_END; - case 'H': - return SPECIAL_HOME; - } - } - if (c == '[' && c2 >= '1' && c2 <= '8') { - /* extended escape */ - c = fd_read_char(fd, 50); - if (c == '~') { - switch (c2) { - case '2': - return SPECIAL_INSERT; - case '3': - return SPECIAL_DELETE; - case '5': - return SPECIAL_PAGE_UP; - case '6': - return SPECIAL_PAGE_DOWN; - case '7': - return SPECIAL_HOME; - case '8': - return SPECIAL_END; - } - } - while (c != -1 && c != '~') { - /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */ - c = fd_read_char(fd, 50); - } - } - - return SPECIAL_NONE; -} -#elif defined(USE_WINCONSOLE) - -static DWORD orig_consolemode = 0; - -static int enableRawMode(struct current *current) { - DWORD n; - INPUT_RECORD irec; - - current->outh = GetStdHandle(STD_OUTPUT_HANDLE); - current->inh = GetStdHandle(STD_INPUT_HANDLE); - - if (!PeekConsoleInput(current->inh, &irec, 1, &n)) { - return -1; - } - if (getWindowSize(current) != 0) { - return -1; - } - if (GetConsoleMode(current->inh, &orig_consolemode)) { - SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT); - } - return 0; -} - -static void disableRawMode(struct current *current) -{ - SetConsoleMode(current->inh, orig_consolemode); -} - -static void clearScreen(struct current *current) -{ - COORD topleft = { 0, 0 }; - DWORD n; - - FillConsoleOutputCharacter(current->outh, ' ', - current->cols * current->rows, topleft, &n); - FillConsoleOutputAttribute(current->outh, - FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, - current->cols * current->rows, topleft, &n); - SetConsoleCursorPosition(current->outh, topleft); -} - -static void cursorToLeft(struct current *current) -{ - COORD pos = { 0, (SHORT)current->y }; - DWORD n; - - FillConsoleOutputAttribute(current->outh, - FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n); - current->x = 0; -} - -static int outputChars(struct current *current, const char *buf, int len) -{ - COORD pos = { (SHORT)current->x, (SHORT)current->y }; - DWORD n; - - WriteConsoleOutputCharacter(current->outh, buf, len, pos, &n); - current->x += len; - return 0; -} - -static void outputControlChar(struct current *current, char ch) -{ - COORD pos = { (SHORT)current->x, (SHORT)current->y }; - DWORD n; - - FillConsoleOutputAttribute(current->outh, BACKGROUND_INTENSITY, 2, pos, &n); - outputChars(current, "^", 1); - outputChars(current, &ch, 1); -} - -static void eraseEol(struct current *current) -{ - COORD pos = { (SHORT)current->x, (SHORT)current->y }; - DWORD n; - - FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n); -} - -static void setCursorPos(struct current *current, int x) -{ - COORD pos = { (SHORT)x, (SHORT)current->y }; - - SetConsoleCursorPosition(current->outh, pos); - current->x = x; -} - -static int fd_read(struct current *current) -{ - while (1) { - INPUT_RECORD irec; - DWORD n; - if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) { - break; - } - if (!ReadConsoleInput (current->inh, &irec, 1, &n)) { - break; - } - if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) { - KEY_EVENT_RECORD *k = &irec.Event.KeyEvent; - if (k->dwControlKeyState & ENHANCED_KEY) { - switch (k->wVirtualKeyCode) { - case VK_LEFT: - return SPECIAL_LEFT; - case VK_RIGHT: - return SPECIAL_RIGHT; - case VK_UP: - return SPECIAL_UP; - case VK_DOWN: - return SPECIAL_DOWN; - case VK_INSERT: - return SPECIAL_INSERT; - case VK_DELETE: - return SPECIAL_DELETE; - case VK_HOME: - return SPECIAL_HOME; - case VK_END: - return SPECIAL_END; - case VK_PRIOR: - return SPECIAL_PAGE_UP; - case VK_NEXT: - return SPECIAL_PAGE_DOWN; - } - } - /* Note that control characters are already translated in AsciiChar */ - else if (k->wVirtualKeyCode == VK_CONTROL) - continue; - else { -#ifdef USE_UTF8 - return k->uChar.UnicodeChar; -#else - return k->uChar.AsciiChar; -#endif - } - } - } - return -1; -} - -static int countColorControlChars(const char* prompt) -{ - /* For windows we assume that there are no embedded ansi color - * control sequences. - */ - return 0; -} - -static int getWindowSize(struct current *current) -{ - CONSOLE_SCREEN_BUFFER_INFO info; - if (!GetConsoleScreenBufferInfo(current->outh, &info)) { - return -1; - } - current->cols = info.dwSize.X; - current->rows = info.dwSize.Y; - if (current->cols <= 0 || current->rows <= 0) { - current->cols = 80; - return -1; - } - current->y = info.dwCursorPosition.Y; - current->x = info.dwCursorPosition.X; - return 0; -} -#endif - -static int utf8_getchars(char *buf, int c) -{ -#ifdef USE_UTF8 - return utf8_fromunicode(buf, c); -#else - *buf = c; - return 1; -#endif -} - -/** - * Returns the unicode character at the given offset, - * or -1 if none. - */ -static int get_char(struct current *current, int pos) -{ - if (pos >= 0 && pos < current->chars) { - int c; - int i = utf8_index(current->buf, pos); - (void)utf8_tounicode(current->buf + i, &c); - return c; - } - return -1; -} - -static void refreshLine(const char *prompt, struct current *current) -{ - int plen; - int pchars; - int backup = 0; - int i; - const char *buf = current->buf; - int chars = current->chars; - int pos = current->pos; - int b; - int ch; - int n; - - /* Should intercept SIGWINCH. For now, just get the size every time */ - getWindowSize(current); - - plen = strlen(prompt); - pchars = utf8_strlen(prompt, plen); - - /* Scan the prompt for embedded ansi color control sequences and - * discount them as characters/columns. - */ - pchars -= countColorControlChars(prompt); - - /* Account for a line which is too long to fit in the window. - * Note that control chars require an extra column - */ - - /* How many cols are required to the left of 'pos'? - * The prompt, plus one extra for each control char - */ - n = pchars + utf8_strlen(buf, current->len); - b = 0; - for (i = 0; i < pos; i++) { - b += utf8_tounicode(buf + b, &ch); - if (ch < ' ') { - n++; - } - } - - /* If too many are needed, strip chars off the front of 'buf' - * until it fits. Note that if the current char is a control character, - * we need one extra col. - */ - if (current->pos < current->chars && get_char(current, current->pos) < ' ') { - n++; - } - - while (n >= current->cols && pos > 0) { - b = utf8_tounicode(buf, &ch); - if (ch < ' ') { - n--; - } - n--; - buf += b; - pos--; - chars--; - } - - /* Cursor to left edge, then the prompt */ - cursorToLeft(current); - outputChars(current, prompt, plen); - - /* Now the current buffer content */ - - /* Need special handling for control characters. - * If we hit 'cols', stop. - */ - b = 0; /* unwritted bytes */ - n = 0; /* How many control chars were written */ - for (i = 0; i < chars; i++) { - int ch; - int w = utf8_tounicode(buf + b, &ch); - if (ch < ' ') { - n++; - } - if (pchars + i + n >= current->cols) { - break; - } - if (ch < ' ') { - /* A control character, so write the buffer so far */ - outputChars(current, buf, b); - buf += b + w; - b = 0; - outputControlChar(current, ch + '@'); - if (i < pos) { - backup++; - } - } - else { - b += w; - } - } - outputChars(current, buf, b); - - /* Erase to right, move cursor to original position */ - eraseEol(current); - setCursorPos(current, pos + pchars + backup); -} - -static void set_current(struct current *current, const char *str) -{ - strncpy(current->buf, str, current->bufmax); - current->buf[current->bufmax - 1] = 0; - current->len = strlen(current->buf); - current->pos = current->chars = utf8_strlen(current->buf, current->len); -} - -static int has_room(struct current *current, int bytes) -{ - return current->len + bytes < current->bufmax - 1; -} - -/** - * Removes the char at 'pos'. - * - * Returns 1 if the line needs to be refreshed, 2 if not - * and 0 if nothing was removed - */ -static int remove_char(struct current *current, int pos) -{ - if (pos >= 0 && pos < current->chars) { - int p1, p2; - int ret = 1; - p1 = utf8_index(current->buf, pos); - p2 = p1 + utf8_index(current->buf + p1, 1); - -#ifdef USE_TERMIOS - /* optimise remove char in the case of removing the last char */ - if (current->pos == pos + 1 && current->pos == current->chars) { - if (current->buf[pos] >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) { - ret = 2; - fd_printf(current->fd, "\b \b"); - } - } -#endif - - /* Move the null char too */ - memmove(current->buf + p1, current->buf + p2, current->len - p2 + 1); - current->len -= (p2 - p1); - current->chars--; - - if (current->pos > pos) { - current->pos--; - } - return ret; - } - return 0; -} - -/** - * Insert 'ch' at position 'pos' - * - * Returns 1 if the line needs to be refreshed, 2 if not - * and 0 if nothing was inserted (no room) - */ -static int insert_char(struct current *current, int pos, int ch) -{ - char buf[3]; - int n = utf8_getchars(buf, ch); - - if (has_room(current, n) && pos >= 0 && pos <= current->chars) { - int p1, p2; - int ret = 1; - p1 = utf8_index(current->buf, pos); - p2 = p1 + n; - -#ifdef USE_TERMIOS - /* optimise the case where adding a single char to the end and no scrolling is needed */ - if (current->pos == pos && current->chars == pos) { - if (ch >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) { - IGNORE_RC(write(current->fd, buf, n)); - ret = 2; - } - } -#endif - - memmove(current->buf + p2, current->buf + p1, current->len - p1); - memcpy(current->buf + p1, buf, n); - current->len += n; - - current->chars++; - if (current->pos >= pos) { - current->pos++; - } - return ret; - } - return 0; -} - -/** - * Captures up to 'n' characters starting at 'pos' for the cut buffer. - * - * This replaces any existing characters in the cut buffer. - */ -static void capture_chars(struct current *current, int pos, int n) -{ - if (pos >= 0 && (pos + n - 1) < current->chars) { - int p1 = utf8_index(current->buf, pos); - int nbytes = utf8_index(current->buf + p1, n); - - if (nbytes) { - free(current->capture); - /* Include space for the null terminator */ - current->capture = (char *)malloc(nbytes + 1); - memcpy(current->capture, current->buf + p1, nbytes); - current->capture[nbytes] = '\0'; - } - } -} - -/** - * Removes up to 'n' characters at cursor position 'pos'. - * - * Returns 0 if no chars were removed or non-zero otherwise. - */ -static int remove_chars(struct current *current, int pos, int n) -{ - int removed = 0; - - /* First save any chars which will be removed */ - capture_chars(current, pos, n); - - while (n-- && remove_char(current, pos)) { - removed++; - } - return removed; -} -/** - * Inserts the characters (string) 'chars' at the cursor position 'pos'. - * - * Returns 0 if no chars were inserted or non-zero otherwise. - */ -static int insert_chars(struct current *current, int pos, const char *chars) -{ - int inserted = 0; - - while (*chars) { - int ch; - int n = utf8_tounicode(chars, &ch); - if (insert_char(current, pos, ch) == 0) { - break; - } - inserted++; - pos++; - chars += n; - } - return inserted; -} - -#ifndef NO_COMPLETION -static linenoiseCompletionCallback *completionCallback = NULL; - -static void beep() { -#ifdef USE_TERMIOS - fprintf(stderr, "\x7"); - fflush(stderr); -#endif -} - -static void freeCompletions(linenoiseCompletions *lc) { - size_t i; - for (i = 0; i < lc->len; i++) - free(lc->cvec[i]); - free(lc->cvec); -} - -static int completeLine(struct current *current) { - linenoiseCompletions lc = { 0, NULL }; - int c = 0; - - completionCallback(current->buf,&lc); - if (lc.len == 0) { - beep(); - } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct current tmp = *current; - tmp.buf = lc.cvec[i]; - tmp.pos = tmp.len = strlen(tmp.buf); - tmp.chars = utf8_strlen(tmp.buf, tmp.len); - refreshLine(current->prompt, &tmp); - } else { - refreshLine(current->prompt, current); - } - - c = fd_read(current); - if (c == -1) { - break; - } - - switch(c) { - case '\t': /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) beep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) { - refreshLine(current->prompt, current); - } - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - set_current(current,lc.cvec[i]); - } - stop = 1; - break; - } - } - } - - freeCompletions(&lc); - return c; /* Return last read character */ -} - -/* Register a callback function to be called for tab-completion. - Returns the prior callback so that the caller may (if needed) - restore it when done. */ -linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { - linenoiseCompletionCallback * old = completionCallback; - completionCallback = fn; - return old; -} - -void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { - lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); - lc->cvec[lc->len++] = strdup(str); -} - -#endif - -static int linenoiseEdit(struct current *current) { - int history_index = 0; - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - linenoiseHistoryAdd(""); - - set_current(current, ""); - refreshLine(current->prompt, current); - - while(1) { - int dir = -1; - int c = fd_read(current); - -#ifndef NO_COMPLETION - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == '\t' && current->pos == current->chars && completionCallback != NULL) { - c = completeLine(current); - /* Return on errors */ - if (c < 0) return current->len; - /* Read next character when 0 */ - if (c == 0) continue; - } -#endif - -process_char: - if (c == -1) return current->len; -#ifdef USE_TERMIOS - if (c == 27) { /* escape sequence */ - c = check_special(current->fd); - } -#endif - switch(c) { - case '\r': /* enter */ - history_len--; - free(history[history_len]); - return current->len; - case ctrl('C'): /* ctrl-c */ - errno = EAGAIN; - return -1; - case 127: /* backspace */ - case ctrl('H'): - if (remove_char(current, current->pos - 1) == 1) { - refreshLine(current->prompt, current); - } - break; - case ctrl('D'): /* ctrl-d */ - if (current->len == 0) { - /* Empty line, so EOF */ - history_len--; - free(history[history_len]); - return -1; - } - /* Otherwise fall through to delete char to right of cursor */ - case SPECIAL_DELETE: - if (remove_char(current, current->pos) == 1) { - refreshLine(current->prompt, current); - } - break; - case SPECIAL_INSERT: - /* Ignore. Expansion Hook. - * Future possibility: Toggle Insert/Overwrite Modes - */ - break; - case ctrl('W'): /* ctrl-w, delete word at left. save deleted chars */ - /* eat any spaces on the left */ - { - int pos = current->pos; - while (pos > 0 && get_char(current, pos - 1) == ' ') { - pos--; - } - - /* now eat any non-spaces on the left */ - while (pos > 0 && get_char(current, pos - 1) != ' ') { - pos--; - } - - if (remove_chars(current, pos, current->pos - pos)) { - refreshLine(current->prompt, current); - } - } - break; - case ctrl('R'): /* ctrl-r */ - { - /* Display the reverse-i-search prompt and process chars */ - char rbuf[50]; - char rprompt[80]; - int rchars = 0; - int rlen = 0; - int searchpos = history_len - 1; - - rbuf[0] = 0; - while (1) { - int n = 0; - const char *p = NULL; - int skipsame = 0; - int searchdir = -1; - - snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf); - refreshLine(rprompt, current); - c = fd_read(current); - if (c == ctrl('H') || c == 127) { - if (rchars) { - int p = utf8_index(rbuf, --rchars); - rbuf[p] = 0; - rlen = strlen(rbuf); - } - continue; - } -#ifdef USE_TERMIOS - if (c == 27) { - c = check_special(current->fd); - } -#endif - if (c == ctrl('P') || c == SPECIAL_UP) { - /* Search for the previous (earlier) match */ - if (searchpos > 0) { - searchpos--; - } - skipsame = 1; - } - else if (c == ctrl('N') || c == SPECIAL_DOWN) { - /* Search for the next (later) match */ - if (searchpos < history_len) { - searchpos++; - } - searchdir = 1; - skipsame = 1; - } - else if (c >= ' ') { - if (rlen >= (int)sizeof(rbuf) + 3) { - continue; - } - - n = utf8_getchars(rbuf + rlen, c); - rlen += n; - rchars++; - rbuf[rlen] = 0; - - /* Adding a new char resets the search location */ - searchpos = history_len - 1; - } - else { - /* Exit from incremental search mode */ - break; - } - - /* Now search through the history for a match */ - for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) { - p = strstr(history[searchpos], rbuf); - if (p) { - /* Found a match */ - if (skipsame && strcmp(history[searchpos], current->buf) == 0) { - /* But it is identical, so skip it */ - continue; - } - /* Copy the matching line and set the cursor position */ - set_current(current,history[searchpos]); - current->pos = utf8_strlen(history[searchpos], p - history[searchpos]); - break; - } - } - if (!p && n) { - /* No match, so don't add it */ - rchars--; - rlen -= n; - rbuf[rlen] = 0; - } - } - if (c == ctrl('G') || c == ctrl('C')) { - /* ctrl-g terminates the search with no effect */ - set_current(current, ""); - c = 0; - } - else if (c == ctrl('J')) { - /* ctrl-j terminates the search leaving the buffer in place */ - c = 0; - } - /* Go process the char normally */ - refreshLine(current->prompt, current); - goto process_char; - } - break; - case ctrl('T'): /* ctrl-t */ - if (current->pos > 0 && current->pos <= current->chars) { - /* If cursor is at end, transpose the previous two chars */ - int fixer = (current->pos == current->chars); - c = get_char(current, current->pos - fixer); - remove_char(current, current->pos - fixer); - insert_char(current, current->pos - 1, c); - refreshLine(current->prompt, current); - } - break; - case ctrl('V'): /* ctrl-v */ - if (has_room(current, 3)) { - /* Insert the ^V first */ - if (insert_char(current, current->pos, c)) { - refreshLine(current->prompt, current); - /* Now wait for the next char. Can insert anything except \0 */ - c = fd_read(current); - - /* Remove the ^V first */ - remove_char(current, current->pos - 1); - if (c != -1) { - /* Insert the actual char */ - insert_char(current, current->pos, c); - } - refreshLine(current->prompt, current); - } - } - break; - case ctrl('B'): - case SPECIAL_LEFT: - if (current->pos > 0) { - current->pos--; - refreshLine(current->prompt, current); - } - break; - case ctrl('F'): - case SPECIAL_RIGHT: - if (current->pos < current->chars) { - current->pos++; - refreshLine(current->prompt, current); - } - break; - case SPECIAL_PAGE_UP: - dir = history_len - history_index - 1; /* move to start of history */ - goto history_navigation; - case SPECIAL_PAGE_DOWN: - dir = -history_index; /* move to 0 == end of history, i.e. current */ - goto history_navigation; - case ctrl('P'): - case SPECIAL_UP: - dir = 1; - goto history_navigation; - case ctrl('N'): - case SPECIAL_DOWN: -history_navigation: - if (history_len > 1) { - /* Update the current history entry before to - * overwrite it with tne next one. */ - free(history[history_len - 1 - history_index]); - history[history_len - 1 - history_index] = strdup(current->buf); - /* Show the new entry */ - history_index += dir; - if (history_index < 0) { - history_index = 0; - break; - } else if (history_index >= history_len) { - history_index = history_len - 1; - break; - } - set_current(current, history[history_len - 1 - history_index]); - refreshLine(current->prompt, current); - } - break; - case ctrl('A'): /* Ctrl+a, go to the start of the line */ - case SPECIAL_HOME: - current->pos = 0; - refreshLine(current->prompt, current); - break; - case ctrl('E'): /* ctrl+e, go to the end of the line */ - case SPECIAL_END: - current->pos = current->chars; - refreshLine(current->prompt, current); - break; - case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */ - if (remove_chars(current, 0, current->pos)) { - refreshLine(current->prompt, current); - } - break; - case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */ - if (remove_chars(current, current->pos, current->chars - current->pos)) { - refreshLine(current->prompt, current); - } - break; - case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */ - if (current->capture && insert_chars(current, current->pos, current->capture)) { - refreshLine(current->prompt, current); - } - break; - case ctrl('L'): /* Ctrl+L, clear screen */ - clearScreen(current); - /* Force recalc of window size for serial terminals */ - current->cols = 0; - refreshLine(current->prompt, current); - break; - default: - /* Only tab is allowed without ^V */ - if (c == '\t' || c >= ' ') { - if (insert_char(current, current->pos, c) == 1) { - refreshLine(current->prompt, current); - } - } - break; - } - } - return current->len; -} - -int linenoiseColumns(void) -{ - struct current current; - enableRawMode (¤t); - getWindowSize (¤t); - disableRawMode (¤t); - return current.cols; -} - -char *linenoise(const char *prompt) -{ - int count; - struct current current; - char buf[LINENOISE_MAX_LINE]; - - if (enableRawMode(¤t) == -1) { - printf("%s", prompt); - fflush(stdout); - if (fgets(buf, sizeof(buf), stdin) == NULL) { - return NULL; - } - count = strlen(buf); - if (count && buf[count-1] == '\n') { - count--; - buf[count] = '\0'; - } - } - else - { - current.buf = buf; - current.bufmax = sizeof(buf); - current.len = 0; - current.chars = 0; - current.pos = 0; - current.prompt = prompt; - current.capture = NULL; - - count = linenoiseEdit(¤t); - - disableRawMode(¤t); - printf("\n"); - - free(current.capture); - if (count == -1) { - return NULL; - } - } - return strdup(buf); -} - -/* Using a circular buffer is smarter, but a bit more complex to handle. */ -int linenoiseHistoryAdd(const char *line) { - char *linecopy; - - if (history_max_len == 0) return 0; - if (history == NULL) { - history = (char **)malloc(sizeof(char*)*history_max_len); - if (history == NULL) return 0; - memset(history,0,(sizeof(char*)*history_max_len)); - } - - /* do not insert duplicate lines into history */ - if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) { - return 0; - } - - linecopy = strdup(line); - if (!linecopy) return 0; - if (history_len == history_max_len) { - free(history[0]); - memmove(history,history+1,sizeof(char*)*(history_max_len-1)); - history_len--; - } - history[history_len] = linecopy; - history_len++; - return 1; -} - -int linenoiseHistoryGetMaxLen(void) { - return history_max_len; -} - -int linenoiseHistorySetMaxLen(int len) { - char **newHistory; - - if (len < 1) return 0; - if (history) { - int tocopy = history_len; - - newHistory = (char **)malloc(sizeof(char*)*len); - if (newHistory == NULL) return 0; - - /* If we can't copy everything, free the elements we'll not use. */ - if (len < tocopy) { - int j; - - for (j = 0; j < tocopy-len; j++) free(history[j]); - tocopy = len; - } - memset(newHistory,0,sizeof(char*)*len); - memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy); - free(history); - history = newHistory; - } - history_max_len = len; - if (history_len > history_max_len) - history_len = history_max_len; - return 1; -} - -/* Save the history in the specified file. On success 0 is returned - * otherwise -1 is returned. */ -int linenoiseHistorySave(const char *filename) { - FILE *fp = fopen(filename,"w"); - int j; - - if (fp == NULL) return -1; - for (j = 0; j < history_len; j++) { - const char *str = history[j]; - /* Need to encode backslash, nl and cr */ - while (*str) { - if (*str == '\\') { - fputs("\\\\", fp); - } - else if (*str == '\n') { - fputs("\\n", fp); - } - else if (*str == '\r') { - fputs("\\r", fp); - } - else { - fputc(*str, fp); - } - str++; - } - fputc('\n', fp); - } - - fclose(fp); - return 0; -} - -/* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. - * - * If the file exists and the operation succeeded 0 is returned, otherwise - * on error -1 is returned. */ -int linenoiseHistoryLoad(const char *filename) { - FILE *fp = fopen(filename,"r"); - char buf[LINENOISE_MAX_LINE]; - - if (fp == NULL) return -1; - - while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { - char *src, *dest; - - /* Decode backslash escaped values */ - for (src = dest = buf; *src; src++) { - char ch = *src; - - if (ch == '\\') { - src++; - if (*src == 'n') { - ch = '\n'; - } - else if (*src == 'r') { - ch = '\r'; - } else { - ch = *src; - } - } - *dest++ = ch; - } - /* Remove trailing newline */ - if (dest != buf && (dest[-1] == '\n' || dest[-1] == '\r')) { - dest--; - } - *dest = 0; - - linenoiseHistoryAdd(buf); - } - fclose(fp); - return 0; -} - -/* Provide access to the history buffer. - * - * If 'len' is not NULL, the length is stored in *len. - */ -char **linenoiseHistory(int *len) { - if (len) { - *len = history_len; - } - return history; -} DELETED bindings/s2/linenoise/linenoise.h Index: bindings/s2/linenoise/linenoise.h ================================================================== --- bindings/s2/linenoise/linenoise.h +++ bindings/s2/linenoise/linenoise.h @@ -1,120 +0,0 @@ -/* linenoise.h -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. - * - * See linenoise.c for more information. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __LINENOISE_H -#define __LINENOISE_H - -#ifndef NO_COMPLETION -typedef struct linenoiseCompletions { - size_t len; - char **cvec; -} linenoiseCompletions; - -/* - * The callback type for tab completion handlers. - */ -typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); - -/* - * Sets the current tab completion handler and returns the previous one, or NULL - * if no prior one has been set. - */ -linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *); - -/* - * Adds a copy of the given string to the given completion list. The copy is owned - * by the linenoiseCompletions object. - */ -void linenoiseAddCompletion(linenoiseCompletions *, const char *); -#endif - -/* - * Prompts for input using the given string as the input - * prompt. Returns when the user has tapped ENTER or (on an empty - * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either - * a copy of the entered string (for ENTER) or NULL (on EOF). The - * caller owns the returned string and must eventually free() it. - */ -char *linenoise(const char *prompt); - -/* - * Adds a copy of the given line of the command history. - */ -int linenoiseHistoryAdd(const char *line); - -/* - * Sets the maximum length of the command history, in lines. - * If the history is currently longer, it will be trimmed, - * retaining only the most recent entries. If len is 0 or less - * then this function does nothing. - */ -int linenoiseHistorySetMaxLen(int len); - -/* - * Returns the current maximum length of the history, in lines. - */ -int linenoiseHistoryGetMaxLen(void); - -/* - * Saves the current contents of the history to the given file. - * Returns 0 on success. - */ -int linenoiseHistorySave(const char *filename); - -/* - * Replaces the current history with the contents - * of the given file. Returns 0 on success. - */ -int linenoiseHistoryLoad(const char *filename); - -/* - * Frees all history entries, clearing the history. - */ -void linenoiseHistoryFree(void); - -/* - * Returns a pointer to the list of history entries, writing its - * length to *len if len is not NULL. The memory is owned by linenoise - * and must not be freed. - */ -char **linenoiseHistory(int *len); - -/* - * Returns the number of display columns in the current terminal. - */ -int linenoiseColumns(void); - -#endif /* __LINENOISE_H */ DELETED bindings/s2/linenoise/utf8.c Index: bindings/s2/linenoise/utf8.c ================================================================== --- bindings/s2/linenoise/utf8.c +++ bindings/s2/linenoise/utf8.c @@ -1,115 +0,0 @@ -/** - * UTF-8 utility functions - * - * (c) 2010 Steve Bennett - * - * See LICENCE for licence details. - */ - -#include -#include -#include -#include -#include "utf8.h" - -#ifdef USE_UTF8 -int utf8_fromunicode(char *p, unsigned short uc) -{ - if (uc <= 0x7f) { - *p = uc; - return 1; - } - else if (uc <= 0x7ff) { - *p++ = 0xc0 | ((uc & 0x7c0) >> 6); - *p = 0x80 | (uc & 0x3f); - return 2; - } - else { - *p++ = 0xe0 | ((uc & 0xf000) >> 12); - *p++ = 0x80 | ((uc & 0xfc0) >> 6); - *p = 0x80 | (uc & 0x3f); - return 3; - } -} - -int utf8_charlen(int c) -{ - if ((c & 0x80) == 0) { - return 1; - } - if ((c & 0xe0) == 0xc0) { - return 2; - } - if ((c & 0xf0) == 0xe0) { - return 3; - } - if ((c & 0xf8) == 0xf0) { - return 4; - } - /* Invalid sequence */ - return -1; -} - -int utf8_strlen(const char *str, int bytelen) -{ - int charlen = 0; - if (bytelen < 0) { - bytelen = strlen(str); - } - while (bytelen) { - int c; - int l = utf8_tounicode(str, &c); - charlen++; - str += l; - bytelen -= l; - } - return charlen; -} - -int utf8_index(const char *str, int index) -{ - const char *s = str; - while (index--) { - int c; - s += utf8_tounicode(s, &c); - } - return s - str; -} - -int utf8_charequal(const char *s1, const char *s2) -{ - int c1, c2; - - utf8_tounicode(s1, &c1); - utf8_tounicode(s2, &c2); - - return c1 == c2; -} - -int utf8_tounicode(const char *str, int *uc) -{ - unsigned const char *s = (unsigned const char *)str; - - if (s[0] < 0xc0) { - *uc = s[0]; - return 1; - } - if (s[0] < 0xe0) { - if ((s[1] & 0xc0) == 0x80) { - *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80); - return 2; - } - } - else if (s[0] < 0xf0) { - if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) { - *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80); - return 3; - } - } - - /* Invalid sequence, so just return the byte */ - *uc = *s; - return 1; -} - -#endif DELETED bindings/s2/linenoise/utf8.h Index: bindings/s2/linenoise/utf8.h ================================================================== --- bindings/s2/linenoise/utf8.h +++ bindings/s2/linenoise/utf8.h @@ -1,79 +0,0 @@ -#ifndef UTF8_UTIL_H -#define UTF8_UTIL_H -/** - * UTF-8 utility functions - * - * (c) 2010 Steve Bennett - * - * See LICENCE for licence details. - */ - -#ifndef USE_UTF8 -#include - -/* No utf-8 support. 1 byte = 1 char */ -#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B)) -#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) -#define utf8_index(C, I) (I) -#define utf8_charlen(C) 1 - -#else -/** - * Converts the given unicode codepoint (0 - 0xffff) to utf-8 - * and stores the result at 'p'. - * - * Returns the number of utf-8 characters (1-3). - */ -int utf8_fromunicode(char *p, unsigned short uc); - -/** - * Returns the length of the utf-8 sequence starting with 'c'. - * - * Returns 1-4, or -1 if this is not a valid start byte. - * - * Note that charlen=4 is not supported by the rest of the API. - */ -int utf8_charlen(int c); - -/** - * Returns the number of characters in the utf-8 - * string of the given byte length. - * - * Any bytes which are not part of an valid utf-8 - * sequence are treated as individual characters. - * - * The string *must* be null terminated. - * - * Does not support unicode code points > \uffff - */ -int utf8_strlen(const char *str, int bytelen); - -/** - * Returns the byte index of the given character in the utf-8 string. - * - * The string *must* be null terminated. - * - * This will return the byte length of a utf-8 string - * if given the char length. - */ -int utf8_index(const char *str, int charindex); - -/** - * Returns the unicode codepoint corresponding to the - * utf-8 sequence 'str'. - * - * Stores the result in *uc and returns the number of bytes - * consumed. - * - * If 'str' is null terminated, then an invalid utf-8 sequence - * at the end of the string will be returned as individual bytes. - * - * If it is not null terminated, the length *must* be checked first. - * - * Does not support unicode code points > \uffff - */ -int utf8_tounicode(const char *str, int *uc); - -#endif - -#endif DELETED bindings/s2/r-tester.sh Index: bindings/s2/r-tester.sh ================================================================== --- bindings/s2/r-tester.sh +++ bindings/s2/r-tester.sh @@ -1,180 +0,0 @@ -#!/bin/bash -#HELP>##################################################################### -# Runs require.s2 unit test scripts. -# -# Usages: -# -# 1) $0 [flags] -# searches for *.test.s2 in $(dirname $0)/require.d and runs them. -# -# 2) $0 [flags] moduleName ...moduleNameN -# Expects that each argument is a require.s2 module name and -# expects moduleName.test.s2 to exist in the require.s2 module -# search path. -# -# Runs each test in its own s2sh instance and exits with non-0 if -# any test fails. -# -# Flags: -# -# -V enables VERBOSE mode, which simply outputs some extra output. -# -# -vg enables valgrind/massif tests IF valgrind is found in the path. -# These generate some files which one may peruse to collect allocation -# metrics. -# -# Any flags not handled by this script are passed on to the underlying -# s2sh call (quoting might be a problem - avoid complex flags). -# -#HELP<################################################################### - -dir=$(dirname $0) -#[[ '.' = "${dir}" ]] && dir=$PWD -s2sh=$dir/f-s2sh - -[[ -x "$s2sh" ]] || s2sh=$(which s2sh 2>/dev/null) -[[ -x "$s2sh" ]] || { - echo "s2sh not found :(. Looked in [${dir}] and \$PATH." 1>&2 - exit 1 -} -echo "s2sh = ${s2sh}" -rdir=${REQUIRES2_HOME} -[[ -d "$rdir" ]] || rdir=$dir/require.d -[[ -d "$rdir" ]] || { - echo "Missing 'required' dir: $rdir" 1>&2 - exit 2 -} -rdir=$(cd $rdir>/dev/null; echo $PWD) -export S2_HOME=$PWD # used by require.s2 - -requireCall="(s2.require ||| import('${rdir}/require.s2'))" - -DO_VG=0 -VERBOSE=0 -S2SH_ADD_FLAGS="${S2SH_XFLAGS}" -DO_DEBUG=0 - -list='' -for i in "$@"; do - case $i in - -V) VERBOSE=1 - ;; - -vg) DO_VG=1 - ;; - -debug) - DO_DEBUG=1 - ;; - -\?|--help) - echo "$0:" - sed -n '/^#HELP/,/^#HELP/p' < $0 | grep -v -e '^#HELP' \ - | sed -e 's/^#/ /g' - exit 0; - ;; - -*) - S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS} $i" - ;; - *) list="$list $i.test" - ;; - esac -done - -[[ x = "x${list}" ]] && { - cd $rdir || exit $? # oh, come on, steve, this isn't C! - list=$(find . -name '*.test.s2' | cut -c3- | sed -e 's/\.s2$//g' | sort) - cd - >/dev/null -} - -[[ "x" = "x${list}" ]] && { - echo "Didn't find any *.test.s2 scripts :(" 1>&2 - exit 3 -} -list=$(echo $list) # remove newlines -#echo "Unit test list: $list" - -function verbose(){ - [[ x1 = "x${VERBOSE}" ]] && echo $@ -} - -function vgTest(){ - local test=$1 - shift - local flags="$@" - local tmp=tmp.$test - local vgout=vg.$test - cat < $tmp -${requireCall}(['nocache!${test}']); -EOF - local cmd="$vg --leak-check=full -v --show-reachable=yes --track-origins=yes $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}" - echo "Valgrind: $test" - verbose -e "\t$cmd" - $cmd &> $vgout || { - rc=$? - rm -f $tmp - echo "Valgrind failed. Output is in ${vgout}" - exit $rc - } - #rm -f $vgout - echo "Valground: $test [==>$vgout]" - vgout=massif.$test - local msp=ms_print.$test - cmd="$massif --massif-out-file=$msp $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}" - echo "Massifying: $test" - verbose -e "\t$cmd" - $cmd &> $vgout || { - rc=$? - rm -f $tmp - echo "Massif failed. Output is in ${vgout}" - exit $rc - } - echo "Massified $test: try: ms_print ${msp} | less" -} - -#if [ -e f-s2sh ]; then -# # kludge for the libfossil source tree -# S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS}" -#fi -if [[ x1 = x${DO_DEBUG} ]]; then - s2sh="gdb --args $s2sh" -fi -S2SHFLAGS="-rv -si ${S2SH_ADD_FLAGS}" -# Reminder: some fsl modules rely on code set up by s2sh init script, -# so we cannot use the --a option in libfossil. -echo S2SHFLAGS=$S2SHFLAGS -for test in $list; do - echo "Running require.s2 test: ${test}" - outfile=${rdir}/${test}.test_out - verbose "Output going to: $outfile" - rm -f "$outfile" - tmpfile=${rdir}/${test}.tmp - echo "${requireCall}(['nocache!${test}']);" > $tmpfile - cmd="$s2sh ${S2SHFLAGS} -o ${outfile} -f ${tmpfile}" - echo "Running test [${test}]: $cmd" - $cmd - rc=$? - [[ 0 -eq $rc ]] || { - echo "Test '${test}' failed. Script output (if any) saved to [${outfile}]" 1>&2 - exit $rc - } - #echo "Test did not fail: ${test}" - rm -f $tmpfile - if [[ -s "$outfile" ]]; then - verbose -e "\tOutput is in: ${outfile}" - else - rm -f "${outfile}" - fi -done - -if [[ x1 = "x$DO_VG" ]]; then - vg=$(which valgrind) - if [[ -x "$vg" ]]; then - echo "Runing test(s) through valgrind..." - massif="${vg} --tool=massif --time-unit=ms --heap-admin=0" - for test in $list; do - outfile=$rdir/${test}.test_out - rm -f "$outfile" - vgTest $test -o "${outfile}" - done - fi -fi - -echo "Done! Tests run: ${list}" DELETED bindings/s2/require-demo.s2 Index: bindings/s2/require-demo.s2 ================================================================== --- bindings/s2/require-demo.s2 +++ bindings/s2/require-demo.s2 @@ -1,106 +0,0 @@ -// Alias our requirejs workalike for convenience... -assert Fossil.require /* gets set via f-s2sh.s2 */; -const R = Fossil.require; -if(!R.fsl){ - /* kludge to avoid requiring a CLI flag to provide a repo */ - const f = R(['fsl/context']).0; - affirm f === R.fsl; - affirm R.fsl.db; - R.fsl.openCheckout(); -}/* else a downstream module will open the repo as needed */ - - -print('require() plugins:', R.plugins.propertyKeys()); - -// Use it like requirejs: -R([// Resources to load: those with a '!' are "plugins", not script files - 'fsl/wikiByName!download', - 'text!require-demo.s2', - 'fsl/blob!rid:1', - 'fsl/manifest!trunk' - ], // Callback to pass the resources to: - function(page, thisScript, rid1, trunk){ - print(__FLC, typeinfo(name page), typeinfo(name thisScript), typeinfo(name rid1)); - print("Wiki page [", page.L,"] is", page.W.lengthBytes(),"bytes long."); - print("This script is",thisScript.lengthBytes(),"bytes long."); - print("RID 1's manifest is", rid1.length(), "bytes long."); - print("trunk has", trunk.F.length(), "F-cards."); - }); - - -R(['fsl/context', // shared/cached Fossil.Context instance - 'fsl/wiki/util' // various Wiki utilities - ], - function(fsl, wikiUtil){ - var pages = []; - wikiUtil.getPageNames(). - eachIndex(proc(name){ - pages[] = fsl.loadManifest( wikiUtil.getLatestRid(name) ); - }); - print(pages.length(),"wiki pages found:"); - const j2h = Fossil.time.julianToHuman; - 0 && pages.sort(function(l,r){ - // It seems we have a bug in/around cwal_array_sort_stateful(), as - // the sorted result here is very unexpected. But i'm too tired - // to fight it. - // print('',j2h(r.D), '\n', j2h(l.D), j2h(r.D).compare(j2h(l.D))); - //return j2h(r.D).compare(j2h(l.D)); - //print(r.D, l.D, r.D.compare(l.D)); - //return (r.D * 10000000).toInt() - (l.D * 10000000).toInt(); - return r.D.compare(l.D); - }); - pages.eachIndex(proc(page){ - print( " %1$-25s @ %2$s by %3$s %4$d bytes". - applyFormat(page.L, - j2h(page.D), - page.U, - page.W.lengthBytes())); - }); - print(""); - }); - -R(['fsl/timeline/basic', // list of most recent rows from the event table - 'ostream' // output utility for fans of C++ - ], - proc(tl, os){ - os << "Most recent timeline entries:\n"; - tl.eachIndex(proc(v){ - os << v.type << ' ' << v.uuid.substr(0,10) - << ' @ ' << Fossil.time.julianToHuman(v.mtime) - << ' by ' - << (v.euser ||| v.user) - << '\n\t' - << (v.ecomment ||| v.comment) - << '\n'; - }); - os << '\n'; - }); - -// An auto-loaded (not pre-registered) plugin: -R(['demo!foo', - 'demo!bar?a=1&b&c=hi there' - ], - proc(demo, d2){ - print("Demo auto-loaded plugin:",demo); - assert d2 === demo /* because of how this particular plugin works */; - assert 2 === demo.counter; - assert 'bar' === demo.lastArgs.0; - assert 'hi there' === demo.lastArgs.1.c; - }); - -if(Fossil.file.isFile('cgimod.so')){ - R(['dll!cgimod', // the CGI API (loadable module) - 'dll!cgimod', // same instance (cached) - 'dll!cgimod?checkingCache' // params bypass caching - /* reminder: 2 instances of this module is semantically - invalid in a real CGI context. We're just testing that - R()'s docs regarding caching matches its behaviour. */ - ], - proc(cgi, cgi2, cgi3){ - assert cgi === cgi2; - assert cgi !== cgi3; - print("Loaded cgi module: "+cgi); - }); -}else{ - print("cgimod DLL not found."); -} DELETED bindings/s2/require.d/BufferFactory.s2 Index: bindings/s2/require.d/BufferFactory.s2 ================================================================== --- bindings/s2/require.d/BufferFactory.s2 +++ bindings/s2/require.d/BufferFactory.s2 @@ -1,18 +0,0 @@ -/** - A require.s2 module which returns an object containing "factory - methods" for creating Buffers. -*/ -return { - /** - Usage: - - var buffer = thisObj.new([reservedMemorySize=0]); - */ - new: s2.Buffer.new, - /** - Usage: - - var buffer = thisObj.readFile(filename); - */ - readFile: s2.Buffer.readFile -} DELETED bindings/s2/require.d/BufferFactory.test.s2 Index: bindings/s2/require.d/BufferFactory.test.s2 ================================================================== --- bindings/s2/require.d/BufferFactory.test.s2 +++ bindings/s2/require.d/BufferFactory.test.s2 @@ -1,14 +0,0 @@ -/** - A require.s2 module which returns an object containing "factory - methods" for creating Buffers. -*/ -requireS2( -['BufferFactory'], -proc(bfac){ - var b = bfac.new(100); - affirm b.capacity()>=100; - - b = bfac.readFile(__FILE); - affirm 'buffer' === typename b; - affirm b.length()>50; -}); DELETED bindings/s2/require.d/DataModels/TestModel.s2 Index: bindings/s2/require.d/DataModels/TestModel.s2 ================================================================== --- bindings/s2/require.d/DataModels/TestModel.s2 +++ bindings/s2/require.d/DataModels/TestModel.s2 @@ -1,43 +0,0 @@ -/** - Test/demo require.s2 DataModel module the modeler module - and/or the DataModel plugin. -*/ -return { - //__typename: 'Testing that this will not get overwritten', - /** - Gets called by the modeler framework when this module's - (inherited) new() method is called. In the context of this - function, "this" will be the new Model instance, which inherits - this object. - - The return value is ignored by the modeler framework. - */ - initialize: proc(a,b,c){ - print(__FLC, 'initialize()ing', typename this, this); - print(__FLC, 'argv =', argv); - print(__FLC, 'super =',super); - assert __FILE.indexOf(typename this)>=0 - /* __typename gets automatically set from the file name - when the model is loaded if it does not set one itself. */; - this.attr('a',a) - .attr('b',b) - .attr('c',c); - }, - /** - Attributes defined in the prototype are visible via the - inherited attr() method, but do not get serialized via the - inherited toJSON() method unless their values have been - explicitly set in the most-derived model instance. - */ - attributes:{ - foo: -1 - } -}; -/*.withThis(proc(){ - return ('undefined' === typename requireS2) - ? this - : ((const that=this), - requireS2(['modeler'],proc(M){ - return M.extendModel(that) - })); -});*/ DELETED bindings/s2/require.d/Ticker.s2 Index: bindings/s2/require.d/Ticker.s2 ================================================================== --- bindings/s2/require.d/Ticker.s2 +++ bindings/s2/require.d/Ticker.s2 @@ -1,315 +0,0 @@ -/** - A require.s2 module implementing an abstract timer class. See the - inlined docs (down below) for details. - - Example usage: - - - const Ticker = require(['Ticker']).0; - const t = new Ticker(); - t.addEvent(1, true, proc(){print("repeating event")}); - t.addEvent(3, proc(){print("one-time event")}); - - t.tick(); - t.tick(); - t.tick(); - // or: t.tick(3) - - Will output the following, though the order of events at firing at - the same logical time (the last two lines here) is not defined and - may change during the lifetime of repeating events or differ - depending on whether the clock is incremented one tick at a time or - more than one: - - repeating event - repeating event - one-time event - repeating event -*/ -/** - Ticker is a utility class implementing an abstract - timer. It doesn't know anything about time - it keeps - track of time in abstract ticks. Events can be added - to it which are fired after a certain number of ticks, - optionally repeating every N ticks. - - To create a new instance, call this function. Its return - value inherits this function. - - It's functionally similar to JavaScript's setTimeout() - and setInterval() except for: - - a) It is strictly synchronous. - - b) It has no clock. The client has to tell it that X amount of - (abstract) time has passed. - - c) It currently has no way to properly remove events (and doing so - from an event handler may mess up its iteration for the time being). -*/ - - -return { - __typename: 'Ticker', - /** Internal list of events */ - //tlist: [], - - /** Current timestamp (tick counter) */ - //ts: 0, - - /** Internal helper to sort event entries by their timestamp. - Empty/null entries are sorted as less than any others - because this make them easy to remove. - */ - sortEvents:function f(){ - f.sorter || (f.sorter=function(l,r){ - // sort nulls to the LEFT (easier (but more memmov'ing) to lop them off that way) - l || return r ? -1 : 0; - r || return 1; - return compare(l.when, r.when) ||| - compare(l.priority, r.priority) ||| - compare(l.ts,r.ts) ||| - compare(l.id,r.id); - }); - this.tlist.sort(f.sorter); - }.importSymbols({compare: function(l,r){ - return (lr) ? 1 : 0); - }}), - - /** - Returns an incremental integer value on each call. - */ - nextEventId: function(){ - this.idCounter || (this.idCounter = 0); - return ++this.idCounter; - }, - - /** - Adds a timer event to (potentially) be triggered - via advancement of the timer (see tick()). - - 'when' is the relative tick number (in the future) in which - to fire the event. Must be a positive value. - - 'repeats' is a boolean. Repeating events trigger every 'when' - ticks. - - 'what' is a Function which gets called when the event timer - is triggered. When called, what's 'this' will be the - event Object containing 'what'. - - If called with two arguments then it is treated as if - (when, false, what) are passed in. - - If called with one argument then it is equivalent to calling - addEvent2(). - - Returns an Object describing the event. Clients may add - their own properties to it and use those from the - what() handler (accessible via this.PROPERTY). - - Callers can control the ordering of events fired at - the same time by setting a priority numeric property - on the returned event object. Priority sorts using - normal numeric comparison, so lower values sort first. - The default priority is 0 and negative values are legal. - - This class supports the following event object properties - (and ignores all others): - - id: has no meaning but is used as a fallback option when sorting. - Clients may set this to what they wish, and a default value - (incremental integers) is set. - - ts: the current "timestamp", in ticks, of this ticker. It is - incremented by calling tick(). - - when: the tick time at which the event will next fire. - - what: the function to call when the event fires. - - interval: an integer specifying that the event should repeat - every this-many ticks. If not set, or set to a falsy value, - the event is a one-time event and will be removed after firing - by the tick() process. - - priority: an integer value which determines run order for - events firing at the same tick (lower values sort first). For - "overlapping" ticks (with a time span of more than one tick) - this order might be somewhat intuitive: all "overlap" runs of - a given event handler are processed before the next - event. i.e. the priority order is maintained, but each event - may be run multiple times in succession before another event - with a higher (or the same) priority value. - - client: this property name is reserved solely for use by - client code. This API promises never to use that property - key in event objects. - */ - addEvent:function(when, repeats, what ){ - const argc = argv.length(); - (1===argc) && return this.addEvent2(when); - if(2===argc && typeinfo(isfunction repeats)){ - what = repeats; - repeats = false; - } - affirm when>0; - affirm typeinfo(isfunction what); - this.idCounter || (this.idCounter = 0); - const ev = { - id: this.nextEventId(), - ts: this.ts, - when: this.ts + when, - what: what, - priority: 0 - }; - repeats && (ev.interval = when); - this.tlist.push(ev); - return ev; - }, - - /** - An alternate form of addEvent() which takes an object. - - Returns the object passed to it, after enriching it - with event state and adding it to the list. - - If the object has a an 'interval' property but no 'when' - property then its 'when' is set to the interval value. - Likewise, if 'when' is set and 'repeats' is set to a truthy - value, 'interval' gets set to 'when'. - - Example: - - ticker.addEvent({interval: 3, what:myFunc}); - - will set up a repeating event, firing first in 3 ticks, - and then ever 3 ticks after that. - */ - addEvent2:function(obj){ - affirm typeinfo(iscontainer obj); - affirm typeinfo(isfunction obj.what); - if(obj.repeats && !obj.interval){ - obj.interval = obj.when; - } - unset obj.repeats; - if(obj.interval && !obj.when){ - obj.when = obj.interval; - } - affirm obj.when > 0; - obj.ts = this.ts; - obj.priority || (obj.priority = 0); - obj.id || (obj.id = this.nextEventId()); - this.tlist[] = obj; - return obj; - }, - - /** - Removes all event handlers and resets the tick counter to 0. - */ - reset: function(){ - this.ts = 0; - this.tlist.clear(); - return this; - }, - - /** - Increments the tick count by incr (default=1) and triggers any - events whose time comes. If incr is greater than one and a - repeating event would normally have been triggered multiple - times within that span, this function calls it the number of - times it would have been called had tick() been called in - increments of 1. The order of event callbacks is unspecified - by default - they continually get re-sorted based on their - trigger time. Clients can control the order of like-timed - events by setting a priority level on an event - see - addEvent() for details. - - Events added to this object by an event handler will NOT - be called in this invocation of tick() - they are queued up - and added to the event list after tick() has finished processing - any pending handlers. - - Returns this object. - */ - tick: function(incr=1){ - affirm typeinfo(isinteger incr); - affirm incr >= 0; // a tick value of 0 might be interesting for something - const li = this.tlist; - var n = li.length(); - affirm typeinfo(isarray li); - this.ts += incr; - n || return this; - - /* Remove any expired-and-fired events here (as opposed to afterwards) - because (it turns out) this simplifies things. */ - this.sortEvents(); // moves nulls to the left - while(n && !li.0){ - /* Remove any expired events */ - li.shift(); - --n; - } - n || return this; - - // Move this.tlist so that adding events while looping does not affect us. - const listSentry = this.tlist; - this.tlist = []; - - /* - Loop over events until we find one with a 'when' - set in the future. All current events will be to the - left of that one. - */ - const tm = this.ts; - for(var i = 0, e, shiftCount = 0, repeater = 0; - itm){ break } /* first in-the-future event. */ - else { - affirm e.when > e.ts; - if(var err=catch{e.what()}){ - print(__FLC,"WARNING: tick handler threw. Removing it.",e,err); - unset e.interval; - } - if(e.interval){ /* Set this one up to run again */ - e.ts = e.ts + e.interval; - e.when = e.ts + e.interval; - if(e.when <= tm){ - /* timespan skipped one or more intervals. Run them now. */ - //print(__FLC,"firing again for overlap:",e); - --i /* fudge the loop counter to repeat this entry. - May break this.tlist is modified by - callback. */; - repeater = e; - continue; - } - }else{ /* remove the event */ - li[i] = undefined; - ++shiftCount; - } - } - } - /* Move this.tlist back... */ - if(this.tlist.isEmpty()){ /* no changes made during iteration */ - this.tlist = listSentry; - }else{ - /* Events added while looping. Integrate them now. - We swap tlist here so that we can keep the original - array (as a minor potential allocation optimization). */ - this.tlist.eachIndex(integrateTlist); - this.tlist = listSentry; - } - return this; - }/*tick()*/.importSymbols({ - integrateTlist: proc(i,v){ listSentry.push(v) } - }), - /** - Constructor function for new instances. - */ - __new: proc(){ - this.tlist = []; - this.ts = 0; - } -}; DELETED bindings/s2/require.d/Ticker.test.s2 Index: bindings/s2/require.d/Ticker.test.s2 ================================================================== --- bindings/s2/require.d/Ticker.test.s2 +++ bindings/s2/require.d/Ticker.test.s2 @@ -1,64 +0,0 @@ -requireS2(['time','nocache!Ticker'], proc(time, Ticker){ - affirm 'Ticker' === typeinfo(name Ticker); - const t = new Ticker(); - affirm t inherits Ticker; - affirm 'Ticker' === typeinfo(name t); - const callback = proc(){print('fired:',this.name)}; - var ev; - affirm typeinfo(isfunction callback); - // Repeating event, every 3rd tick: - ev = t.addEvent(3, true, callback); - - ev.name = "threeer"; - ev.priority = 3; - /* - Event priority is used to break ties for events firing at - the same virtual time. It sorts by value, so a lower value - has a higher priority. Events have a default priority of 0. - */ - - // Add a repeating event which fires every tick: - ev = t.addEvent(1, true, callback); - ev.name = "oner"; - ev.priority = -1; - - - // Add a one-time event, fired once, four ticks from now: - affirm typeinfo(isfunction callback); - ev = t.addEvent(4, callback); - ev.name = "fourer"; - ev.priority = 4; - - // Add a repeating event, firing every 2nd tick: - ev = t.addEvent({interval: 2, what: callback, name: "twoer"}); - - print(__FLC,typeinfo(name t),':',t); - - // Now advance our virtual clock a few times... - var i = 1, max = 10, sleepTimeMs = 100; - - print("Ticking",max,"times, sleeping",sleepTimeMs," millis between ticks..."); - //const timeFmt = {%Y-%m-%d %H:%M:%S} - //const startTime = strftime(timeFmt, time()) - for( ;i<=max; i=i+1){ - print("TICK #"+i); - time.mssleep(sleepTimeMs); - t.tick(); - } - //const endTime = strftime(timeFmt, time()); - print('After loop: Tick =',t.ts); - //print('Start time:',startTime,'\nEnd time :',endTime); - if(0) { - print('"Manually" ticking...'); - t.tick(6); // note how priority sorting kinda breaks down here - // potential workaround: run ticks>1 in a loop of smaller ticks, - // but how small? Maybe set tickerInstance.timeUnit=integer to - // set the smallest time unit, and loop in increments of that unit? - } - //t.tick(5); - //print(__FLC,"Done:",t); - - //dumpInternedStrings(); - t.reset(); - affirm t.tlist.isEmpty(); -}); DELETED bindings/s2/require.d/cliargs.s2 Index: bindings/s2/require.d/cliargs.s2 ================================================================== --- bindings/s2/require.d/cliargs.s2 +++ bindings/s2/require.d/cliargs.s2 @@ -1,101 +0,0 @@ -/** - A require.s2 module which returns a utility object for working with - the CLI arguments provided via s2.ARGV. - - "Script arguments" are those passes to s2sh after any '--' flag. - Those get imported into s2.ARGV (which is not set if no script - flags are provided). - - s2.ARGV, if set, is an Array containing all flags passed after - '--', plus possibly containing these two properties: - - .flags: an object of --flag=value pairs, where --flags with no - value are treated as boolean true. - - .nonFlags: any script argument which does not start with '-' is - assumed to be a filename or some other string, and is appended - to this array. - - Either property will be undefined if no flags resp. non-flags - are provided. -*/ -return { - - /** - Holds an Array of all arguments passed after '--' to s2sh - resp. any app which installs that particular binding. It is - undefined (not an empty array) if there are no such flags. - */ - args: s2.ARGV, - /** - An object (or not) containing any -flags. The keys are stripped - of any number of leading dashes and if a given flag is - duplicated, the last one currently wins (as opposed to getting - an array of values, though that might be a useful addition). - */ - flags: s2.ARGV ? s2.ARGV.flags : undefined, - - /** - An array (or not) of any non-flags passed after --, - in the order they were passed in. - */ - nonFlags: s2.ARGV ? s2.ARGV.nonFlags : undefined, - - /** - If the given flag (minus any number of prefixing "-") was passed in - the "script flags" (any flags passed after '--' to the s2sh - interpreter), its value is returned, otherwise dflt is returned. - */ - getFlag: proc(flag, dflt){ - return this.flags - ? (this.flags[flag] ?: dflt) - : dflt; - }, - - /** - If the given flag (minus any number of prefixing "-") was - passed in the "script flags" (any flags passed after '--' to - the s2sh interpreter), its is removed from the flags and its - value is returned, otherwise dflt is returned. - - When the last flag is removed, this.flags is unset. - */ - takeFlag: proc(flag, dflt){ - this.flags || return dflt; - if(undefined !== const v = this.flags[flag]){ - unset this.flags[flag]; - this.flags.# || unset this.flags; - return rc; - }else return dflt; - }, - - /** - Returns true if the CLI flags (still) contain any flags, - otherwise false. - */ - hasFlags: proc(){ - return this.flags ? this.flags.#>0 : false; - }, - - /** - Returns true if the CLI flags (still) contain any non-flags, - otherwise false. - */ - hasNonFlags: proc(){ - return this.nonFlags ? !this.nonFlags.isEmpty() : false; - }, - - /** - Removes the first non-flag from the list and returns - it. Returns undefined if there are no non-flags (or none - remaining). - - When the last entry is removed, this.nonFlags is unset. - */ - nextNonFlag: proc(){ - this.nonFlags || return undefined; - var rc = this.nonFlags.shift(); - (0===this.nonFlags.length()) && unset this.nonFlags; - return rc; - } -}; DELETED bindings/s2/require.d/fsl/context.s2 Index: bindings/s2/require.d/fsl/context.s2 ================================================================== --- bindings/s2/require.d/fsl/context.s2 +++ bindings/s2/require.d/fsl/context.s2 @@ -1,7 +0,0 @@ -/** - require() plugin which simply resolves to the shared Fossil.Context - instance set up by the f-s2sh bootstrap code. This instance should - be used by all other modules so that everybody is using the same - database handles. -*/ -return requireS2.fsl ||| (requireS2.fsl = new Fossil.Context()); DELETED bindings/s2/require.d/fsl/db/checkout.s2 Index: bindings/s2/require.d/fsl/db/checkout.s2 ================================================================== --- bindings/s2/require.d/fsl/db/checkout.s2 +++ bindings/s2/require.d/fsl/db/checkout.s2 @@ -1,14 +0,0 @@ -/** - require() plugin which returns a handle to the repo Db object for - Fossil.require's shared Fossil.Context. If no checkout has been - opened when this is called, openCheckout() is called on the - context, otherwise there are no side effects. -*/ -return requireS2(['fsl/context'], proc(F){ - affirm const d = F.db; - if(!d.checkout){ - F.openCheckout(); - } - affirm d.repo; - return d.repo; -}); DELETED bindings/s2/require.d/fsl/db/config.s2 Index: bindings/s2/require.d/fsl/db/config.s2 ================================================================== --- bindings/s2/require.d/fsl/db/config.s2 +++ bindings/s2/require.d/fsl/db/config.s2 @@ -1,11 +0,0 @@ -/* require() plugin which returns a handle to the repo Db object for - Fossil.require's shared Fossil.Context. If no repo has been opened - when this is called, it opens the repo specified via the - --repo-db|-R=FILENAME CLI script flag. -*/ -return requireS2(['fsl/context'], proc(F,args){ - affirm const d = F.db; - d.config || F.openConfig(); - affirm d.config; - return d.config; -}); DELETED bindings/s2/require.d/fsl/db/main.s2 Index: bindings/s2/require.d/fsl/db/main.s2 ================================================================== --- bindings/s2/require.d/fsl/db/main.s2 +++ bindings/s2/require.d/fsl/db/main.s2 @@ -1,5 +0,0 @@ -/** - require.s2 module which returns the "main" db handle of - the shared Fossil.Context instance. -*/ -return requireS2(['fsl/context']).0.db; DELETED bindings/s2/require.d/fsl/db/repo.s2 Index: bindings/s2/require.d/fsl/db/repo.s2 ================================================================== --- bindings/s2/require.d/fsl/db/repo.s2 +++ bindings/s2/require.d/fsl/db/repo.s2 @@ -1,18 +0,0 @@ -/* require() plugin which returns a handle to the repo Db object for - Fossil.require's shared Fossil.Context. If no repo has been opened - when this is called, it opens the repo specified via the - --repo-db|-R=FILENAME CLI script flag. -*/ -return requireS2(['fsl/context','cliargs'], proc(F,args){ - affirm const d = F.db; - if(!d.repo){ - const f = args.takeFlag('repo-db',args.takeFlag('R')) - ||| throw "No repo db specified. "+ - "Pass the --repo-db|-R=filename "+ - "SCRIPT flag (after --) to the interpreter or open a checkout."; - ('string' === typename f) || throw "Invalid argument type for the --repo-db|-R=DBFILE flag"; - F.openRepo(f); - } - affirm d.repo; - return d.repo; -}); DELETED bindings/s2/require.d/fsl/db/repoOrCheckout.s2 Index: bindings/s2/require.d/fsl/db/repoOrCheckout.s2 ================================================================== --- bindings/s2/require.d/fsl/db/repoOrCheckout.s2 +++ bindings/s2/require.d/fsl/db/repoOrCheckout.s2 @@ -1,16 +0,0 @@ -/** - require.s2 module which returns either the repo db (specified by - the -R|--repo-db=DBFILE script flags) or (if that fails) the repo - db associated with the current checkout. Throws if it can neither - open a checkout nor figure out the db from the CLI args. -*/ -return requireS2(['fsl/context'], proc(F){ - affirm F.db; - F.db.repo && return F.db.repo; - F.db.checkout && return F.db.checkout; - catch return requireS2(['fsl/db/repo']).0; // fails if no -R|--repo-db=DBFILE flag specified - requireS2(['fsl/db/checkout']) /* opens the checkout */; - assert F.db.checkout; - return F.db.repo; -}); - DELETED bindings/s2/require.d/fsl/extendFossil.s2 Index: bindings/s2/require.d/fsl/extendFossil.s2 ================================================================== --- bindings/s2/require.d/fsl/extendFossil.s2 +++ bindings/s2/require.d/fsl/extendFossil.s2 @@ -1,153 +0,0 @@ -/* - A require.s2 module which adds some functionality - to the various Fossil classes. - - This module is not intended to be used more than once and should not - be cached. Thus, to use it: - - Fossil.require(['nocache!fsl/extendFossil'],proc(){}) -*/ - - -assert Fossil; -assert Fossil.Context; -Fossil.artifactTypes && return Fossil /* already ran this module */; - - -//Fossil.loadModule = s2.loadModule2; -//Fossil.importScript = s2.import2; - -/** - Counterpart of the C-level fsl_catype_e enum. -*/ -Fossil.artifactTypes = { - ANY: 0, - CHECKIN: 1, - CLUSTER: 2, - CONTROL: 3, - WIKI: 4, - TICKET: 5, - ATTACHMENT: 6, - EVENT: 7 -}; - -/** - Fetches the first row from the given SQL statement (String or - Buffer) and returns it as an Object (if asArray is false) or an - Array (if asArray is true). - - If the bind argument is passed in then it is passed on to the - Stmt.bind() method of the underlying statement. Use an array to - bind multiple values. To specify the third parameter when there is - nothing to bind, pass the undefined value as the second argument. - i.e. bind===undefined is treated as "nothing to bind," instead of - binding undefined/null. - - Throws on error. Returns undefined if no row is found. -*/ -Fossil.Db.selectRow = proc(sql, bind = undefined, asArray = false){ - affirm this inherits Fossil.Db; - const st = this.prepare(sql); - var rc; - const ex = catch { - st.bind(bind); - rc = asArray ? st.stepArray() : st.stepObject(); - }; - st.finalize(); - ex ? throw ex : return rc; -}; - -/** - Returns an array containing the complete results of each row in the - given SQL's result set. If asArray is true, each row is returned as - an array of column values in the same order as the result set's, - else each is returned an an Object of column name/value pairs with - columns in an an unspecified order. - - The bind parameter may hold value(s) to bind to the given SQL. -*/ -Fossil.Db.selectAll = proc(sql, bind = undefined, asArray = true){ - affirm this inherits Fossil.Db; - const rc = []; - this.each({ - sql: sql, - bind: bind, - mode: asArray ? 1 : 0, - callback: 'rc[] = this', - }); - return rc; -}; - -/** - Given a SELECT-style query and optional bind parameters - (either a single value for a single param or an array - of multiple params), this routine simply dumps out - the results of the query. bind===undefined is treated - as "nothing to bind," instead of binding undefined/null. -*/ -Fossil.Db.dumpQuery = proc(sql,bind = undefined, separator='\t'){ - affirm this inherits Fossil.Db; - return this.each({ - sql:sql, - bind:bind, // note that undefined value is treated as non-existent here - callback:proc(){ - (1===rowNumber) && print(columnNames.join(separator)); - print(this.join(separator)); - } - }); -}; - -/** - Stmt.each() loops over this.step(), calling func(N) on each - iteration, where N is the current row number (1-based). If func() - returns a literal false, looping stops without an error. In the - context of the call, 'this' is the Stmt object. - - Returns this object. -*/ -Fossil.Db.Stmt.each = proc(func){ - affirm this inherits Fossil.Db.Stmt; - affirm typeinfo(iscallable func) && typeinfo(iscallable func.call); - for(var rowNum = 1; this.step(); ++rowNum){ - (false === func.call(this, rowNum)) && break; - //^^^^^^^^ emulate Db.each() - } - return this; -}; - - -/** - Changes to the given dir and pushes the (old) current dir - to the directory stack. To change back to the pre-pushd() - directory, call Fossil.file.popd(). This function relies - on this 'this' object beeing Fossil.file. Throws if it - cannot change directories. - - The array of pushed directory names is available after calling - this function one time via the property - Fossil.file.pushd.dirStack. The most recent directory is at the - end of that array. If that property is undefined, pushd() has - never been called (or someone removed the property). If it is an - empty array, there are currently no directories in the stack. -*/ -Fossil.file.pushd = proc callee(dir){ - affirm 'string' === typename dir; - const curdir = this.currentDir(); - this.chdir(dir); - (callee.dirStack ||| (callee.dirStack = [])).push( curdir ); -}; - -/** - Pops the directory mostly recently pushed by Fossil.file.pushd() - off of the directory stack and changes to that directory. Throws - if called when no directories can be popped. -*/ -Fossil.file.popd = proc(){ - const list = this.pushd.dirStack; - const len = (list ? list.length() : 0) - ||| throw "Directory stack is empty."; - this.chdir(list[len-1]); - return list.pop() -}; - -return Fossil; DELETED bindings/s2/require.d/fsl/reports/common.s2 Index: bindings/s2/require.d/fsl/reports/common.s2 ================================================================== --- bindings/s2/require.d/fsl/reports/common.s2 +++ bindings/s2/require.d/fsl/reports/common.s2 @@ -1,67 +0,0 @@ -/** - INCOMPLETE helper module for pending timeline/reporting bits. -*/ -return requireS2(['fsl/db/repo'], -proc(db){ - const mod = { - db: db, - - /*eventTypeMap:{ - '*': 'all', - ci: 'checkins', - w: 'wiki', - g: 'tags', - e: 'events', - t: 'tickets' - },*/ - - /** - Returns an array of years (integers) representing - the years for which there is timeline activity in - this repo. The entries are sorted ascending. - */ - getActiveYears:proc(){ - return this.db.selectValues(<< 0; - - const isLibfossil = 'libfossil' === rutil.getConfig('project-name'); - - if(isLibfossil){ - affirm x.indexOf(2014) >= 0; - affirm x.indexOf(1972) < 0; - } - - x = rcom.getActiveWeeksForYear(2014); - affirm 'array' === typename x; - affirm x.length() > 0; - - if(isLibfossil){ - affirm 1 === x.0; - affirm x.indexOf(22) < 0 /* no activity that week */; - affirm x.indexOf(41) > 0 /* when this test was added */; - } - - x = rcom.getActiveYearsAndWeeks(); - affirm 'array' === typename x; - affirm x.length() > 0; - affirm 'integer' === typename x.0.year; - affirm 'array' === typename x.0.weeks; - -}); DELETED bindings/s2/require.d/fsl/timeline/basic.s2 Index: bindings/s2/require.d/fsl/timeline/basic.s2 ================================================================== --- bindings/s2/require.d/fsl/timeline/basic.s2 +++ bindings/s2/require.d/fsl/timeline/basic.s2 @@ -1,20 +0,0 @@ -/** - require() module which returns an array of Objects from the event - table. Each includes all fields from the event table plus the - associated blob's uuid. -*/ -return requireS2( - ['fsl/db/repo'], - proc(repo){ - var rc = []; - repo.each({ - mode: 0, - sql:<<<_SQL - SELECT e.*, b.uuid uuid - FROM event e JOIN blob b ON e.objid=b.rid - ORDER BY e.mtime DESC LIMIT 5 - _SQL, - callback:'rc[] = this' - }); - return rc; -}); DELETED bindings/s2/require.d/fsl/util/repo.s2 Index: bindings/s2/require.d/fsl/util/repo.s2 ================================================================== --- bindings/s2/require.d/fsl/util/repo.s2 +++ bindings/s2/require.d/fsl/util/repo.s2 @@ -1,25 +0,0 @@ -/** - In-progress set of utilities for working with repo db tables. -*/ -requireS2(['fsl/db/repoOrCheckout'], proc(repo){ - return { - db: repo, - /** - Fetches an array of strings from this.db, each one being - the name of a user in the current repository. They are - sorted by name. - */ - getUserList: proc(){ - return this.db.selectValues('select login from user order by login'); - }, - - /** - Returns the value of the repoDb.config field with the given key, - or undefined if no such row is found. - */ - getConfig: proc(key){ - affirm 'string' === typename key /* expecting a legal repo.config.key value */; - return this.db.selectValue('SELECT value FROM config WHERE name=?',key); - } - }; -}); DELETED bindings/s2/require.d/fsl/util/repo.test.s2 Index: bindings/s2/require.d/fsl/util/repo.test.s2 ================================================================== --- bindings/s2/require.d/fsl/util/repo.test.s2 +++ bindings/s2/require.d/fsl/util/repo.test.s2 @@ -1,19 +0,0 @@ -/** - In-progress set of utilities for working with repo db tables. -*/ -requireS2(['fsl/db/checkout'/*force repo db open*/,'fsl/util/repo'], -proc(ignored, repo){ - const db = repo.db; - affirm db; - affirm repo.getUserList; - var x = repo.getUserList(); - affirm 'array' === typename x; - affirm x.length() > 0; - affirm x.indexOf('anonymous') >= 0; - - x = repo.getConfig('project-name'); - affirm 'string' === typename x; - x = repo.getConfig('no-such-key'); - affirm undefined === x; - affirm catch{repo.getConfig()}.message.indexOf("'string'") >0; -}); DELETED bindings/s2/require.d/fsl/wiki/pageNames.s2 Index: bindings/s2/require.d/fsl/wiki/pageNames.s2 ================================================================== --- bindings/s2/require.d/fsl/wiki/pageNames.s2 +++ bindings/s2/require.d/fsl/wiki/pageNames.s2 @@ -1,4 +0,0 @@ -return requireS2( - ['fsl/wiki/util'], - proc(util){ return util.getPageNames(); } -); DELETED bindings/s2/require.d/fsl/wiki/util.s2 Index: bindings/s2/require.d/fsl/wiki/util.s2 ================================================================== --- bindings/s2/require.d/fsl/wiki/util.s2 +++ bindings/s2/require.d/fsl/wiki/util.s2 @@ -1,39 +0,0 @@ -/** - A set of wiki-related utilities. -*/ -return requireS2( - ['fsl/context', 'fsl/db/repo'], - proc(fsl, repo){ - return { - getPageNames: proc( caseSensitive ){ - const st = repo.prepare(<<<_SQL - select distinct(substr(tagname,6)) name from tag t, - tagxref x where x.tagid=t.tagid and t.tagname - like 'wiki-%' order by name _SQL - + (caseSensitive - ? '' : ' collate nocase')); - var rc = []; - const ex = catch { - while(st.step()) rc[] = st.get(0); - }; - st.finalize(); - ex ? throw ex : return rc; - }.importSymbols(nameof repo), - - getLatestRid: proc(pageName){ - affirm pageName && 'string' === typename pageName; - return repo.selectValue(<<<_SQL - SELECT x.rid FROM tag t, tagxref x - WHERE x.tagid=t.tagid - AND t.tagname=? - ORDER BY mtime DESC LIMIT 1 - _SQL, - 'wiki-'+pageName - ); - }.importSymbols(nameof repo), - - loadPageArtifact: proc(pageName){ - return fsl.loadManifest( this.getLatestRid(pageName) ); - }.importSymbols(nameof fsl) - } - }); DELETED bindings/s2/require.d/io.s2 Index: bindings/s2/require.d/io.s2 ================================================================== --- bindings/s2/require.d/io.s2 +++ bindings/s2/require.d/io.s2 @@ -1,5 +0,0 @@ -/* a require.s2 module which "hides" s2.io via the module - interface. It could optionally load 'dll!mod_io', but that's - an internal impl detail. -*/ -return s2.io; DELETED bindings/s2/require.d/json.s2 Index: bindings/s2/require.d/json.s2 ================================================================== --- bindings/s2/require.d/json.s2 +++ bindings/s2/require.d/json.s2 @@ -1,5 +0,0 @@ -/* a require.s2 module which "hides" s2.json via the module - interface. It could optionally load 'dll!mod_json', but that's - an internal impl detail. -*/ -return s2.json; DELETED bindings/s2/require.d/json2.s2 Index: bindings/s2/require.d/json2.s2 ================================================================== --- bindings/s2/require.d/json2.s2 +++ bindings/s2/require.d/json2.s2 @@ -1,204 +0,0 @@ -affirm s2.json /* we need this as our basis */; -affirm s2.json.stringify /* was added later, might not be in all (two?) trees yet (ha!) */; -/** - This object basically acts as a (mostly) drop-in replacement for - s2.json, and it uses s2.json to implement most of its - functionality. It uses a custom, script-side stringify() which is - orders of magnitude less efficient (on several levels) than - s2.json.stringify() (which is implemented in C), but allows - overriding of to-JSON behaviour on a per-container or per-prototype - basis. -*/ -return { - /** See s2.json.parse(). */ - parse: s2.json.parse, - /** See s2.json.parseFile(). */ - parseFile: s2.json.parseFile, - /** - Converts the value v into a JSON string (or throws while trying). - - indention may be either a falsy value (for no intenation), a - string (which gets prepended N times for N levels of - indentation), or an integer: a positive value indents that many - spaces and a negative value indents that many tabs. - - v need not be a root-level value (Object or Array), but may be - a string, number, or boolean. - - Returns a string on success, throws on error. - - Notes about special cases: - - - If v or a prototype of v contains a function property named - toJSON() then v.toJSON() is used in place of v for - to-JSON-string conversion. The function must return some - JSON-able form of v. e.g. an implementation for a Hashtable - might return an Object in the form {keys:[...], values:[...]}. - - - Object _keys_ which are _not_ of type (string, integer, double) - are elided from the output. Keys of numeric types are converted - to strings for JSON key purposes. - - - Objects elide any keys which have value counterpart of - undefined. JSON does not know 'undefined'. We "could" translate - it to null here, but we instead opt to elide it. - - - The undefined value: if passed to this function, the string - 'null' is returned. undefined is also translated to 'null' in - the context of array empty entries. - */ - stringify: proc stringify(v, indention = stringify.config.indention){ - affirm ++stringify.level > 0; - const ex = catch{ - typeinfo(isderefable v) && typeinfo(iscallable v.toJSON) && (v = v.toJSON()); - const f = tmap # typename v; - affirm f /* Argument must be a known JSON-able type or have a toJSON() method. */; - if('string'===typename f){ - affirm --stringify.level>=0; - return f; - }else if(!f.buffered){ - /* "Simple" conversions which do not recurse */ - const rc = f(v); - affirm --stringify.level>=0; - return rc; - }else if(stringify.level>stringify.config.maxOutputDepth){ - throw exception('CWAL_RC_RANGE', - "Output depth limit ("+stringify.config.maxOutputDepth+ - ") exceeded while generating JSON."); - }else{ - affirm f.buffered /* f.buffered is set, so... */; - const jbuf = s2.Buffer.new() /* gets appended to by f() */; - f(v) /* appends all output to jbuf */; - affirm --stringify.level>=0; - affirm !jbuf.isEmpty(); - return jbuf.takeString(); - } - }; - affirm --stringify.level>=0; - assert ex /* or we couldn't have gotten this far */; - throw ex; - }.withThis(proc(){ - /** - Public configuration for stringify(). Change these - options to modify the defaults. - */ - this.config = { - /* Default indention used by stringify(). */ - indention: undefined, - /* Separator for entries in arrays and object lists. */ - commaSeparator: ', ', - /* Separator for keys and value in objects. */ - keyValSeparator: ': ', - /* Max object/array depth to allow before erroring - out. Remember that cycles will generally be detected - before this happens, so this doesn't necessarily - indicate that any cycles were encountered. - */ - maxOutputDepth: 15 - }; - this.level = 0; - return this; - }).importSymbols({ - // some crazy scoping and var accesses going on here... - /** - Indents the output, if appropriate, based on the current - call level (or the level specified by the 2nd - parameter). If addNL is true, a newline is appended before - the indentation. This is a no-op if stringify() is called - with a falsy indention parameter. - */ - indent: proc callee(addNL=true, level = stringify.level){ - indention || return; - callee.idbuf || (callee.idbuf = s2.Buffer.new(64)); - if(callee.prevLevel !== level){ - callee.prevLevel = level; - if('integer'===typename indention){ - const len = (indention<0) ? -indention : indention; - affirm len >= 0; - callee.idbuf.length( len * level ) - .fill((indention<0) ? 0x09 : 0x20); - }else if('string' === typename indention){ - callee.idbuf.reset(); - for(var i = 0; i < level; ++i){ - callee.idbuf << indention; - } - } - } - addNL && (jbuf << '\n'); - jbuf << callee.idbuf; - }, - /** - A hashtable mapping typenames to either strings (for static - conversions) or a function taking a value parameter. Those - functions normally return a string, but if the function has - a 'buffered' property which is truthy then its return - result is ignored and instead a Buffer value named jbuf is - made available to them, and they are expected to append all - output there. - */ - tmap: scope { - const proxy4Obj = proc(v){ - v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected."); - jbuf << '{'; - proxyEachProp.first = true; - //proxyEachProp('LEVEL', stringify.level); - const ex = catch v.eachProperty(proxyEachProp); - indention && indent(true,stringify.level-1); - jbuf << '}'; - ex && throw ex; - }.importSymbols({ - // Object.eachProperty() proxy. - proxyEachProp: proc callee(k,v){ - undefined === v && return; - callee.first - ? callee.first = false - : jbuf << stringify.config.commaSeparator; - indention && indent(); - const tk = typename k; - if('string'===tk){ - jbuf << k.toJSONString(); - }else if('integer'===tk||'double'===tk){ - jbuf << '"' << k << '"'; - }else{ - return; - } - jbuf << stringify.config.keyValSeparator - << stringify(v,indention); - } - }); - const proxy4Array = proc(v){ - v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected."); - jbuf << '['; - proxyEachIndex.first = true; - v.eachIndex(proxyEachIndex); - indention && indent(true,stringify.level-1); - jbuf << ']'; - }.importSymbols({ - proxyEachIndex: proc callee(v){ - callee.first - ? callee.first = false - : jbuf << stringify.config.commaSeparator; - indention && indent(); - jbuf << stringify(v,indention); - } - }); - - proxy4Obj.buffered = - proxy4Array.buffered = true - /* tells stringify() to set up a buffer to send the - results to. */; - const nativeImpl = s2.json.stringify; - {# - array: proxy4Array, - bool: nativeImpl, - double: nativeImpl, - exception: proxy4Obj, - integer: nativeImpl, - null: 'null', - object: proxy4Obj, - string: nativeImpl, - undefined: 'null' - }; // scope result - } - })/*stringify()*/ -}; DELETED bindings/s2/require.d/json2.test.s2 Index: bindings/s2/require.d/json2.test.s2 ================================================================== --- bindings/s2/require.d/json2.test.s2 +++ bindings/s2/require.d/json2.test.s2 @@ -1,74 +0,0 @@ -requireS2(['json2'],proc(JSON){ - //JSON.stringify.config.indention = -1; - const str = proc(v,indent=-1){ - return JSON.stringify(v,indent); - }; - var s; - assert '1' === str(1); - assert '1.23' === str(1.23); - assert '"hi, \\"there\\""' === str('hi, "there"'); - assert 'null' === str(null); - assert 'null' === str(undefined) - /* undefined will be elided in some contexts (object properties), - translated to null in others (e.g. arrays). */; - assert 'false' === str(false); - assert 'true' === str(true); - s = str({x:1, y:{z:'hi "there"', a:[1,2,"yo"]}, u: undefined, n: null}); - assert s.indexOf('"u":') < 0; - assert s.indexOf('"n": null')>0; - - s = JSON.parse(str(exception(-1,"not an error"))); - assert 'not an error' === s.message; - assert -1 === s.code; - - s = JSON.parse(str({a:{b:{1:2,3:4}}})); - assert 2 === s.a.b.1; - assert 4 === s.a.b.3 - /** - BUT ACHTUNG: the integer _keys_ got converted to strings in the - round trip because JSON only supports strings as - keys. Supporting round-trip fideltity for non-string keys - requires a layer of indirection, as demonstrated next... - */; - - // Customizing toJSON for a non-POD type... - var h = s2.Hash.new(); - - // A stringify()-compliant toJSON() impl for Hashtables. - h.toJSON = proc(){ - return { - keys: this.entryKeys(), - values: this.entryValues() - } - }; - - // Just for symmetry (not used by the JSON API)... - h.fromJSON = proc(jsonObj){ - ('string' === typename jsonObj) && (jsonObj = JSON.parse(jsonObj)); - this.clearEntries(); - if(('array' === typename jsonObj.keys) && - ('array' === typename jsonObj.values)){ - const that = this; - jsonObj.keys.eachIndex(proc(v,i){ - that.insert(v, jsonObj.values[i]); - }); - } - return this; - }.importSymbols(nameof JSON); - - // Now try serializing a hash... - h.insert(1, "one"); - h.insert(2, "two"); - s = JSON.parse(str(h)); - assert 2 === s.keys.length(); - assert 2 === s.values.length(); - assert s.keys.indexOf(2)>=0; - assert s.values.indexOf('two')>=0; - - h.clearEntries(); - assert undefined === h # 2; - - h.fromJSON(s); - assert 'two' === h # 2; - -}); DELETED bindings/s2/require.d/ob.s2 Index: bindings/s2/require.d/ob.s2 ================================================================== --- bindings/s2/require.d/ob.s2 +++ bindings/s2/require.d/ob.s2 @@ -1,5 +0,0 @@ -/* a require.s2 module which "hides" s2.ob via the module - interface. It could optionally load 'dll!mod_ob', but that's - an internal impl detail. -*/ -return s2.ob; DELETED bindings/s2/require.d/ostream.s2 Index: bindings/s2/require.d/ostream.s2 ================================================================== --- bindings/s2/require.d/ostream.s2 +++ bindings/s2/require.d/ostream.s2 @@ -1,13 +0,0 @@ -/** - A require() module providing an "ostream" object. It overloads the - << operator and sends all arguments to s2out. - - This module was written before the s2out keyword existed. That - keyword does the same thing but does so more efficiently, making - this module entirely superfluous. It is retained solely as a basic - example of how to write a require.s2 module. -*/ -affirm typeinfo(iscallable s2out); -return { - 'operator<<': proc(){s2out<= 0 ? ';' : ':') - : ['.'], - suffix: ('string' === typename (dllTmp = s2.getenv('S2_MODULE_EXTENSIONS'))) - ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':') - : ['.so','.dll'], - load: function(name,opt){ - var rc; - affirm name && 'string' === typename name; - rc = (opt && opt.entryPoint) - ? s2.loadModule(name, opt.entryPoint, {}) - : s2.loadModule(name, {}); - affirm rc; - if(opt && opt.hasOwnProperty('moduleProperty')){ - rc = rc[opt.moduleProperty]; - }else if(var keys=rc.propertyKeys(); - 1===keys.length()){ - rc = rc[keys.0]; - } - return rc; - } -}; - -mod.prefix.push( requireS2.home + s2.io.dirSeparator + 'dll' ); - -return mod; DELETED bindings/s2/require.d/plugins/fsl/blob.s2 Index: bindings/s2/require.d/plugins/fsl/blob.s2 ================================================================== --- bindings/s2/require.d/plugins/fsl/blob.s2 +++ bindings/s2/require.d/plugins/fsl/blob.s2 @@ -1,22 +0,0 @@ -/** - require() plugin which fetches a blob from the repo db. It - interprets its argument as a symbolic blob name (anything - libfossil's symbol-to-RID conversions support). -*/ -return requireS2( - ['fsl/context', - 'fsl/db/repo' // so that we are ensured that repo is opened by this point - ], - proc(fsl){ - return { - isVirtual: true, - _F: fsl, - load: function(sym){ - return this._F.loadBlob(sym) - } - /* If we wanted to be really pedantic, we would - require(['fsl/db/repo']...) inside load(). - Overkill. - */ - } - }); DELETED bindings/s2/require.d/plugins/fsl/manifest.s2 Index: bindings/s2/require.d/plugins/fsl/manifest.s2 ================================================================== --- bindings/s2/require.d/plugins/fsl/manifest.s2 +++ bindings/s2/require.d/plugins/fsl/manifest.s2 @@ -1,21 +0,0 @@ -/** - require() plugin which fetches a Manifest from the repo db, - in the form of an Object which mimics the fsl_deck structure - (see Fossil.Context.loadManifest()). - - It interprets its argument as a symbolic blob name (anything - libfossil's symbol-to-RID conversions support). -*/ -return requireS2( - ['fsl/context', - 'fsl/db/repo' // so that we are ensured that repo is opened by this point - ], - proc(fsl){ - return { - isVirtual: true, - _F: fsl, - load: function(sym){ - return this._F.loadManifest(sym); - } - } - }); DELETED bindings/s2/require.d/plugins/fsl/wikiByName.s2 Index: bindings/s2/require.d/plugins/fsl/wikiByName.s2 ================================================================== --- bindings/s2/require.d/plugins/fsl/wikiByName.s2 +++ bindings/s2/require.d/plugins/fsl/wikiByName.s2 @@ -1,25 +0,0 @@ -/** - require() plugin which fetches a wiki page from the repo db, - in the form of an Object which mimics the fsl_deck structure - (see Fossil.Context.loadManifest()). - - It interprets its argument as a wiki page name and throws if not - found. -*/ -return requireS2(['fsl/context', 'fsl/db/repo'], proc(fsl, repo){ - return { - isVirtual: true, - _F: fsl, - _sql: <<<_SQL - SELECT x.rid FROM tag t, tagxref x - WHERE x.tagid=t.tagid - AND t.tagname='wiki-%1$q' - ORDER BY mtime DESC LIMIT 1 _SQL, - load: function(name){ - return this._F.loadManifest( - this._F.db.selectValue(this._sql.applyFormat(name)) - ||| throw "No wiki such wiki page: "+name - ); - } - }; -}); DELETED bindings/s2/require.d/plugins/json-cached.s2 Index: bindings/s2/require.d/plugins/json-cached.s2 ================================================================== --- bindings/s2/require.d/plugins/json-cached.s2 +++ bindings/s2/require.d/plugins/json-cached.s2 @@ -1,4 +0,0 @@ -/** A clone of the json plugin, except that cacheIt is set to true. */ -const mod = requireS2.getPlugin('json').copyPropertiesTo({}); -mod.cacheIt = true; -return mod; DELETED bindings/s2/require.d/plugins/json.s2 Index: bindings/s2/require.d/plugins/json.s2 ================================================================== --- bindings/s2/require.d/plugins/json.s2 +++ bindings/s2/require.d/plugins/json.s2 @@ -1,21 +0,0 @@ -/** A require.s2 plugin which loads file content as JSON, - using s2.json.parseFile(). It does _not_ cache its - results. Use the json-cached plugin for the same - effect but with cached results. -*/ -return { - cacheIt: false /* - We _generally_ don't want these cached because - they are "probably" used only once in most cases. - - Use the 'json-cached' plugin if you want cached - JSON files. - */, - suffix: ['.json'], - prefix: // Append '/json' to all paths in the default plugin's path - ((const p = [], suffix = s2.io.dirSeparator+'json'), - requireS2.getPlugin('default').prefix.eachIndex(proc(v){ - p[] = v+suffix; - }), p), - load: proc(file){return s2.json.parseFile(file)} -}; DELETED bindings/s2/require.d/plugins/placeholder.s2 Index: bindings/s2/require.d/plugins/placeholder.s2 ================================================================== --- bindings/s2/require.d/plugins/placeholder.s2 +++ bindings/s2/require.d/plugins/placeholder.s2 @@ -1,6 +0,0 @@ -/* a placeholder. you'll know if you need it. */ -return { - isVirtual: true, - cacheIt: true, - load: proc(){ return {} } -}; DELETED bindings/s2/require.d/plugins/tmpl-compiled.s2 Index: bindings/s2/require.d/plugins/tmpl-compiled.s2 ================================================================== --- bindings/s2/require.d/plugins/tmpl-compiled.s2 +++ bindings/s2/require.d/plugins/tmpl-compiled.s2 @@ -1,12 +0,0 @@ -/** - Works like the tmpl plugin, but passes the file's contents - throughs s2.tmpl() before returning it. -*/ -return { - cacheIt: false, - // prefix: uses the defaults - suffix: ['.tmpl'], - load: proc(fn){ - return s2.tmpl(s2.Buffer.readFile(fn)) - } -}; DELETED bindings/s2/require.d/plugins/tmpl.s2 Index: bindings/s2/require.d/plugins/tmpl.s2 ================================================================== --- bindings/s2/require.d/plugins/tmpl.s2 +++ bindings/s2/require.d/plugins/tmpl.s2 @@ -1,10 +0,0 @@ -/** Loads file content as a buffer, intended - for use with .tmpl files (for s2.tmpl()). */ -return { - cacheIt: false, - // prefix: uses the defaults - suffix: ['.tmpl'], - load: function(name){ - return s2.Buffer.readFile(name); - } -}; DELETED bindings/s2/require.d/pubsub.s2 Index: bindings/s2/require.d/pubsub.s2 ================================================================== --- bindings/s2/require.d/pubsub.s2 +++ bindings/s2/require.d/pubsub.s2 @@ -1,117 +0,0 @@ -/** - A require.s2 module implementing a basic publish-subscriber - manager. - - Example usage: - - http://fossil.wanderinghorse.net/repos/cwal/index.cgi/finfo?name=s2/require.d/pubsub.test.s2 - - Returns a class, each instance of which manages its own list - of publishers and subscribers. -*/ -return { - __typename: 'PubSub', - prototype: undefined, - - /** - Constructor for use with the 'new' keyword. - - var pubber = new thisObj(); - assert pubber inherits thisObj; // this will hold - - Each instance maintains its own, independent list of - subscriptions. - */ - __new: proc(){ - this.reset(); - }, - - /** - Subscribes a callback to events published for a given key. - key may be of any value type. func must be-a Function. - - Returns a unique-per-subscription value (of an unspecified - type) which can be passed to unsub() to opt out of a - subscription. For a permanent subscription, simply ignore the - result value. - */ - sub:proc callee(key, func){ - affirm typeinfo(iscallable func) /* is Function-like */; - const m = (this.$map[key] ||| (this.$map[key]={prototype:null})), - i = enum{k:key}.k; - m[i] = func; - return i; - }, - - /** - Expects id to be a value returned by this.sub() and - unsubscribes a subscriber registered with that id. - - Returns this object. - */ - unsub: proc(id){ - affirm typeinfo(isunique id); - (const c = this.$map[id.value]) && unset c[id]; - return this; - }, - - /** - Publishes an event to all subscribers (if any) of the key - (event type) given as the first argument. - Important notes: - - a) Subscribers are notified in an UNSPECIFIED and (very) - POSSIBLY CHANGING order. - - b) Any arguments given after the event key are passed to each - subscriber callback function, in the order they are passed in - here. e.g. if this function is passed ('foo', 'bar', 1) then - each subscriber will be called with ('bar', 1). Pedantic - side-note: each callback gets its own copy of the arguments - array, to avoid unintended side-effects if a callback modifies - its argv. - - Returns this object (for lack of a better option). - - It propagates any exceptions thrown by a subscriber, and any - pending subscribers won't get called. - - Pedantic side-node: each subscriber gets its own copy of the - arguments array, so it's safe to change it or keep a reference - to it in the subscribers without affecting others. - */ - pub:proc(/*key, event args...*/){ - return this.pubWithThis(argv.shift(), undefined, @argv); - }, - - /** - A special case of pub() useful in certain code constellations. - - For each listener of event type e, its callback is called using - t as the callback's "this" and passing on all arguments after - the second. If t is undefined then each callback is its own - 'this' (as is conventional for s2). - - Returns this object. - - See pub() for more details. - */ - pubWithThis: proc(e,t/*...*/){ - const m = this.$map[e] ||| return this; - affirm typeinfo(isobject m); - argv.shift(2); - foreach(m=>k,f) f.apply(t?:f, argv.slice()); - return this; - }, - - /** - Removes all subscriptions for the given key or (if no arguments - are passed) all subscriptions for all keys. - - Returns this object (for lack of a better option). - */ - reset:proc(/*key*/){ - argv.# ? unset this.$map[argv.0] : this.$map = {prototype:null}; - return this; - } -}; DELETED bindings/s2/require.d/pubsub.test.s2 Index: bindings/s2/require.d/pubsub.test.s2 ================================================================== --- bindings/s2/require.d/pubsub.test.s2 +++ bindings/s2/require.d/pubsub.test.s2 @@ -1,55 +0,0 @@ -/* Short demo of the pubsub require.s2 module. */ -requireS2( - ['nocache!pubsub'], - proc(P){ - const p = new P(); - assert 'object' === typename p.$map /* testing internals */; - print(__FLC, 'pubsub:'); - foreach(p=>k,v) print('\t',k,v); - var counter = 0; - const id = p.sub('hi', proc(){ - ++counter; - print(__FLC,'hi handler 1',argv); - }); - const id2 = p.sub('bye', proc(){ - ++counter; - print(__FLC,'bye handler',argv); - }); - - const id3 = p.sub('hi', proc(){ - ++counter; - print(__FLC,'hi handler 2',argv); - }); - - print(__FLC, 'subscription IDs =',id, id2, id3); - print("Publishing events..."); - p.pub('hi',0, __FLC); - assert 2 === counter; - - p.pub('nope',1, __FLC); - assert 2 === counter; - - p.pub('bye', 2, __FLC); - assert 3 === counter; - - p.pub('hi',3, __FLC); - assert 5 === counter; - p.unsub(id); - p.pub('hi',4, __FLC); - assert 6 === counter; - p.pub('bye', 5, __FLC); - assert 7 === counter; - print(__FLC, 'done'); - - var p2 = new P(); - assert p2.$map; - assert p2.$map !== p.map; - assert p2 !== p; - assert !(p2 inherits p); - assert p2 inherits p.prototype; - assert p2.sub === p.sub; - - print(__FLC, 'really done'); - return p; - } -); DELETED bindings/s2/require.d/require.s2 Index: bindings/s2/require.d/require.s2 ================================================================== --- bindings/s2/require.d/require.s2 +++ bindings/s2/require.d/require.s2 @@ -1,559 +0,0 @@ -/** - Implements a basic script module loading API similar to - require.js (http://requirejs.org). - - In short, this module acts as a loader for arbitrary other content, - be it s2 script code, raw file content, or "virtual" (non-file) - content plugged in by the user. Its usage is modelled very much of - off requirejs, and it will look familiar to anyone who has used - that library. - - See the API docs in this module for more details. For even more - details (or other details, in any case), see this public Google - Doc: - - https://docs.google.com/document/d/14gRP4f-WWgWNS64KM_BI7YjQqBLl4WT3jIrmNmFefYU/view - - Example usage: - - Assume the files r1.s2 and r2.s2 live somewhere in this module's - search path and that both return an Object with a foo() method. - This is how we might import and use them: - - @code - const R = import('path/to/require.s2'); - - R(['r1', 'r2'], proc(r1, r2){ - r1.counter = r2.counter = 1; - print(__FLC,r1); - print(__FLC,r2); - r1.foo(); - r2.foo(); - ++r1.counter; - ++r2.counter; - }); - - R(['r2', 'r1'], proc(r2, r1){ - assert 2 === r2.counter; - assert 2 === r1.counter; - // ^^^ because imported script results are cached - }); - @endcode - -*/ -affirm s2.fs; -affirm s2.Buffer; -affirm typeinfo(isfunction s2.getenv); -affirm typeinfo(isfunction s2.fs.realpath); -affirm typeinfo(isfunction s2.Buffer.readFile); -const /* saves some var lookup time */ -s2 = s2, -realpath = s2.fs.realpath, -getenv = s2.getenv, -cliFlags = (s2.ARGV ? s2.ARGV.flags : 0) ||| {prototype:null} -; - -const importFileBuffer = s2.Buffer.readFile; -const importFileText = proc(fn) using(importFileBuffer) { - return importFileBuffer(fn).takeString(); -}; -/** - If a string-type CLI flag OR environment variable (in that - order) with the given name is found, this function returns an - array of its contents, tokenized using - s2.PathFinder.tokenizePath(). If no flag/environment variable - is found, or a CLI flag with a non-string value is found, - undefined is returned. - - Interpretation of the arguments is as follows: - - - If only 1 argument is provided, this routine looks for both a - CLI flag and environment var (in that order) with that name. - - - If 2 arguments are provided, the 1st is checked as a CLI flag - and the 2nd as an environment variable (in that order). -*/ -const pathFromEnv = proc(f,e=f){ - const p = F[f] ||| E(e); - return typeinfo(isstring p) - ? P.tokenizePath(p) - : undefined; -} using { - E: getenv, - P: s2.PathFinder, - F: cliFlags -}; - -/** - Internal utility to convert almost-URL-encoded plugin options - to some value form. Treats non-existent values (===undefined) - as a boolean true flag. -*/ -const convertQValue = proc(v){ - undefined === v && return true; - 'true' === v && return true; - 'false' === v && return false; - 'null' === v && return null; - return 0.parseNumber(v) ?: v; -}; - -/** - Module-internal cache of import() responses. -*/ -const modCache = new s2.Hash(0.nthPrime(15)); - -/** - The main "require" module object. -*/ -const mod = { - __typename: 'require.s2', - /** - Used for doing file lookups. Its search path and extensions - get continually swapped out by require() and friends to use - whatever search path the current context requires. - */ - pf: new s2.PathFinder(), - pathFromEnv, - /* Exposed for corner-case use by plugins. */ - modCache -}; - -/** - This "default" plugin (A) acts as the default - implementation for fetching require()'d files, (B) is - where the interface for other plugins is documented. -*/ -const PluginModel ={ - /** - Clear the prototype - we don't really need it for - this object. - */ - prototype: undefined, - /** If true, require() does not search for the filename it - is given. (It is assumed to be some form of virtual - unique identifier, and may be empty.) - */ - isVirtual: false, - - /** - cacheIt tells require whether or not to cache the results - of requests to this plugin. If it is true, then this.load() - will only be called for the first request, and its result - will be returned on subsequent requests. EXCEPTION: if - a plugin is called with arguments (see load()) the cache - is ignored/bypassed. - - The default is expected (by client code) to always be true - for the default plugin! - */ - cacheIt: true, - - /** - For file-based plugins, prefix specifies the search - directories (array of strings, excluding any trailing slash - on the directory parts unless they refer to the root - directory). - */ - prefix: pathFromEnv('s2.require.path', 'S2_REQUIRE_PATH') ||| ['.'], - /** - For file-based plugins, suffix specifies the search - extensions (array of strings, including any '.' part, - e.g. use ".foo" instead of "foo" unless you're searching - for partial extensions). - */ - suffix: pathFromEnv('s2.require.extensions', 'S2_REQUIRE_EXTENSIONS') ||| ['.s2'], - - /** - Called by require() to "load" a given module. load() must - accept a module name, "load" that module, for a given - definition of "load", and return its contents or - result. (It need not actually load anything from anywhere, - much less a file: it might simply return a predefined - object or other value.) - - In the call context, 'this' will be this plugin - configuration object. - - The first argument is the "name" part of the string passed - to require. - - The second argument part is either undefined or an object: - if the file string contains '?', anything after the ? is - assumed to be encoded in the form a=b&c=d... (URL-like, but - _without_ the URL encoding), and that gets transformed into - an Object of key/value pairs before passing them to this - function (the default value, if no "=" is provided, is boolean - true). HOWEVER: passing any arguments after '?' will - cause caching to be bypassed, because the arguments presumably - change how the plugin works. Note that if nothing follows - the '?' then no options object is created. - - Caveats regarding the opt parameter: - - 1) all of the values in the opt object will originally be - of type string, but numeric-looking strings and the strings - ("true", "false", "null") get converted to their - script-native type. - - 2) Only one level of opt object is supported, not nested - objects, arrays, etc. - */ - load: proc(f/*, opt*/) using({ - b: s2.Buffer.readFile, - r: mod - }) { - return b(f).evalContents(f,{requireS2: r}); - } - /* Reminder to self: we cannot simply alias to s2.import() because - any extra non-string arguments would be passed to it (doing the - wrong thing). Reminder #2: adding comments outside of function - bodies, instead of if them, uses less memory, since they don't - get allocated as part of the function body. That's especially - significant when the length of the comments outweigh the rest - of the source, as in this case. */ -}; - -/** - The funkiness we do with 'this' vs 'mod' in may places places - below is so that this stuff still works when clients copy the - returned module into a property of another object. i.e. the following - usages are equivalent: - - var x = mod; - obj.x = mod; - x([...],...); - obj.x([...],...); - - Both calls (because of these extra bindings) use the same "this" - inside the call (the local 'mod' symbol), which is important for - identical/correct semantics in both uses. -*/ - -/** - All the plugins are stored in this object, and any number may - potentially be loaded (and added here) via client-side use. -*/ -mod.plugins = { - /** - The name 'default' is magic and assumes certain - plugin-level defaults. It also provides the default .prefix - and .suffix properties for other non-isVirtual (file-using) - plugins (used only if a given plugin does not define them - itself). - */ - default: PluginModel, - /** - Works just like the default plugin but bypasses - the cache. - */ - nocache: { - cacheIt: false, - load: PluginModel.load - }, - /** Loads file content as a string. */ - text: { - cacheIt: false, - // prefix: uses the defaults - suffix: ['.txt', '.s2', '.html'], - load: importFileText - }, - /** Loads file content as a buffer. */ - buffer: { - cacheIt: false, - // prefix: uses the defaults - suffix: ['.txt', '.s2', '.html'], - load: importFileBuffer - } - - /** - Demonstration of a "virtual" plugin (one which does not - use files). - - virtualDemo:{ - isVirtual: true, - cacheIt: true, // not strictly necessary, plugin-dependent - load: proc f(fn,opt){ - print("Example of a 'virtual' handler. Arguments:",argv); - return opt ||| this; - } - } - */ - -}; - -/** - Installs one or more plugins into this object. - - If called with an initial string arugment then: - - - Adds a new plugin. name must be a string and pluginObj must - be an object which follows the PluginModel interface. Returns - pluginObj on success. - - If called with a non-string argument then: - - - name is assumed to be a container. Each of its properties is - assumed to be a module definition, and each one gets installed - via a call back into this function, passing it each key and - value. Returns this object. - - Throws on error. -*/ -mod.addPlugin = proc callee(name, pluginObj){ - if(name && !pluginObj && typeinfo(iscontainer name)){ - foreach(name=>k,v) callee(k,v); - return this; - } - affirm 'string' === typeinfo(name name); - affirm name /* name must be non-empty */; - affirm typeinfo(iscontainer pluginObj) && typeinfo(iscallable pluginObj.load); - mod.plugins[name] = pluginObj; - return mod; -} using(mod); - -/** - Searches for a plugin script by using this.plugins.default's - search path and the name ("plugins/"+name). Returns undefined - if not found, else the result of s2.import()'ing that file. It - does not check if the plugin is already installed, but installs - (or overwrites) it into mod.plugins[name]. -*/ -mod.searchAndInstallPlugin = proc(name){ - mod.pf.prefix = mod.plugins.default.prefix; - mod.pf.suffix = mod.plugins.default.suffix; - const fn = mod.pf.search('plugins/'+name); - return fn - ? mod.plugins[name] = ((const requireS2=mod), import(false,R(fn))) - : undefined; -} using {mod, R: realpath}, - -/** - If the given plugin name is already installed, it is returned, - otherwise it is sought for, installed, and returned. An - exception is thrown if it cannot be found or if installing it - fails. -*/ -mod.getPlugin = proc(name) using(mod) { - return mod.plugins[name] ||| mod.searchAndInstallPlugin(name); -}, - -/** - Attempts to resolve a file name using a given plugin's search - path. basename is the unresolved name of the file to search for - and forPlugin is either a plugin object or the name of a - plugin. The search path/extensions use are those of the given - plugin or (if that plugin has none), the default plugin. If the - given plugin has the isVirtual flag, no search is performed and - the undefined value is returned. -*/ -mod.resolveFilename = proc(basename,forPlugin='default') using(mod){ - const pConf = typeinfo(isobject forPlugin) ? forPlugin : mod.getPlugin(forPlugin); - affirm typeinfo(isobject pConf); - const pf = (pConf.isVirtual ? undefined : mod.pf) ||| return; - pf.prefix = pConf.prefix ||| mod.plugins.default.prefix; - pf.suffix = pConf.suffix ||| mod.plugins.default.suffix; - return pf.search(basename,0); -}; - -/** - Given a base filename... - - 1) If the given name does not contain a '!' character, - it searches for an exact-match name in the cache. If it - finds one, it returns that value. - - 2) It searches for the file using the configured search - paths/extensions (from this.plugins). If found, it is passed to - the import() function specified for the import type (see - below). - - By default this.plugins.default is used to search for and - import the file. If a "special" type of name is provided to - this function, though (meaning the base name looks like - with "SOMETHING!basename"), then this.plugins[SOMETHING] is - used (if set), which may change the caching behaviour and - how the content of the file is interpreted. - - Depending on the configuration options, requests might get - cached. Subsequent calls which expand to the same file name - will return the same (cached) result value on each - subsequent call. -*/ -mod.import = proc(basename){ - affirm 'string' === typeinfo(name basename); - if(basename.indexOf('!')<0 && const c = C#basename){ - return c; - } - var pluginName; - /* Check for pluginName!... */ - if((var ndx = basename.indexOf('!'))>0){ - pluginName = basename.substr(0,ndx); - basename = basename.substr(ndx+1); - } - pluginName || (pluginName ='default'); - /* Configuration for this plugin... */ - const pConf = mod.getPlugin(pluginName); - pConf || throw "Could not load plugin '"+pluginName+"'."; - const pf = pConf.isVirtual ? undefined : mod.pf /* PathFinder */; - if(pf){ - // Set up/reset file lookup paths/suffixes... - pf.prefix = pConf.prefix ||| mod.plugins.default.prefix; - pf.suffix = pConf.suffix ||| mod.plugins.default.suffix; - } - /* Treat ?a=b&c=d... almost like a URL-encoded - as query string, but without the URL encoding. - */ - var qArgs, useCache = pConf.cacheIt; - if(basename.indexOf('?')>=0){ - /* Parse args. If any are provided, bypass the cache. */ - const sp = basename.split('?',2); - basename = sp.0; - (qArgs = Q(sp.1)) && (useCache = false); - } - /* Find the file, if necessary... */ - var fn = pConf.isVirtual - ? basename - : (basename ? pf.search(basename, 0) : false) - ||| throw "Plugin '%1$s' cannot find '%2$s' in search path %3$J with extensions %4$J.". - applyFormat(pluginName, basename, pf.prefix, pf.suffix); - // expand to the fully qualified path for non-virtual plugins... - pConf.isVirtual || (fn = P(fn)); - //print(__FLC,"fn=",fn,"useCache=",useCache,"qArgs =",qArgs,pluginName,pConf); - //print(__FLC,'pConf.cacheIt=',pConf.cacheIt,', fn=',fn); - //print(__FLC,cache.toJSONString(2)); - const requireS2 = mod /* public API symbol, potentially - needed by anything which - uses anyPlugin.load() */; - return useCache - ? (const k = - (pluginName ? pluginName+'!'+fn : fn), - cacheCheck = (C # k)) - ? cacheCheck - : C.insert(k, pConf.load(fn, qArgs)) - : pConf.load(fn, qArgs); -} using(mod, { - C: modCache, - P: realpath, - Q: proc(str) using {q:convertQValue} { - str || return str; - var r; - foreach(@str.split('&')=>v){ - const s = v.split('=',2); - (r ||| (r = {prototype:null}))[s.0] = s.1 ? q(s.1) : (v.indexOf('=')>0 ? s.1 : true); - }; - return r; - } -}); - -/** - For each base filename in the given array/tuple, this function - calls this.import(basename) and appends the results to a new - array. Returns an array of the results to each import() call, - the order of the array's elements being the same as the calls - to import(). Throws if list is not an array or is empty or if - loading any resources fails. Propagates any exceptions. -*/ -mod.importList = function(list) using(mod) { - affirm typeinfo(islist list) /* expecting an Array or Tuple */; - affirm !list.isEmpty() /* expecting a non-empty list of module names */; - const imps = []; - foreach(@list=>v) imps.push(mod.import(v)); - return imps; -}; - -/** - Imports a list of script modules and optionally calls a - callback after loading all of them. - - list is an array of strings - script file base names to import. - func is an optional function or code string which gets called - resp. eval'd after importing all of the scripts. If it's a - function, it is passed one argument for each entry in the list, - in the same order they are imported. If it is a code string - then it is eval'd in a scope with the array 'modules' defined - to the resolved list of modules. - - In either case, it declares the const symbol requireS2 to be - the require() module, so that these callbacks may recursively - invoke require() via a call to requireS2(). (It is often useful - to do so, it turns out, and this consolidates the convention - across modules originally written for different code bases.) - - Returns the result of calling the function (if any) or the - array of loaded modules (if passed no function/string). - - Example: - - assert 42 === thisObj( - ['module1', 'module2'], - function(mod1, mod2){ - print('result of module1 import:' mod1); - print('result of module2 import:' mod2); - return 42; - } - ); - - - _Potential_ TODOs: - - - If passed a non-string value where a name is expected, use it - as-is as the result of the loading. i suspect we might have - some interesting uses for that, but want a use case before trying - it out. -*/ -mod.require = function(list, func) using(mod) { - func && affirm (typeinfo(iscallable func) || typeinfo(isstring func)); - list = mod.importList(list); - func || return list; - if(typeinfo(iscallable func)){ - const requireS2=mod, s2 = s2; - return func.apply(func, list); - }else{ - return func.evalContents('require() script',{ - s2: s2, - requireS2: mod, - modules: list - }); - } -}; - -/** - Installs a cached entry for the given module name, such that - future calls to import() or require() which use that exact name - will return the given result object. Note that the name need - not be filesystem-friendly. e.g. "" is perfectly - legal. The only limitation is that it "really should not" - contain an exclamation point, as that may confuse import() - because that character is used to denote a plugin. -*/ -mod.installModule = function(name, result) using(modCache){ - modCache.insert(name, result); - return this; -}; - - -// Try to determine some useful directories to search for scripts in... -if(typeinfo(isstring var d = cliFlags['s2.require.home'])){ - mod.home = realpath(d); - mod.plugins.default.prefix.push( mod.home ); -} -else if((d=(cliFlags['s2.home']|||getenv('S2_HOME'))) - && (d=realpath(d+'/require.d'))){ - mod.plugins.default.prefix.push( mod.home = d ); -} -if((var d = __FILEDIR ? realpath(__FILEDIR) : 0) - && (mod.home !== d)){ - mod.plugins.default.prefix.push( d ); - mod.home || (mod.home = d); -} -/* __FILEDIR may be a synthetic __FILE name - (e.g. via eval or Buffer.evalContents()) */ -mod.home || (mod.home=""); - -/* We set mod.require.home so that plugins can construct paths via - requireS2.home. */ - -/* Make it so that call()ing this object calls mod.require() */ -mod.prototype = mod.require /** Holy cow! We've just inherited our - own member function. */; -mod /* script result */; DELETED bindings/s2/require.d/require.test.s2 Index: bindings/s2/require.d/require.test.s2 ================================================================== --- bindings/s2/require.d/require.test.s2 +++ bindings/s2/require.d/require.test.s2 @@ -1,50 +0,0 @@ - -affirm requireS2; -affirm 'string' === typename requireS2.home; -affirm requireS2.home.indexOf('/')>-1 || requireS2.home.indexOf('\\')>-1; -//print(__FLC,'requireS2.home =',requireS2.home); -/** - A quite incomplete test of require.s2. Since this cannot even - be loaded by require.s2 if require.s2 isn't at least basically - working, just getting here already tells us much. -*/ -requireS2.addPlugin('dummy',{ - isVirtual: true, - cacheIt: false, - load: proc(n,opt){ - return { - name: n, - opt: opt - }; - } -}); - -requireS2.addPlugin({ - dummy2:{ - isVirtual: true, - chachIt: true, - load: proc(n,opt){ - return 1; - } - }, - dummy3:{ - isVirtual: true, - chachIt: true, - load: proc(n,opt){ - return 0; - } - } -}); - -requireS2(['dummy!fred?a=1'],proc(obj){ - affirm 'fred' === obj.name; - affirm 1 === obj.opt.a; - - assert true === requireS2(['dummy!barny', 'dummy2!', 'dummy3!'],proc(d, d2, d3){ - assert 'barny' === d.name; - assert !d.opt; - assert 1 === d2; - assert 0 === d3; - return true; - }); -}); DELETED bindings/s2/require.d/time.s2 Index: bindings/s2/require.d/time.s2 ================================================================== --- bindings/s2/require.d/time.s2 +++ bindings/s2/require.d/time.s2 @@ -1,5 +0,0 @@ -/* a require.s2 module which "hides" s2.time via the module - interface. It could optionally load 'dll!mod_time', but that's - an internal impl detail. -*/ -return s2.time; DELETED bindings/s2/require.d/tmpl.s2 Index: bindings/s2/require.d/tmpl.s2 ================================================================== --- bindings/s2/require.d/tmpl.s2 +++ bindings/s2/require.d/tmpl.s2 @@ -1,60 +0,0 @@ -/** - The tmpl module (as distinct from the tmpl plugin!) provides - utilities for working with s2.tmpl(). - -*/ -return { - /** - "Processes" a s2.tmpl() template as follows... - - The first argument is a tmpl()-compiled script (of type Buffer) - or a non-compiled script of type String (in which case this - function compiles it and uses the original value as the 3rd - parameter). - - The second parameter is an optional container holding key/value - pairs which get imported into the current scope before - evaluating the script. This allows one to easily create - mini-templates for use in loops and such. If you _have_ - to pass a value but don't have an object, any falsy value - will do. - - The final argument is intended to hold the uncompiled script - and is only used in error reporting, and is stored in any - exception propagated via evaluating a template. It is ignored - when the first argument has a typename of 'string'. - */ - process: proc(template, opt, tmplUncompiled){ - if(typeinfo(isstring template)){ - tmplUncompiled = template; - template = this.compile(template); - } - affirm typeinfo(isbuffer template); - if(const ex = catch template.evalContents(opt|||{})){ - ex && throw { - message: "Error evaluating compiled template (location info is relative to the compiled script).", - exception: ex, - template: { - compiled: template.toString(), - uncompiled: tmplUncompiled ? tmplUncompiled.toString() : undefined - } - }; - } - }, - - processFile: proc(fn, opt){ - return this.process( this.load(fn, true), opt ); - }, - - /** - A proxy for s2.tmpl(). - */ - compile: s2.tmpl, - /** - Uses the tmpl! plugin to load the given file - and optionally compile it using this.compile() - */ - load: proc(fn, compile){ - return requireS2([(compile ? 'tmpl-compiled!' : 'tmpl!')+fn]).0; - } using (requireS2) -}; DELETED bindings/s2/require.d/tmpl.test.s2 Index: bindings/s2/require.d/tmpl.test.s2 ================================================================== --- bindings/s2/require.d/tmpl.test.s2 +++ bindings/s2/require.d/tmpl.test.s2 @@ -1,42 +0,0 @@ -/* A brief test of the 'tmpl' require.s2 module. */ -requireS2( -['ob', - 'nocache!tmpl' - // bug: ^^^^ reading a directory (by accident) is reported as OOM. - // Fixed by not allowing PathFinder to resolve directory names, but - // that support is missing for Windows (patches welcomed). -], -proc(ob, t){ - const src = 'a=<% a %>, b=<%b%>, c=<%c%>\n'; - const obLevel = ob.level(); - var str; - ob.push(); - var ex = catch{ - t.process(src,{ - a:'hi', - b:'there', - c:'world' - }); - str = ob.pop(-1); - }; - while(ob.level()>obLevel) ob.pop(); - ex && throw ex; - affirm "a=hi, b=there, c=world\n" === str; - - scope { - const src2 = '<%a%>,<%b%>,<%c%>\n', - compiled = t.compile(src2), - opt = { a:-1, b:0, c:1 } - ,TMPLOUT = proc(){} /* eval'd template uses this func, if defined */ - ; - const XYZ = 'hi'; - t.process('<% XYZ %>\n', null); - for(var i = 0; i < 5; ++i, ++opt.a, ++opt.b, ++opt.c ){ - t.process(compiled, opt, src); - } - affirm 4==opt.a; - affirm 5==opt.b; - affirm 6==opt.c; - }; - print(__FLC,'done!'); -}); DELETED bindings/s2/s2_amalgamation.c Index: bindings/s2/s2_amalgamation.c ================================================================== --- bindings/s2/s2_amalgamation.c +++ bindings/s2/s2_amalgamation.c @@ -1,62674 +0,0 @@ -#include "s2_amalgamation.h" -#if S2_INTERNAL_MINIZ -/* start of file miniz-sgb.c */ -#undef MINIZ_NO_STDIO -#define MINIZ_NO_STDIO -#undef MINIZ_NO_ARCHIVE_APIS -#define MINIZ_NO_ARCHIVE_APIS -/* miniz.c 2.0.6 beta - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing - See "unlicense" statement at the end of this file. - Rich Geldreich , last updated Oct. 13, 2013 - Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt - - Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define - MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). - - * Low-level Deflate/Inflate implementation notes: - - Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or - greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses - approximately as well as zlib. - - Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function - coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory - block large enough to hold the entire file. - - The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. - - * zlib-style API notes: - - miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in - zlib replacement in many apps: - The z_stream struct, optional memory allocation callbacks - deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound - inflateInit/inflateInit2/inflate/inflateEnd - compress, compress2, compressBound, uncompress - CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. - Supports raw deflate streams or standard zlib streams with adler-32 checking. - - Limitations: - The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. - I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but - there are no guarantees that miniz.c pulls this off perfectly. - - * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by - Alex Evans. Supports 1-4 bytes/pixel images. - - * ZIP archive API notes: - - The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to - get the job done with minimal fuss. There are simple API's to retrieve file information, read files from - existing archives, create new archives, append new files to existing archives, or clone archive data from - one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), - or you can specify custom file read/write callbacks. - - - Archive reading: Just call this function to read a single file from a disk archive: - - void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, - size_t *pSize, mz_uint zip_flags); - - For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central - directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - - - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: - - int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); - - The locate operation can optionally check file comments too, which (as one example) can be used to identify - multiple versions of the same file in an archive. This function uses a simple linear search through the central - directory, so it's not very fast. - - Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and - retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - - - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data - to disk and builds an exact image of the central directory in memory. The central directory image is written - all at once at the end of the archive file when the archive is finalized. - - The archive writer can optionally align each file's local header and file data to any power of 2 alignment, - which can be useful when the archive will be read from optical media. Also, the writer supports placing - arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still - readable by any ZIP tool. - - - Archive appending: The simple way to add a single file to an archive is to call this function: - - mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, - const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); - - The archive will be created if it doesn't already exist, otherwise it'll be appended to. - Note the appending is done in-place and is not an atomic operation, so if something goes wrong - during the operation it's possible the archive could be left without a central directory (although the local - file headers and file data will be fine, so the archive will be recoverable). - - For more complex archive modification scenarios: - 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to - preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the - compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and - you're done. This is safe but requires a bunch of temporary disk space or heap memory. - - 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), - append new files as needed, then finalize the archive which will write an updated central directory to the - original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a - possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - - - ZIP archive support limitations: - No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. - Requires streams capable of seeking. - - * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the - below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. - - * Important: For best perf. be sure to customize the below macros for your target platform: - #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 - #define MINIZ_LITTLE_ENDIAN 1 - #define MINIZ_HAS_64BIT_REGISTERS 1 - - * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz - uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files - (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). -*/ - - - - - -/* Defines to completely disable specific portions of miniz.c: - If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ - -/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ -/*#define MINIZ_NO_STDIO */ - -/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ -/* get/set file times, and the C run-time funcs that get/set times won't be called. */ -/* The current downside is the times written to your archives will be from 1979. */ -/*#define MINIZ_NO_TIME */ - -/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ -/*#define MINIZ_NO_ARCHIVE_APIS */ - -/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ -/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ - -/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ -/*#define MINIZ_NO_ZLIB_APIS */ - -/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ -/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ - -/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. - Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc - callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user - functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ -/*#define MINIZ_NO_MALLOC */ - -#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) -/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ -#define MINIZ_NO_TIME -#endif - -#include - -#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) -#include -#endif - -#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) -/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ -#define MINIZ_X86_OR_X64_CPU 1 -#else -#define MINIZ_X86_OR_X64_CPU 0 -#endif - -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU -/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ -#define MINIZ_LITTLE_ENDIAN 1 -#else -#define MINIZ_LITTLE_ENDIAN 0 -#endif - -#if MINIZ_X86_OR_X64_CPU -/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ -#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 -#else -#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 -#endif - -#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) -/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ -#define MINIZ_HAS_64BIT_REGISTERS 1 -#else -#define MINIZ_HAS_64BIT_REGISTERS 0 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- zlib-style API Definitions. */ - -/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ -typedef unsigned long mz_ulong; - -/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ -void mz_free(void *p); - -#define MZ_ADLER32_INIT (1) -/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); - -#define MZ_CRC32_INIT (0) -/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ -mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); - -/* Compression strategies. */ -enum -{ - MZ_DEFAULT_STRATEGY = 0, - MZ_FILTERED = 1, - MZ_HUFFMAN_ONLY = 2, - MZ_RLE = 3, - MZ_FIXED = 4 -}; - -/* Method */ -#define MZ_DEFLATED 8 - -/* Heap allocation callbacks. -Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ -typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); -typedef void (*mz_free_func)(void *opaque, void *address); -typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); - -/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ -enum -{ - MZ_NO_COMPRESSION = 0, - MZ_BEST_SPEED = 1, - MZ_BEST_COMPRESSION = 9, - MZ_UBER_COMPRESSION = 10, - MZ_DEFAULT_LEVEL = 6, - MZ_DEFAULT_COMPRESSION = -1 -}; - -#define MZ_VERSION "10.0.1" -#define MZ_VERNUM 0xA010 -#define MZ_VER_MAJOR 10 -#define MZ_VER_MINOR 0 -#define MZ_VER_REVISION 1 -#define MZ_VER_SUBREVISION 0 - -#ifndef MINIZ_NO_ZLIB_APIS - -/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ -enum -{ - MZ_NO_FLUSH = 0, - MZ_PARTIAL_FLUSH = 1, - MZ_SYNC_FLUSH = 2, - MZ_FULL_FLUSH = 3, - MZ_FINISH = 4, - MZ_BLOCK = 5 -}; - -/* Return status codes. MZ_PARAM_ERROR is non-standard. */ -enum -{ - MZ_OK = 0, - MZ_STREAM_END = 1, - MZ_NEED_DICT = 2, - MZ_ERRNO = -1, - MZ_STREAM_ERROR = -2, - MZ_DATA_ERROR = -3, - MZ_MEM_ERROR = -4, - MZ_BUF_ERROR = -5, - MZ_VERSION_ERROR = -6, - MZ_PARAM_ERROR = -10000 -}; - -/* Window bits */ -#define MZ_DEFAULT_WINDOW_BITS 15 - -struct mz_internal_state; - -/* Compression/decompression stream struct. */ -typedef struct mz_stream_s -{ - const unsigned char *next_in; /* pointer to next byte to read */ - unsigned int avail_in; /* number of bytes available at next_in */ - mz_ulong total_in; /* total number of bytes consumed so far */ - - unsigned char *next_out; /* pointer to next byte to write */ - unsigned int avail_out; /* number of bytes that can be written to next_out */ - mz_ulong total_out; /* total number of bytes produced so far */ - - char *msg; /* error msg (unused) */ - struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ - - mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ - mz_free_func zfree; /* optional heap free function (defaults to free) */ - void *opaque; /* heap alloc function user pointer */ - - int data_type; /* data_type (unused) */ - mz_ulong adler; /* adler32 of the source or uncompressed data */ - mz_ulong reserved; /* not used */ -} mz_stream; - -typedef mz_stream *mz_streamp; - -/* Returns the version string of miniz.c. */ -const char *mz_version(void); - -/* mz_deflateInit() initializes a compressor with default options: */ -/* Parameters: */ -/* pStream must point to an initialized mz_stream struct. */ -/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ -/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ -/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ -/* Return values: */ -/* MZ_OK on success. */ -/* MZ_STREAM_ERROR if the stream is bogus. */ -/* MZ_PARAM_ERROR if the input parameters are bogus. */ -/* MZ_MEM_ERROR on out of memory. */ -int mz_deflateInit(mz_streamp pStream, int level); - -/* mz_deflateInit2() is like mz_deflate(), except with more control: */ -/* Additional parameters: */ -/* method must be MZ_DEFLATED */ -/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ -/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); - -/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ -int mz_deflateReset(mz_streamp pStream); - -/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ -/* Parameters: */ -/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ -/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ -/* Return values: */ -/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ -/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ -/* MZ_STREAM_ERROR if the stream is bogus. */ -/* MZ_PARAM_ERROR if one of the parameters is invalid. */ -/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ -int mz_deflate(mz_streamp pStream, int flush); - -/* mz_deflateEnd() deinitializes a compressor: */ -/* Return values: */ -/* MZ_OK on success. */ -/* MZ_STREAM_ERROR if the stream is bogus. */ -int mz_deflateEnd(mz_streamp pStream); - -/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); - -/* Single-call compression functions mz_compress() and mz_compress2(): */ -/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); - -/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ -mz_ulong mz_compressBound(mz_ulong source_len); - -/* Initializes a decompressor. */ -int mz_inflateInit(mz_streamp pStream); - -/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ -/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ -int mz_inflateInit2(mz_streamp pStream, int window_bits); - -/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ -/* Parameters: */ -/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ -/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ -/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ -/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ -/* Return values: */ -/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ -/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ -/* MZ_STREAM_ERROR if the stream is bogus. */ -/* MZ_DATA_ERROR if the deflate stream is invalid. */ -/* MZ_PARAM_ERROR if one of the parameters is invalid. */ -/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ -/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ -int mz_inflate(mz_streamp pStream, int flush); - -/* Deinitializes a decompressor. */ -int mz_inflateEnd(mz_streamp pStream); - -/* Single-call decompression. */ -/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); - -/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ -const char *mz_error(int err); - -/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ -/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ -#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES -typedef unsigned char Byte; -typedef unsigned int uInt; -typedef mz_ulong uLong; -typedef Byte Bytef; -typedef uInt uIntf; -typedef char charf; -typedef int intf; -typedef void *voidpf; -typedef uLong uLongf; -typedef void *voidp; -typedef void *const voidpc; -#define Z_NULL 0 -#define Z_NO_FLUSH MZ_NO_FLUSH -#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH -#define Z_SYNC_FLUSH MZ_SYNC_FLUSH -#define Z_FULL_FLUSH MZ_FULL_FLUSH -#define Z_FINISH MZ_FINISH -#define Z_BLOCK MZ_BLOCK -#define Z_OK MZ_OK -#define Z_STREAM_END MZ_STREAM_END -#define Z_NEED_DICT MZ_NEED_DICT -#define Z_ERRNO MZ_ERRNO -#define Z_STREAM_ERROR MZ_STREAM_ERROR -#define Z_DATA_ERROR MZ_DATA_ERROR -#define Z_MEM_ERROR MZ_MEM_ERROR -#define Z_BUF_ERROR MZ_BUF_ERROR -#define Z_VERSION_ERROR MZ_VERSION_ERROR -#define Z_PARAM_ERROR MZ_PARAM_ERROR -#define Z_NO_COMPRESSION MZ_NO_COMPRESSION -#define Z_BEST_SPEED MZ_BEST_SPEED -#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION -#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION -#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY -#define Z_FILTERED MZ_FILTERED -#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY -#define Z_RLE MZ_RLE -#define Z_FIXED MZ_FIXED -#define Z_DEFLATED MZ_DEFLATED -#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS -#define alloc_func mz_alloc_func -#define free_func mz_free_func -#define internal_state mz_internal_state -#define z_stream mz_stream -#define deflateInit mz_deflateInit -#define deflateInit2 mz_deflateInit2 -#define deflateReset mz_deflateReset -#define deflate mz_deflate -#define deflateEnd mz_deflateEnd -#define deflateBound mz_deflateBound -#define compress mz_compress -#define compress2 mz_compress2 -#define compressBound mz_compressBound -#define inflateInit mz_inflateInit -#define inflateInit2 mz_inflateInit2 -#define inflate mz_inflate -#define inflateEnd mz_inflateEnd -#define uncompress mz_uncompress -#define crc32 mz_crc32 -#define adler32 mz_adler32 -#define MAX_WBITS 15 -#define MAX_MEM_LEVEL 9 -#define zError mz_error -#define ZLIB_VERSION MZ_VERSION -#define ZLIB_VERNUM MZ_VERNUM -#define ZLIB_VER_MAJOR MZ_VER_MAJOR -#define ZLIB_VER_MINOR MZ_VER_MINOR -#define ZLIB_VER_REVISION MZ_VER_REVISION -#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION -#define zlibVersion mz_version -#define zlib_version mz_version() -#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ - -#endif /* MINIZ_NO_ZLIB_APIS */ - -#ifdef __cplusplus -} -#endif -#include -#include -#include -#include - -/* ------------------- Types and macros */ -typedef unsigned char mz_uint8; -typedef signed short mz_int16; -typedef unsigned short mz_uint16; -typedef unsigned int mz_uint32; -typedef unsigned int mz_uint; -typedef int64_t mz_int64; -typedef uint64_t mz_uint64; -typedef int mz_bool; - -#define MZ_FALSE (0) -#define MZ_TRUE (1) - -/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ -#ifdef _MSC_VER -#define MZ_MACRO_END while (0, 0) -#else -#define MZ_MACRO_END while (0) -#endif - -#ifdef MINIZ_NO_STDIO -#define MZ_FILE void * -#else -#include -#define MZ_FILE FILE -#endif /* #ifdef MINIZ_NO_STDIO */ - -#ifdef MINIZ_NO_TIME -typedef struct mz_dummy_time_t_tag -{ - int m_dummy; -} mz_dummy_time_t; -#define MZ_TIME_T mz_dummy_time_t -#else -#define MZ_TIME_T time_t -#endif - -#define MZ_ASSERT(x) assert(x) - -#ifdef MINIZ_NO_MALLOC -#define MZ_MALLOC(x) NULL -#define MZ_FREE(x) (void)x, ((void)0) -#define MZ_REALLOC(p, x) NULL -#else -#define MZ_MALLOC(x) malloc(x) -#define MZ_FREE(x) free(x) -#define MZ_REALLOC(p, x) realloc(p, x) -#endif - -#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) -#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN -#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) -#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) -#else -#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) -#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) -#endif - -#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) - -#ifdef _MSC_VER -#define MZ_FORCEINLINE __forceinline -#elif defined(__GNUC__) -#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) -#else -#define MZ_FORCEINLINE inline -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); -extern void miniz_def_free_func(void *opaque, void *address); -extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); - -#define MZ_UINT16_MAX (0xFFFFU) -#define MZ_UINT32_MAX (0xFFFFFFFFU) - -#ifdef __cplusplus -} -#endif - - -#ifdef __cplusplus -extern "C" { -#endif -/* ------------------- Low-level Compression API Definitions */ - -/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ -#define TDEFL_LESS_MEMORY 0 - -/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ -/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ -enum -{ - TDEFL_HUFFMAN_ONLY = 0, - TDEFL_DEFAULT_MAX_PROBES = 128, - TDEFL_MAX_PROBES_MASK = 0xFFF -}; - -/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ -/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ -/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ -/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ -/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ -/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ -/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ -/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ -/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ -enum -{ - TDEFL_WRITE_ZLIB_HEADER = 0x01000, - TDEFL_COMPUTE_ADLER32 = 0x02000, - TDEFL_GREEDY_PARSING_FLAG = 0x04000, - TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, - TDEFL_RLE_MATCHES = 0x10000, - TDEFL_FILTER_MATCHES = 0x20000, - TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, - TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 -}; - -/* High level compression functions: */ -/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ -/* On entry: */ -/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ -/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ -/* On return: */ -/* Function returns a pointer to the compressed data, or NULL on failure. */ -/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ -/* The caller must free() the returned block when it's no longer needed. */ -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); - -/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ -/* Returns 0 on failure. */ -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); - -/* Compresses an image to a compressed PNG file in memory. */ -/* On entry: */ -/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ -/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ -/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ -/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ -/* On return: */ -/* Function returns a pointer to the compressed data, or NULL on failure. */ -/* *pLen_out will be set to the size of the PNG image file. */ -/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); - -/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ -typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); - -/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); - -enum -{ - TDEFL_MAX_HUFF_TABLES = 3, - TDEFL_MAX_HUFF_SYMBOLS_0 = 288, - TDEFL_MAX_HUFF_SYMBOLS_1 = 32, - TDEFL_MAX_HUFF_SYMBOLS_2 = 19, - TDEFL_LZ_DICT_SIZE = 32768, - TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, - TDEFL_MIN_MATCH_LEN = 3, - TDEFL_MAX_MATCH_LEN = 258 -}; - -/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ -#if TDEFL_LESS_MEMORY -enum -{ - TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, - TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, - TDEFL_MAX_HUFF_SYMBOLS = 288, - TDEFL_LZ_HASH_BITS = 12, - TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, - TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, - TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS -}; -#else -enum -{ - TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, - TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, - TDEFL_MAX_HUFF_SYMBOLS = 288, - TDEFL_LZ_HASH_BITS = 15, - TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, - TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, - TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS -}; -#endif - -/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ -typedef enum { - TDEFL_STATUS_BAD_PARAM = -2, - TDEFL_STATUS_PUT_BUF_FAILED = -1, - TDEFL_STATUS_OKAY = 0, - TDEFL_STATUS_DONE = 1 -} tdefl_status; - -/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ -typedef enum { - TDEFL_NO_FLUSH = 0, - TDEFL_SYNC_FLUSH = 2, - TDEFL_FULL_FLUSH = 3, - TDEFL_FINISH = 4 -} tdefl_flush; - -/* tdefl's compression state structure. */ -typedef struct -{ - tdefl_put_buf_func_ptr m_pPut_buf_func; - void *m_pPut_buf_user; - mz_uint m_flags, m_max_probes[2]; - int m_greedy_parsing; - mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; - mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; - mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; - mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; - tdefl_status m_prev_return_status; - const void *m_pIn_buf; - void *m_pOut_buf; - size_t *m_pIn_buf_size, *m_pOut_buf_size; - tdefl_flush m_flush; - const mz_uint8 *m_pSrc; - size_t m_src_buf_left, m_out_buf_ofs; - mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; - mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; - mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; - mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; - mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; -} tdefl_compressor; - -/* Initializes the compressor. */ -/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ -/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ -/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ -/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); - -/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); - -/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ -/* tdefl_compress_buffer() always consumes the entire input buffer. */ -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); - -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); -mz_uint32 tdefl_get_adler32(tdefl_compressor *d); - -/* Create tdefl_compress() flags given zlib-style compression parameters. */ -/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ -/* window_bits may be -15 (raw deflate) or 15 (zlib) */ -/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); - -/* Allocate the tdefl_compressor structure in C so that */ -/* non-C language bindings to tdefl_ API don't need to worry about */ -/* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc(); -void tdefl_compressor_free(tdefl_compressor *pComp); - -#ifdef __cplusplus -} -#endif - -/* ------------------- Low-level Decompression API Definitions */ - -#ifdef __cplusplus -extern "C" { -#endif -/* Decompression flags used by tinfl_decompress(). */ -/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ -/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ -/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ -/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ -enum -{ - TINFL_FLAG_PARSE_ZLIB_HEADER = 1, - TINFL_FLAG_HAS_MORE_INPUT = 2, - TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, - TINFL_FLAG_COMPUTE_ADLER32 = 8 -}; - -/* High level decompression functions: */ -/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ -/* On entry: */ -/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ -/* On return: */ -/* Function returns a pointer to the decompressed data, or NULL on failure. */ -/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ -/* The caller must call mz_free() on the returned block when it's no longer needed. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); - -/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ -/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ -#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); - -/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ -/* Returns 1 on success or 0 on failure. */ -typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); - -struct tinfl_decompressor_tag; -typedef struct tinfl_decompressor_tag tinfl_decompressor; - -/* Allocate the tinfl_decompressor structure in C so that */ -/* non-C language bindings to tinfl_ API don't need to worry about */ -/* structure size and allocation mechanism. */ - -tinfl_decompressor *tinfl_decompressor_alloc(); -void tinfl_decompressor_free(tinfl_decompressor *pDecomp); - -/* Max size of LZ dictionary. */ -#define TINFL_LZ_DICT_SIZE 32768 - -/* Return status. */ -typedef enum { - /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ - /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ - /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ - TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, - - /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ - TINFL_STATUS_BAD_PARAM = -3, - - /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ - TINFL_STATUS_ADLER32_MISMATCH = -2, - - /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ - TINFL_STATUS_FAILED = -1, - - /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ - - /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ - /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ - TINFL_STATUS_DONE = 0, - - /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ - /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ - /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ - TINFL_STATUS_NEEDS_MORE_INPUT = 1, - - /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ - /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ - /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ - /* so I may need to add some code to address this. */ - TINFL_STATUS_HAS_MORE_OUTPUT = 2 -} tinfl_status; - -/* Initializes the decompressor to its initial state. */ -#define tinfl_init(r) \ - do \ - { \ - (r)->m_state = 0; \ - } \ - MZ_MACRO_END -#define tinfl_get_adler32(r) (r)->m_check_adler32 - -/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ -/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); - -/* Internal/private bits follow. */ -enum -{ - TINFL_MAX_HUFF_TABLES = 3, - TINFL_MAX_HUFF_SYMBOLS_0 = 288, - TINFL_MAX_HUFF_SYMBOLS_1 = 32, - TINFL_MAX_HUFF_SYMBOLS_2 = 19, - TINFL_FAST_LOOKUP_BITS = 10, - TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS -}; - -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; -} tinfl_huff_table; - -#if MINIZ_HAS_64BIT_REGISTERS -#define TINFL_USE_64BIT_BITBUF 1 -#else -#define TINFL_USE_64BIT_BITBUF 0 -#endif - -#if TINFL_USE_64BIT_BITBUF -typedef mz_uint64 tinfl_bit_buf_t; -#define TINFL_BITBUF_SIZE (64) -#else -typedef mz_uint32 tinfl_bit_buf_t; -#define TINFL_BITBUF_SIZE (32) -#endif - -struct tinfl_decompressor_tag -{ - mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; - tinfl_bit_buf_t m_bit_buf; - size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; - mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; -}; - -#ifdef __cplusplus -} -#endif - - - -/* ------------------- ZIP archive reading/writing */ - -#ifndef MINIZ_NO_ARCHIVE_APIS - -#ifdef __cplusplus -extern "C" { -#endif - -enum -{ - /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ - MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, - MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, - MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 -}; - -typedef struct -{ - /* Central directory file index. */ - mz_uint32 m_file_index; - - /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ - mz_uint64 m_central_dir_ofs; - - /* These fields are copied directly from the zip's central dir. */ - mz_uint16 m_version_made_by; - mz_uint16 m_version_needed; - mz_uint16 m_bit_flag; - mz_uint16 m_method; - -#ifndef MINIZ_NO_TIME - MZ_TIME_T m_time; -#endif - - /* CRC-32 of uncompressed data. */ - mz_uint32 m_crc32; - - /* File's compressed size. */ - mz_uint64 m_comp_size; - - /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ - mz_uint64 m_uncomp_size; - - /* Zip internal and external file attributes. */ - mz_uint16 m_internal_attr; - mz_uint32 m_external_attr; - - /* Entry's local header file offset in bytes. */ - mz_uint64 m_local_header_ofs; - - /* Size of comment in bytes. */ - mz_uint32 m_comment_size; - - /* MZ_TRUE if the entry appears to be a directory. */ - mz_bool m_is_directory; - - /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ - mz_bool m_is_encrypted; - - /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ - mz_bool m_is_supported; - - /* Filename. If string ends in '/' it's a subdirectory entry. */ - /* Guaranteed to be zero terminated, may be truncated to fit. */ - char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; - - /* Comment field. */ - /* Guaranteed to be zero terminated, may be truncated to fit. */ - char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; - -} mz_zip_archive_file_stat; - -typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); -typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); -typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); - -struct mz_zip_internal_state_tag; -typedef struct mz_zip_internal_state_tag mz_zip_internal_state; - -typedef enum { - MZ_ZIP_MODE_INVALID = 0, - MZ_ZIP_MODE_READING = 1, - MZ_ZIP_MODE_WRITING = 2, - MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 -} mz_zip_mode; - -typedef enum { - MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, - MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, - MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, - MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, - MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ - MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ - MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ - MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, - MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 -} mz_zip_flags; - -typedef enum { - MZ_ZIP_TYPE_INVALID = 0, - MZ_ZIP_TYPE_USER, - MZ_ZIP_TYPE_MEMORY, - MZ_ZIP_TYPE_HEAP, - MZ_ZIP_TYPE_FILE, - MZ_ZIP_TYPE_CFILE, - MZ_ZIP_TOTAL_TYPES -} mz_zip_type; - -/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ -typedef enum { - MZ_ZIP_NO_ERROR = 0, - MZ_ZIP_UNDEFINED_ERROR, - MZ_ZIP_TOO_MANY_FILES, - MZ_ZIP_FILE_TOO_LARGE, - MZ_ZIP_UNSUPPORTED_METHOD, - MZ_ZIP_UNSUPPORTED_ENCRYPTION, - MZ_ZIP_UNSUPPORTED_FEATURE, - MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, - MZ_ZIP_NOT_AN_ARCHIVE, - MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, - MZ_ZIP_UNSUPPORTED_MULTIDISK, - MZ_ZIP_DECOMPRESSION_FAILED, - MZ_ZIP_COMPRESSION_FAILED, - MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, - MZ_ZIP_CRC_CHECK_FAILED, - MZ_ZIP_UNSUPPORTED_CDIR_SIZE, - MZ_ZIP_ALLOC_FAILED, - MZ_ZIP_FILE_OPEN_FAILED, - MZ_ZIP_FILE_CREATE_FAILED, - MZ_ZIP_FILE_WRITE_FAILED, - MZ_ZIP_FILE_READ_FAILED, - MZ_ZIP_FILE_CLOSE_FAILED, - MZ_ZIP_FILE_SEEK_FAILED, - MZ_ZIP_FILE_STAT_FAILED, - MZ_ZIP_INVALID_PARAMETER, - MZ_ZIP_INVALID_FILENAME, - MZ_ZIP_BUF_TOO_SMALL, - MZ_ZIP_INTERNAL_ERROR, - MZ_ZIP_FILE_NOT_FOUND, - MZ_ZIP_ARCHIVE_TOO_LARGE, - MZ_ZIP_VALIDATION_FAILED, - MZ_ZIP_WRITE_CALLBACK_FAILED, - MZ_ZIP_TOTAL_ERRORS -} mz_zip_error; - -typedef struct -{ - mz_uint64 m_archive_size; - mz_uint64 m_central_directory_file_ofs; - - /* We only support up to UINT32_MAX files in zip64 mode. */ - mz_uint32 m_total_files; - mz_zip_mode m_zip_mode; - mz_zip_type m_zip_type; - mz_zip_error m_last_error; - - mz_uint64 m_file_offset_alignment; - - mz_alloc_func m_pAlloc; - mz_free_func m_pFree; - mz_realloc_func m_pRealloc; - void *m_pAlloc_opaque; - - mz_file_read_func m_pRead; - mz_file_write_func m_pWrite; - mz_file_needs_keepalive m_pNeeds_keepalive; - void *m_pIO_opaque; - - mz_zip_internal_state *m_pState; - -} mz_zip_archive; - -typedef struct -{ - mz_zip_archive *pZip; - mz_uint flags; - - int status; -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - mz_uint file_crc32; -#endif - mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; - mz_zip_archive_file_stat file_stat; - void *pRead_buf; - void *pWrite_buf; - - size_t out_blk_remain; - - tinfl_decompressor inflator; - -} mz_zip_reader_extract_iter_state; - -/* -------- ZIP reading */ - -/* Inits a ZIP archive reader. */ -/* These functions read and validate the archive's central directory. */ -mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); - -mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); - -#ifndef MINIZ_NO_STDIO -/* Read a archive from a disk file. */ -/* file_start_ofs is the file offset where the archive actually begins, or 0. */ -/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ -mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); -mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); - -/* Read an archive from an already opened FILE, beginning at the current file position. */ -/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ -/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ -mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); -#endif - -/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ -mz_bool mz_zip_reader_end(mz_zip_archive *pZip); - -/* -------- ZIP reading or writing */ - -/* Clears a mz_zip_archive struct to all zeros. */ -/* Important: This must be done before passing the struct to any mz_zip functions. */ -void mz_zip_zero_struct(mz_zip_archive *pZip); - -mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); -mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); - -/* Returns the total number of files in the archive. */ -mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); - -mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); -mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); -MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); - -/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ -size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); - -/* Attempts to locates a file in the archive's central directory. */ -/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ -/* Returns -1 if the file cannot be found. */ -int mz_zip_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); -/* Returns MZ_FALSE if the file cannot be found. */ -mz_bool mz_zip_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex); - -/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ -/* Note that the m_last_error functionality is not thread safe. */ -mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); -mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); -mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); -mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); -const char *mz_zip_get_error_string(mz_zip_error mz_err); - -/* MZ_TRUE if the archive file entry is a directory entry. */ -mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); - -/* MZ_TRUE if the file is encrypted/strong encrypted. */ -mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); - -/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ -mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); - -/* Retrieves the filename of an archive file entry. */ -/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ -mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); - -/* Attempts to locates a file in the archive's central directory. */ -/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ -/* Returns -1 if the file cannot be found. */ -int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); -int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); - -/* Returns detailed information about an archive file entry. */ -mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); - -/* MZ_TRUE if the file is in zip64 format. */ -/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ -mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); - -/* Returns the total central directory size in bytes. */ -/* The current max supported size is <= MZ_UINT32_MAX. */ -size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); - -/* Extracts a archive file to a memory buffer using no memory allocation. */ -/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); -mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); - -/* Extracts a archive file to a memory buffer. */ -mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); - -/* Extracts a archive file to a dynamically allocated heap buffer. */ -/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ -/* Returns NULL and sets the last error on failure. */ -void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); -void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); - -/* Extracts a archive file using a callback function to output the file's data. */ -mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); - -/* Extract a file iteratively */ -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); -size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); -mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); - -#ifndef MINIZ_NO_STDIO -/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ -/* This function only extracts files, not archive directory records. */ -mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); - -/* Extracts a archive file starting at the current position in the destination FILE stream. */ -mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); -#endif - -#if 0 -/* TODO */ - typedef void *mz_zip_streaming_extract_state_ptr; - mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); - uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); - uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); - mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); - size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); - mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); -#endif - -/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ -/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ -mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); - -/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ -mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); - -/* Misc utils/helpers, valid for ZIP reading or writing */ -mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); -mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); - -/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ -mz_bool mz_zip_end(mz_zip_archive *pZip); - -/* -------- ZIP writing */ - -#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS - -/* Inits a ZIP archive writer. */ -/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ -/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ -mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); -mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); - -mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); -mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); - -#ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); -mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); -mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); -#endif - -/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ -/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ -/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ -/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ -/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ -/* the archive is finalized the file's central directory will be hosed. */ -mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); -mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); - -/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ -/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ -/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ -mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); - -/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ -/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ -mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); - -mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, - const char *user_extra_data_central, mz_uint user_extra_data_central_len); - -#ifndef MINIZ_NO_STDIO -/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ -/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ -mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); - -/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ -mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, - const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, - const char *user_extra_data_central, mz_uint user_extra_data_central_len); -#endif - -/* Adds a file to an archive by fully cloning the data from another archive. */ -/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ -mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); - -/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ -/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ -/* An archive must be manually finalized by calling this function for it to be valid. */ -mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); - -/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ -/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ -mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); - -/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ -/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ -mz_bool mz_zip_writer_end(mz_zip_archive *pZip); - -/* -------- Misc. high-level helper functions: */ - -/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ -/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ -/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ -/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ -mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); -mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); - -/* Reads a single file from an archive into a heap block. */ -/* If pComment is not NULL, only the file with the specified comment will be extracted. */ -/* Returns NULL on failure. */ -void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); -void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); - -#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ - -#ifdef __cplusplus -} -#endif - -#endif /* MINIZ_NO_ARCHIVE_APIS */ -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - - -typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; -typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; -typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- zlib-style API's */ - -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) -{ - mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); - size_t block_len = buf_len % 5552; - if (!ptr) - return MZ_ADLER32_INIT; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; - s1 += ptr[1], s2 += s1; - s1 += ptr[2], s2 += s1; - s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; - s1 += ptr[5], s2 += s1; - s1 += ptr[6], s2 += s1; - s1 += ptr[7], s2 += s1; - } - for (; i < block_len; ++i) - s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; - buf_len -= block_len; - block_len = 5552; - } - return (s2 << 16) + s1; -} - -/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ -#if 0 - mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) - { - static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; - mz_uint32 crcu32 = (mz_uint32)crc; - if (!ptr) - return MZ_CRC32_INIT; - crcu32 = ~crcu32; - while (buf_len--) - { - mz_uint8 b = *ptr++; - crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; - crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; - } - return ~crcu32; - } -#else -/* Faster, but larger CPU cache footprint. - */ -mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) -{ - static const mz_uint32 s_crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, - 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, - 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, - 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, - 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, - 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, - 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, - 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, - 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, - 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, - 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, - 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, - 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, - 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, - 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, - 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, - 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, - 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, - 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, - 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, - 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, - 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, - 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, - 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, - 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, - 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, - 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, - 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, - 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, - 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, - 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; - const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; - - while (buf_len >= 4) - { - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; - pByte_buf += 4; - buf_len -= 4; - } - - while (buf_len) - { - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; - ++pByte_buf; - --buf_len; - } - - return ~crc32; -} -#endif - -void mz_free(void *p) -{ - MZ_FREE(p); -} - -void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) -{ - (void)opaque, (void)items, (void)size; - return MZ_MALLOC(items * size); -} -void miniz_def_free_func(void *opaque, void *address) -{ - (void)opaque, (void)address; - MZ_FREE(address); -} -void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) -{ - (void)opaque, (void)address, (void)items, (void)size; - return MZ_REALLOC(address, items * size); -} - -const char *mz_version(void) -{ - return MZ_VERSION; -} - -#ifndef MINIZ_NO_ZLIB_APIS - -int mz_deflateInit(mz_streamp pStream, int level) -{ - return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); -} - -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) -{ - tdefl_compressor *pComp; - mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); - - if (!pStream) - return MZ_STREAM_ERROR; - if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) - return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = MZ_ADLER32_INIT; - pStream->msg = NULL; - pStream->reserved = 0; - pStream->total_in = 0; - pStream->total_out = 0; - if (!pStream->zalloc) - pStream->zalloc = miniz_def_alloc_func; - if (!pStream->zfree) - pStream->zfree = miniz_def_free_func; - - pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); - if (!pComp) - return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pComp; - - if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) - { - mz_deflateEnd(pStream); - return MZ_PARAM_ERROR; - } - - return MZ_OK; -} - -int mz_deflateReset(mz_streamp pStream) -{ - if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) - return MZ_STREAM_ERROR; - pStream->total_in = pStream->total_out = 0; - tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); - return MZ_OK; -} - -int mz_deflate(mz_streamp pStream, int flush) -{ - size_t in_bytes, out_bytes; - mz_ulong orig_total_in, orig_total_out; - int mz_status = MZ_OK; - - if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) - return MZ_STREAM_ERROR; - if (!pStream->avail_out) - return MZ_BUF_ERROR; - - if (flush == MZ_PARTIAL_FLUSH) - flush = MZ_SYNC_FLUSH; - - if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) - return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; - - orig_total_in = pStream->total_in; - orig_total_out = pStream->total_out; - for (;;) - { - tdefl_status defl_status; - in_bytes = pStream->avail_in; - out_bytes = pStream->avail_out; - - defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); - - pStream->next_out += (mz_uint)out_bytes; - pStream->avail_out -= (mz_uint)out_bytes; - pStream->total_out += (mz_uint)out_bytes; - - if (defl_status < 0) - { - mz_status = MZ_STREAM_ERROR; - break; - } - else if (defl_status == TDEFL_STATUS_DONE) - { - mz_status = MZ_STREAM_END; - break; - } - else if (!pStream->avail_out) - break; - else if ((!pStream->avail_in) && (flush != MZ_FINISH)) - { - if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) - break; - return MZ_BUF_ERROR; /* Can't make forward progress without some input. - */ - } - } - return mz_status; -} - -int mz_deflateEnd(mz_streamp pStream) -{ - if (!pStream) - return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; -} - -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) -{ - (void)pStream; - /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ - return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); -} - -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) -{ - int status; - mz_stream stream; - memset(&stream, 0, sizeof(stream)); - - /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) - return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_deflateInit(&stream, level); - if (status != MZ_OK) - return status; - - status = mz_deflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_deflateEnd(&stream); - return (status == MZ_OK) ? MZ_BUF_ERROR : status; - } - - *pDest_len = stream.total_out; - return mz_deflateEnd(&stream); -} - -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); -} - -mz_ulong mz_compressBound(mz_ulong source_len) -{ - return mz_deflateBound(NULL, source_len); -} - -typedef struct -{ - tinfl_decompressor m_decomp; - mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; - int m_window_bits; - mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; - tinfl_status m_last_status; -} inflate_state; - -int mz_inflateInit2(mz_streamp pStream, int window_bits) -{ - inflate_state *pDecomp; - if (!pStream) - return MZ_STREAM_ERROR; - if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) - return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = 0; - pStream->msg = NULL; - pStream->total_in = 0; - pStream->total_out = 0; - pStream->reserved = 0; - if (!pStream->zalloc) - pStream->zalloc = miniz_def_alloc_func; - if (!pStream->zfree) - pStream->zfree = miniz_def_free_func; - - pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); - if (!pDecomp) - return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pDecomp; - - tinfl_init(&pDecomp->m_decomp); - pDecomp->m_dict_ofs = 0; - pDecomp->m_dict_avail = 0; - pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; - pDecomp->m_first_call = 1; - pDecomp->m_has_flushed = 0; - pDecomp->m_window_bits = window_bits; - - return MZ_OK; -} - -int mz_inflateInit(mz_streamp pStream) -{ - return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); -} - -int mz_inflate(mz_streamp pStream, int flush) -{ - inflate_state *pState; - mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; - size_t in_bytes, out_bytes, orig_avail_in; - tinfl_status status; - - if ((!pStream) || (!pStream->state)) - return MZ_STREAM_ERROR; - if (flush == MZ_PARTIAL_FLUSH) - flush = MZ_SYNC_FLUSH; - if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) - return MZ_STREAM_ERROR; - - pState = (inflate_state *)pStream->state; - if (pState->m_window_bits > 0) - decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; - orig_avail_in = pStream->avail_in; - - first_call = pState->m_first_call; - pState->m_first_call = 0; - if (pState->m_last_status < 0) - return MZ_DATA_ERROR; - - if (pState->m_has_flushed && (flush != MZ_FINISH)) - return MZ_STREAM_ERROR; - pState->m_has_flushed |= (flush == MZ_FINISH); - - if ((flush == MZ_FINISH) && (first_call)) - { - /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ - decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; - in_bytes = pStream->avail_in; - out_bytes = pStream->avail_out; - status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); - pState->m_last_status = status; - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tinfl_get_adler32(&pState->m_decomp); - pStream->next_out += (mz_uint)out_bytes; - pStream->avail_out -= (mz_uint)out_bytes; - pStream->total_out += (mz_uint)out_bytes; - - if (status < 0) - return MZ_DATA_ERROR; - else if (status != TINFL_STATUS_DONE) - { - pState->m_last_status = TINFL_STATUS_FAILED; - return MZ_BUF_ERROR; - } - return MZ_STREAM_END; - } - /* flush != MZ_FINISH then we must assume there's more input. */ - if (flush != MZ_FINISH) - decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; - - if (pState->m_dict_avail) - { - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; - pStream->avail_out -= n; - pStream->total_out += n; - pState->m_dict_avail -= n; - pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; - } - - for (;;) - { - in_bytes = pStream->avail_in; - out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; - - status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); - pState->m_last_status = status; - - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tinfl_get_adler32(&pState->m_decomp); - - pState->m_dict_avail = (mz_uint)out_bytes; - - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; - pStream->avail_out -= n; - pStream->total_out += n; - pState->m_dict_avail -= n; - pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - - if (status < 0) - return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ - else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) - return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ - else if (flush == MZ_FINISH) - { - /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ - if (status == TINFL_STATUS_DONE) - return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; - /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ - else if (!pStream->avail_out) - return MZ_BUF_ERROR; - } - else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) - break; - } - - return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; -} - -int mz_inflateEnd(mz_streamp pStream) -{ - if (!pStream) - return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; -} - -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - mz_stream stream; - int status; - memset(&stream, 0, sizeof(stream)); - - /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) - return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_inflateInit(&stream); - if (status != MZ_OK) - return status; - - status = mz_inflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_inflateEnd(&stream); - return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; - } - *pDest_len = stream.total_out; - - return mz_inflateEnd(&stream); -} - -const char *mz_error(int err) -{ - static struct - { - int m_err; - const char *m_pDesc; - } s_error_descs[] = - { - { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } - }; - mz_uint i; - for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) - if (s_error_descs[i].m_err == err) - return s_error_descs[i].m_pDesc; - return NULL; -} - -#endif /*MINIZ_NO_ZLIB_APIS */ - -#ifdef __cplusplus -} -#endif - -/* - This is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this software, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this software dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - 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 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. - - For more information, please refer to -*/ -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - - - - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- Low-level Compression (independent from all decompression API's) */ - -/* Purposely making these tables static for faster init and thread safety. */ -static const mz_uint16 s_tdefl_len_sym[256] = - { - 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, - 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, - 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, - 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, - 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, - 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, - 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, - 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 - }; - -static const mz_uint8 s_tdefl_len_extra[256] = - { - 0, 0, 0, 0, 0, 0, 0, 0, 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, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 - }; - -static const mz_uint8 s_tdefl_small_dist_sym[512] = - { - 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, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, - 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, - 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, - 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 - }; - -static const mz_uint8 s_tdefl_small_dist_extra[512] = - { - 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7 - }; - -static const mz_uint8 s_tdefl_large_dist_sym[128] = - { - 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 - }; - -static const mz_uint8 s_tdefl_large_dist_extra[128] = - { - 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 - }; - -/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ -typedef struct -{ - mz_uint16 m_key, m_sym_index; -} tdefl_sym_freq; -static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) -{ - mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; - tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; - MZ_CLEAR_OBJ(hist); - for (i = 0; i < num_syms; i++) - { - mz_uint freq = pSyms0[i].m_key; - hist[freq & 0xFF]++; - hist[256 + ((freq >> 8) & 0xFF)]++; - } - while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) - total_passes--; - for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) - { - const mz_uint32 *pHist = &hist[pass << 8]; - mz_uint offsets[256], cur_ofs = 0; - for (i = 0; i < 256; i++) - { - offsets[i] = cur_ofs; - cur_ofs += pHist[i]; - } - for (i = 0; i < num_syms; i++) - pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; - { - tdefl_sym_freq *t = pCur_syms; - pCur_syms = pNew_syms; - pNew_syms = t; - } - } - return pCur_syms; -} - -/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ -static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) -{ - int root, leaf, next, avbl, used, dpth; - if (n == 0) - return; - else if (n == 1) - { - A[0].m_key = 1; - return; - } - A[0].m_key += A[1].m_key; - root = 0; - leaf = 2; - for (next = 1; next < n - 1; next++) - { - if (leaf >= n || A[root].m_key < A[leaf].m_key) - { - A[next].m_key = A[root].m_key; - A[root++].m_key = (mz_uint16)next; - } - else - A[next].m_key = A[leaf++].m_key; - if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) - { - A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); - A[root++].m_key = (mz_uint16)next; - } - else - A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); - } - A[n - 2].m_key = 0; - for (next = n - 3; next >= 0; next--) - A[next].m_key = A[A[next].m_key].m_key + 1; - avbl = 1; - used = dpth = 0; - root = n - 2; - next = n - 1; - while (avbl > 0) - { - while (root >= 0 && (int)A[root].m_key == dpth) - { - used++; - root--; - } - while (avbl > used) - { - A[next--].m_key = (mz_uint16)(dpth); - avbl--; - } - avbl = 2 * used; - dpth++; - used = 0; - } -} - -/* Limits canonical Huffman code table's max code size. */ -enum -{ - TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 -}; -static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) -{ - int i; - mz_uint32 total = 0; - if (code_list_len <= 1) - return; - for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) - pNum_codes[max_code_size] += pNum_codes[i]; - for (i = max_code_size; i > 0; i--) - total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); - while (total != (1UL << max_code_size)) - { - pNum_codes[max_code_size]--; - for (i = max_code_size - 1; i > 0; i--) - if (pNum_codes[i]) - { - pNum_codes[i]--; - pNum_codes[i + 1] += 2; - break; - } - total--; - } -} - -static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) -{ - int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; - mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; - MZ_CLEAR_OBJ(num_codes); - if (static_table) - { - for (i = 0; i < table_len; i++) - num_codes[d->m_huff_code_sizes[table_num][i]]++; - } - else - { - tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; - int num_used_syms = 0; - const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; - for (i = 0; i < table_len; i++) - if (pSym_count[i]) - { - syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; - syms0[num_used_syms++].m_sym_index = (mz_uint16)i; - } - - pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); - tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); - - for (i = 0; i < num_used_syms; i++) - num_codes[pSyms[i].m_key]++; - - tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); - - MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); - MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); - for (i = 1, j = num_used_syms; i <= code_size_limit; i++) - for (l = num_codes[i]; l > 0; l--) - d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); - } - - next_code[1] = 0; - for (j = 0, i = 2; i <= code_size_limit; i++) - next_code[i] = j = ((j + num_codes[i - 1]) << 1); - - for (i = 0; i < table_len; i++) - { - mz_uint rev_code = 0, code, code_size; - if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) - continue; - code = next_code[code_size]++; - for (l = code_size; l > 0; l--, code >>= 1) - rev_code = (rev_code << 1) | (code & 1); - d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; - } -} - -#define TDEFL_PUT_BITS(b, l) \ - do \ - { \ - mz_uint bits = b; \ - mz_uint len = l; \ - MZ_ASSERT(bits <= ((1U << len) - 1U)); \ - d->m_bit_buffer |= (bits << d->m_bits_in); \ - d->m_bits_in += len; \ - while (d->m_bits_in >= 8) \ - { \ - if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ - *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ - d->m_bit_buffer >>= 8; \ - d->m_bits_in -= 8; \ - } \ - } \ - MZ_MACRO_END - -#define TDEFL_RLE_PREV_CODE_SIZE() \ - { \ - if (rle_repeat_count) \ - { \ - if (rle_repeat_count < 3) \ - { \ - d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ - while (rle_repeat_count--) \ - packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ - } \ - else \ - { \ - d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 16; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ - } \ - rle_repeat_count = 0; \ - } \ - } - -#define TDEFL_RLE_ZERO_CODE_SIZE() \ - { \ - if (rle_z_count) \ - { \ - if (rle_z_count < 3) \ - { \ - d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ - while (rle_z_count--) \ - packed_code_sizes[num_packed_code_sizes++] = 0; \ - } \ - else if (rle_z_count <= 10) \ - { \ - d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 17; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ - } \ - else \ - { \ - d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 18; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ - } \ - rle_z_count = 0; \ - } \ - } - -static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - -static void tdefl_start_dynamic_block(tdefl_compressor *d) -{ - int num_lit_codes, num_dist_codes, num_bit_lengths; - mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; - mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; - - d->m_huff_count[0][256] = 1; - - tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); - tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); - - for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) - if (d->m_huff_code_sizes[0][num_lit_codes - 1]) - break; - for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) - if (d->m_huff_code_sizes[1][num_dist_codes - 1]) - break; - - memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); - memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); - total_code_sizes_to_pack = num_lit_codes + num_dist_codes; - num_packed_code_sizes = 0; - rle_z_count = 0; - rle_repeat_count = 0; - - memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); - for (i = 0; i < total_code_sizes_to_pack; i++) - { - mz_uint8 code_size = code_sizes_to_pack[i]; - if (!code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - if (++rle_z_count == 138) - { - TDEFL_RLE_ZERO_CODE_SIZE(); - } - } - else - { - TDEFL_RLE_ZERO_CODE_SIZE(); - if (code_size != prev_code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); - packed_code_sizes[num_packed_code_sizes++] = code_size; - } - else if (++rle_repeat_count == 6) - { - TDEFL_RLE_PREV_CODE_SIZE(); - } - } - prev_code_size = code_size; - } - if (rle_repeat_count) - { - TDEFL_RLE_PREV_CODE_SIZE(); - } - else - { - TDEFL_RLE_ZERO_CODE_SIZE(); - } - - tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); - - TDEFL_PUT_BITS(2, 2); - - TDEFL_PUT_BITS(num_lit_codes - 257, 5); - TDEFL_PUT_BITS(num_dist_codes - 1, 5); - - for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) - if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) - break; - num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); - TDEFL_PUT_BITS(num_bit_lengths - 4, 4); - for (i = 0; (int)i < num_bit_lengths; i++) - TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); - - for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) - { - mz_uint code = packed_code_sizes[packed_code_sizes_index++]; - MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); - TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); - if (code >= 16) - TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); - } -} - -static void tdefl_start_static_block(tdefl_compressor *d) -{ - mz_uint i; - mz_uint8 *p = &d->m_huff_code_sizes[0][0]; - - for (i = 0; i <= 143; ++i) - *p++ = 8; - for (; i <= 255; ++i) - *p++ = 9; - for (; i <= 279; ++i) - *p++ = 7; - for (; i <= 287; ++i) - *p++ = 8; - - memset(d->m_huff_code_sizes[1], 5, 32); - - tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); - tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); - - TDEFL_PUT_BITS(1, 2); -} - -static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - mz_uint8 *pOutput_buf = d->m_pOutput_buf; - mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; - mz_uint64 bit_buffer = d->m_bit_buffer; - mz_uint bits_in = d->m_bits_in; - -#define TDEFL_PUT_BITS_FAST(b, l) \ - { \ - bit_buffer |= (((mz_uint64)(b)) << bits_in); \ - bits_in += (l); \ - } - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - - if (flags & 1) - { - mz_uint s0, s1, n0, n1, sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); - pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ - s0 = s_tdefl_small_dist_sym[match_dist & 511]; - n0 = s_tdefl_small_dist_extra[match_dist & 511]; - s1 = s_tdefl_large_dist_sym[match_dist >> 8]; - n1 = s_tdefl_large_dist_extra[match_dist >> 8]; - sym = (match_dist < 512) ? s0 : s1; - num_extra_bits = (match_dist < 512) ? n0 : n1; - - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); - } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - } - } - } - - if (pOutput_buf >= d->m_pOutput_buf_end) - return MZ_FALSE; - - *(mz_uint64 *)pOutput_buf = bit_buffer; - pOutput_buf += (bits_in >> 3); - bit_buffer >>= (bits_in & ~7); - bits_in &= 7; - } - -#undef TDEFL_PUT_BITS_FAST - - d->m_pOutput_buf = pOutput_buf; - d->m_bits_in = 0; - d->m_bit_buffer = 0; - - while (bits_in) - { - mz_uint32 n = MZ_MIN(bits_in, 16); - TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); - bit_buffer >>= n; - bits_in -= n; - } - - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - - return (d->m_pOutput_buf < d->m_pOutput_buf_end); -} -#else -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - if (flags & 1) - { - mz_uint sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); - pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - if (match_dist < 512) - { - sym = s_tdefl_small_dist_sym[match_dist]; - num_extra_bits = s_tdefl_small_dist_extra[match_dist]; - } - else - { - sym = s_tdefl_large_dist_sym[match_dist >> 8]; - num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; - } - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); - } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - } - } - - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - - return (d->m_pOutput_buf < d->m_pOutput_buf_end); -} -#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ - -static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) -{ - if (static_block) - tdefl_start_static_block(d); - else - tdefl_start_dynamic_block(d); - return tdefl_compress_lz_codes(d); -} - -static int tdefl_flush_block(tdefl_compressor *d, int flush) -{ - mz_uint saved_bit_buf, saved_bits_in; - mz_uint8 *pSaved_output_buf; - mz_bool comp_block_succeeded = MZ_FALSE; - int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; - mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; - - d->m_pOutput_buf = pOutput_buf_start; - d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; - - MZ_ASSERT(!d->m_output_flush_remaining); - d->m_output_flush_ofs = 0; - d->m_output_flush_remaining = 0; - - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); - d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); - - if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) - { - TDEFL_PUT_BITS(0x78, 8); - TDEFL_PUT_BITS(0x01, 8); - } - - TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); - - pSaved_output_buf = d->m_pOutput_buf; - saved_bit_buf = d->m_bit_buffer; - saved_bits_in = d->m_bits_in; - - if (!use_raw_block) - comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); - - /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ - if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && - ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) - { - mz_uint i; - d->m_pOutput_buf = pSaved_output_buf; - d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - TDEFL_PUT_BITS(0, 2); - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) - { - TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); - } - for (i = 0; i < d->m_total_lz_bytes; ++i) - { - TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); - } - } - /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ - else if (!comp_block_succeeded) - { - d->m_pOutput_buf = pSaved_output_buf; - d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - tdefl_compress_block(d, MZ_TRUE); - } - - if (flush) - { - if (flush == TDEFL_FINISH) - { - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) - { - mz_uint i, a = d->m_adler32; - for (i = 0; i < 4; i++) - { - TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); - a <<= 8; - } - } - } - else - { - mz_uint i, z = 0; - TDEFL_PUT_BITS(0, 3); - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - for (i = 2; i; --i, z ^= 0xFFFF) - { - TDEFL_PUT_BITS(z & 0xFFFF, 16); - } - } - } - - MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); - - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; - d->m_pLZ_flags = d->m_lz_code_buf; - d->m_num_flags_left = 8; - d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; - d->m_total_lz_bytes = 0; - d->m_block_index++; - - if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) - { - if (d->m_pPut_buf_func) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) - return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); - } - else if (pOutput_buf_start == d->m_output_buf) - { - int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); - d->m_out_buf_ofs += bytes_to_copy; - if ((n -= bytes_to_copy) != 0) - { - d->m_output_flush_ofs = bytes_to_copy; - d->m_output_flush_remaining = n; - } - } - else - { - d->m_out_buf_ofs += n; - } - } - - return d->m_output_flush_remaining; -} - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES -#ifdef MINIZ_UNALIGNED_USE_MEMCPY -static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) -{ - mz_uint16 ret; - memcpy(&ret, p, sizeof(mz_uint16)); - return ret; -} -static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) -{ - mz_uint16 ret; - memcpy(&ret, p, sizeof(mz_uint16)); - return ret; -} -#else -#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) -#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) -#endif -static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; - mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); - if (max_match_len <= match_len) - return; - for (;;) - { - for (;;) - { - if (--num_probes_left == 0) - return; -#define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ - return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ - break; - TDEFL_PROBE; - TDEFL_PROBE; - TDEFL_PROBE; - } - if (!dist) - break; - q = (const mz_uint16 *)(d->m_dict + probe_pos); - if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) - continue; - p = s; - probe_len = 32; - do - { - } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && - (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); - if (!probe_len) - { - *pMatch_dist = dist; - *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); - break; - } - else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) - { - *pMatch_dist = dist; - if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) - break; - c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); - } - } -} -#else -static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint8 *s = d->m_dict + pos, *p, *q; - mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); - if (max_match_len <= match_len) - return; - for (;;) - { - for (;;) - { - if (--num_probes_left == 0) - return; -#define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ - return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ - break; - TDEFL_PROBE; - TDEFL_PROBE; - TDEFL_PROBE; - } - if (!dist) - break; - p = s; - q = d->m_dict + probe_pos; - for (probe_len = 0; probe_len < max_match_len; probe_len++) - if (*p++ != *q++) - break; - if (probe_len > match_len) - { - *pMatch_dist = dist; - if ((*pMatch_len = match_len = probe_len) == max_match_len) - return; - c0 = d->m_dict[pos + match_len]; - c1 = d->m_dict[pos + match_len - 1]; - } - } -} -#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN -static mz_bool tdefl_compress_fast(tdefl_compressor *d) -{ - /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ - mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; - mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; - mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - - while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) - { - const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; - mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); - d->m_src_buf_left -= num_bytes_to_process; - lookahead_size += num_bytes_to_process; - - while (num_bytes_to_process) - { - mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); - memcpy(d->m_dict + dst_pos, d->m_pSrc, n); - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); - d->m_pSrc += n; - dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; - num_bytes_to_process -= n; - } - - dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); - if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) - break; - - while (lookahead_size >= 4) - { - mz_uint cur_match_dist, cur_match_len = 1; - mz_uint8 *pCur_dict = d->m_dict + cur_pos; - mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; - mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; - mz_uint probe_pos = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)lookahead_pos; - - if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) - { - const mz_uint16 *p = (const mz_uint16 *)pCur_dict; - const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); - mz_uint32 probe_len = 32; - do - { - } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && - (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); - cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); - if (!probe_len) - cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; - - if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) - { - cur_match_len = 1; - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - else - { - mz_uint32 s0, s1; - cur_match_len = MZ_MIN(cur_match_len, lookahead_size); - - MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); - - cur_match_dist--; - - pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); - *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; - pLZ_code_buf += 3; - *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); - - s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; - s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; - d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; - - d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; - } - } - else - { - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - - if (--num_flags_left == 0) - { - num_flags_left = 8; - pLZ_flags = pLZ_code_buf++; - } - - total_lz_bytes += cur_match_len; - lookahead_pos += cur_match_len; - dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; - MZ_ASSERT(lookahead_size >= cur_match_len); - lookahead_size -= cur_match_len; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; - pLZ_code_buf = d->m_pLZ_code_buf; - pLZ_flags = d->m_pLZ_flags; - num_flags_left = d->m_num_flags_left; - } - } - - while (lookahead_size) - { - mz_uint8 lit = d->m_dict[cur_pos]; - - total_lz_bytes++; - *pLZ_code_buf++ = lit; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - if (--num_flags_left == 0) - { - num_flags_left = 8; - pLZ_flags = pLZ_code_buf++; - } - - d->m_huff_count[0][lit]++; - - lookahead_pos++; - dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; - lookahead_size--; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; - pLZ_code_buf = d->m_pLZ_code_buf; - pLZ_flags = d->m_pLZ_flags; - num_flags_left = d->m_num_flags_left; - } - } - } - - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - return MZ_TRUE; -} -#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ - -static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) -{ - d->m_total_lz_bytes++; - *d->m_pLZ_code_buf++ = lit; - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); - if (--d->m_num_flags_left == 0) - { - d->m_num_flags_left = 8; - d->m_pLZ_flags = d->m_pLZ_code_buf++; - } - d->m_huff_count[0][lit]++; -} - -static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) -{ - mz_uint32 s0, s1; - - MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); - - d->m_total_lz_bytes += match_len; - - d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); - - match_dist -= 1; - d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); - d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); - d->m_pLZ_code_buf += 3; - - *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); - if (--d->m_num_flags_left == 0) - { - d->m_num_flags_left = 8; - d->m_pLZ_flags = d->m_pLZ_code_buf++; - } - - s0 = s_tdefl_small_dist_sym[match_dist & 511]; - s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; - d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; - - if (match_len >= TDEFL_MIN_MATCH_LEN) - d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; -} - -static mz_bool tdefl_compress_normal(tdefl_compressor *d) -{ - const mz_uint8 *pSrc = d->m_pSrc; - size_t src_buf_left = d->m_src_buf_left; - tdefl_flush flush = d->m_flush; - - while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) - { - mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; - /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ - if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) - { - mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; - mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); - const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; - src_buf_left -= num_bytes_to_process; - d->m_lookahead_size += num_bytes_to_process; - while (pSrc != pSrc_end) - { - mz_uint8 c = *pSrc++; - d->m_dict[dst_pos] = c; - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)(ins_pos); - dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; - ins_pos++; - } - } - else - { - while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - { - mz_uint8 c = *pSrc++; - mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - src_buf_left--; - d->m_dict[dst_pos] = c; - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) - { - mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; - mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)(ins_pos); - } - } - } - d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); - if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - break; - - /* Simple lazy/greedy parsing state machine. */ - len_to_move = 1; - cur_match_dist = 0; - cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); - cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) - { - if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) - { - mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; - cur_match_len = 0; - while (cur_match_len < d->m_lookahead_size) - { - if (d->m_dict[cur_pos + cur_match_len] != c) - break; - cur_match_len++; - } - if (cur_match_len < TDEFL_MIN_MATCH_LEN) - cur_match_len = 0; - else - cur_match_dist = 1; - } - } - else - { - tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); - } - if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) - { - cur_match_dist = cur_match_len = 0; - } - if (d->m_saved_match_len) - { - if (cur_match_len > d->m_saved_match_len) - { - tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); - if (cur_match_len >= 128) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - d->m_saved_match_len = 0; - len_to_move = cur_match_len; - } - else - { - d->m_saved_lit = d->m_dict[cur_pos]; - d->m_saved_match_dist = cur_match_dist; - d->m_saved_match_len = cur_match_len; - } - } - else - { - tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); - len_to_move = d->m_saved_match_len - 1; - d->m_saved_match_len = 0; - } - } - else if (!cur_match_dist) - tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); - else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - len_to_move = cur_match_len; - } - else - { - d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; - d->m_saved_match_dist = cur_match_dist; - d->m_saved_match_len = cur_match_len; - } - /* Move the lookahead forward by len_to_move bytes. */ - d->m_lookahead_pos += len_to_move; - MZ_ASSERT(d->m_lookahead_size >= len_to_move); - d->m_lookahead_size -= len_to_move; - d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); - /* Check if it's time to flush the current LZ codes to the internal output buffer. */ - if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || - ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) - { - int n; - d->m_pSrc = pSrc; - d->m_src_buf_left = src_buf_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - } - } - - d->m_pSrc = pSrc; - d->m_src_buf_left = src_buf_left; - return MZ_TRUE; -} - -static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) -{ - if (d->m_pIn_buf_size) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - } - - if (d->m_pOut_buf_size) - { - size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); - d->m_output_flush_ofs += (mz_uint)n; - d->m_output_flush_remaining -= (mz_uint)n; - d->m_out_buf_ofs += n; - - *d->m_pOut_buf_size = d->m_out_buf_ofs; - } - - return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; -} - -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) -{ - if (!d) - { - if (pIn_buf_size) - *pIn_buf_size = 0; - if (pOut_buf_size) - *pOut_buf_size = 0; - return TDEFL_STATUS_BAD_PARAM; - } - - d->m_pIn_buf = pIn_buf; - d->m_pIn_buf_size = pIn_buf_size; - d->m_pOut_buf = pOut_buf; - d->m_pOut_buf_size = pOut_buf_size; - d->m_pSrc = (const mz_uint8 *)(pIn_buf); - d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; - d->m_out_buf_ofs = 0; - d->m_flush = flush; - - if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || - (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) - { - if (pIn_buf_size) - *pIn_buf_size = 0; - if (pOut_buf_size) - *pOut_buf_size = 0; - return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); - } - d->m_wants_to_finish |= (flush == TDEFL_FINISH); - - if ((d->m_output_flush_remaining) || (d->m_finished)) - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && - ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && - ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) - { - if (!tdefl_compress_fast(d)) - return d->m_prev_return_status; - } - else -#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ - { - if (!tdefl_compress_normal(d)) - return d->m_prev_return_status; - } - - if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) - d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); - - if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) - { - if (tdefl_flush_block(d, flush) < 0) - return d->m_prev_return_status; - d->m_finished = (flush == TDEFL_FINISH); - if (flush == TDEFL_FULL_FLUSH) - { - MZ_CLEAR_OBJ(d->m_hash); - MZ_CLEAR_OBJ(d->m_next); - d->m_dict_size = 0; - } - } - - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); -} - -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) -{ - MZ_ASSERT(d->m_pPut_buf_func); - return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); -} - -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - d->m_pPut_buf_func = pPut_buf_func; - d->m_pPut_buf_user = pPut_buf_user; - d->m_flags = (mz_uint)(flags); - d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; - d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; - d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; - if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_hash); - d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; - d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; - d->m_pLZ_flags = d->m_lz_code_buf; - d->m_num_flags_left = 8; - d->m_pOutput_buf = d->m_output_buf; - d->m_pOutput_buf_end = d->m_output_buf; - d->m_prev_return_status = TDEFL_STATUS_OKAY; - d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; - d->m_adler32 = 1; - d->m_pIn_buf = NULL; - d->m_pOut_buf = NULL; - d->m_pIn_buf_size = NULL; - d->m_pOut_buf_size = NULL; - d->m_flush = TDEFL_NO_FLUSH; - d->m_pSrc = NULL; - d->m_src_buf_left = 0; - d->m_out_buf_ofs = 0; - if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_dict); - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - return TDEFL_STATUS_OKAY; -} - -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) -{ - return d->m_prev_return_status; -} - -mz_uint32 tdefl_get_adler32(tdefl_compressor *d) -{ - return d->m_adler32; -} - -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - tdefl_compressor *pComp; - mz_bool succeeded; - if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) - return MZ_FALSE; - pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); - if (!pComp) - return MZ_FALSE; - succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); - succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); - MZ_FREE(pComp); - return succeeded; -} - -typedef struct -{ - size_t m_size, m_capacity; - mz_uint8 *m_pBuf; - mz_bool m_expandable; -} tdefl_output_buffer; - -static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) -{ - tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; - size_t new_size = p->m_size + len; - if (new_size > p->m_capacity) - { - size_t new_capacity = p->m_capacity; - mz_uint8 *pNew_buf; - if (!p->m_expandable) - return MZ_FALSE; - do - { - new_capacity = MZ_MAX(128U, new_capacity << 1U); - } while (new_size > new_capacity); - pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); - if (!pNew_buf) - return MZ_FALSE; - p->m_pBuf = pNew_buf; - p->m_capacity = new_capacity; - } - memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); - p->m_size = new_size; - return MZ_TRUE; -} - -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tdefl_output_buffer out_buf; - MZ_CLEAR_OBJ(out_buf); - if (!pOut_len) - return MZ_FALSE; - else - *pOut_len = 0; - out_buf.m_expandable = MZ_TRUE; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) - return NULL; - *pOut_len = out_buf.m_size; - return out_buf.m_pBuf; -} - -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) -{ - tdefl_output_buffer out_buf; - MZ_CLEAR_OBJ(out_buf); - if (!pOut_buf) - return 0; - out_buf.m_pBuf = (mz_uint8 *)pOut_buf; - out_buf.m_capacity = out_buf_len; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) - return 0; - return out_buf.m_size; -} - -static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - -/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) -{ - mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); - if (window_bits > 0) - comp_flags |= TDEFL_WRITE_ZLIB_HEADER; - - if (!level) - comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; - else if (strategy == MZ_FILTERED) - comp_flags |= TDEFL_FILTER_MATCHES; - else if (strategy == MZ_HUFFMAN_ONLY) - comp_flags &= ~TDEFL_MAX_PROBES_MASK; - else if (strategy == MZ_FIXED) - comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; - else if (strategy == MZ_RLE) - comp_flags |= TDEFL_RLE_MATCHES; - - return comp_flags; -} - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ -#endif - -/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at - http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. - This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) -{ - /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ - static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); - tdefl_output_buffer out_buf; - int i, bpl = w * num_chans, y, z; - mz_uint32 c; - *pLen_out = 0; - if (!pComp) - return NULL; - MZ_CLEAR_OBJ(out_buf); - out_buf.m_expandable = MZ_TRUE; - out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); - if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) - { - MZ_FREE(pComp); - return NULL; - } - /* write dummy header */ - for (z = 41; z; --z) - tdefl_output_buffer_putter(&z, 1, &out_buf); - /* compress image data */ - tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); - for (y = 0; y < h; ++y) - { - tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); - tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); - } - if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) - { - MZ_FREE(pComp); - MZ_FREE(out_buf.m_pBuf); - return NULL; - } - /* write real header */ - *pLen_out = out_buf.m_size - 41; - { - static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; - mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, - 0x0a, 0x1a, 0x0a, 0x00, 0x00, - 0x00, 0x0d, 0x49, 0x48, 0x44, - 0x52, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x49, 0x44, 0x41, - 0x54 }; - pnghdr[18] = (mz_uint8)(w >> 8); - pnghdr[19] = (mz_uint8)w; - pnghdr[22] = (mz_uint8)(h >> 8); - pnghdr[23] = (mz_uint8)h; - pnghdr[25] = chans[num_chans]; - pnghdr[33] = (mz_uint8)(*pLen_out >> 24); - pnghdr[34] = (mz_uint8)(*pLen_out >> 16); - pnghdr[35] = (mz_uint8)(*pLen_out >> 8); - pnghdr[36] = (mz_uint8)*pLen_out; - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); - for (i = 0; i < 4; ++i, c <<= 8) - ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); - memcpy(out_buf.m_pBuf, pnghdr, 41); - } - /* write footer (IDAT CRC-32, followed by IEND chunk) */ - if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) - { - *pLen_out = 0; - MZ_FREE(pComp); - MZ_FREE(out_buf.m_pBuf); - return NULL; - } - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); - for (i = 0; i < 4; ++i, c <<= 8) - (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); - /* compute final size of file, grab compressed data buffer and return */ - *pLen_out += 57; - MZ_FREE(pComp); - return out_buf.m_pBuf; -} -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) -{ - /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ - return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); -} - -/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ -/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ -/* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc() -{ - return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); -} - -void tdefl_compressor_free(tdefl_compressor *pComp) -{ - MZ_FREE(pComp); -} - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#ifdef __cplusplus -} -#endif -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - - - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- Low-level Decompression (completely independent from all compression API's) */ - -#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) -#define TINFL_MEMSET(p, c, l) memset(p, c, l) - -#define TINFL_CR_BEGIN \ - switch (r->m_state) \ - { \ - case 0: -#define TINFL_CR_RETURN(state_index, result) \ - do \ - { \ - status = result; \ - r->m_state = state_index; \ - goto common_exit; \ - case state_index:; \ - } \ - MZ_MACRO_END -#define TINFL_CR_RETURN_FOREVER(state_index, result) \ - do \ - { \ - for (;;) \ - { \ - TINFL_CR_RETURN(state_index, result); \ - } \ - } \ - MZ_MACRO_END -#define TINFL_CR_FINISH } - -#define TINFL_GET_BYTE(state_index, c) \ - do \ - { \ - while (pIn_buf_cur >= pIn_buf_end) \ - { \ - TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ - } \ - c = *pIn_buf_cur++; \ - } \ - MZ_MACRO_END - -#define TINFL_NEED_BITS(state_index, n) \ - do \ - { \ - mz_uint c; \ - TINFL_GET_BYTE(state_index, c); \ - bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ - num_bits += 8; \ - } while (num_bits < (mz_uint)(n)) -#define TINFL_SKIP_BITS(state_index, n) \ - do \ - { \ - if (num_bits < (mz_uint)(n)) \ - { \ - TINFL_NEED_BITS(state_index, n); \ - } \ - bit_buf >>= (n); \ - num_bits -= (n); \ - } \ - MZ_MACRO_END -#define TINFL_GET_BITS(state_index, b, n) \ - do \ - { \ - if (num_bits < (mz_uint)(n)) \ - { \ - TINFL_NEED_BITS(state_index, n); \ - } \ - b = bit_buf & ((1 << (n)) - 1); \ - bit_buf >>= (n); \ - num_bits -= (n); \ - } \ - MZ_MACRO_END - -/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ -/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ -/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ -/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ - do \ - { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ - if (temp >= 0) \ - { \ - code_len = temp >> 9; \ - if ((code_len) && (num_bits >= code_len)) \ - break; \ - } \ - else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ - { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do \ - { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while ((temp < 0) && (num_bits >= (code_len + 1))); \ - if (temp >= 0) \ - break; \ - } \ - TINFL_GET_BYTE(state_index, c); \ - bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ - num_bits += 8; \ - } while (num_bits < 15); - -/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ -/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ -/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ -/* The slow path is only executed at the very end of the input buffer. */ -/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ -/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ - do \ - { \ - int temp; \ - mz_uint code_len, c; \ - if (num_bits < 15) \ - { \ - if ((pIn_buf_end - pIn_buf_cur) < 2) \ - { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ - } \ - else \ - { \ - bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ - pIn_buf_cur += 2; \ - num_bits += 16; \ - } \ - } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ - code_len = temp >> 9, temp &= 511; \ - else \ - { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do \ - { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while (temp < 0); \ - } \ - sym = temp; \ - bit_buf >>= code_len; \ - num_bits -= code_len; \ - } \ - MZ_MACRO_END - -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) -{ - static const int s_length_base[31] = { 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 }; - static const int s_length_extra[31] = { 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, 0, 0 }; - static const int s_dist_base[32] = { 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, 0, 0 }; - static const int s_dist_extra[32] = { 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 }; - static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; - - tinfl_status status = TINFL_STATUS_FAILED; - mz_uint32 num_bits, dist, counter, num_extra; - tinfl_bit_buf_t bit_buf; - const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; - size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; - - /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ - if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) - { - *pIn_buf_size = *pOut_buf_size = 0; - return TINFL_STATUS_BAD_PARAM; - } - - num_bits = r->m_num_bits; - bit_buf = r->m_bit_buf; - dist = r->m_dist; - counter = r->m_counter; - num_extra = r->m_num_extra; - dist_from_out_buf_start = r->m_dist_from_out_buf_start; - TINFL_CR_BEGIN - - bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; - r->m_z_adler32 = r->m_check_adler32 = 1; - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_GET_BYTE(1, r->m_zhdr0); - TINFL_GET_BYTE(2, r->m_zhdr1); - counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); - if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); - if (counter) - { - TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); - } - } - - do - { - TINFL_GET_BITS(3, r->m_final, 3); - r->m_type = r->m_final >> 1; - if (r->m_type == 0) - { - TINFL_SKIP_BITS(5, num_bits & 7); - for (counter = 0; counter < 4; ++counter) - { - if (num_bits) - TINFL_GET_BITS(6, r->m_raw_header[counter], 8); - else - TINFL_GET_BYTE(7, r->m_raw_header[counter]); - } - if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) - { - TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); - } - while ((counter) && (num_bits)) - { - TINFL_GET_BITS(51, dist, 8); - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = (mz_uint8)dist; - counter--; - } - while (counter) - { - size_t n; - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); - } - while (pIn_buf_cur >= pIn_buf_end) - { - TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); - } - n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); - TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); - pIn_buf_cur += n; - pOut_buf_cur += n; - counter -= (mz_uint)n; - } - } - else if (r->m_type == 3) - { - TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); - } - else - { - if (r->m_type == 1) - { - mz_uint8 *p = r->m_tables[0].m_code_size; - mz_uint i; - r->m_table_sizes[0] = 288; - r->m_table_sizes[1] = 32; - TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); - for (i = 0; i <= 143; ++i) - *p++ = 8; - for (; i <= 255; ++i) - *p++ = 9; - for (; i <= 279; ++i) - *p++ = 7; - for (; i <= 287; ++i) - *p++ = 8; - } - else - { - for (counter = 0; counter < 3; counter++) - { - TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); - r->m_table_sizes[counter] += s_min_table_sizes[counter]; - } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); - for (counter = 0; counter < r->m_table_sizes[2]; counter++) - { - mz_uint s; - TINFL_GET_BITS(14, s, 3); - r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; - } - r->m_table_sizes[2] = 19; - } - for (; (int)r->m_type >= 0; r->m_type--) - { - int tree_next, tree_cur; - tinfl_huff_table *pTable; - mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; - pTable = &r->m_tables[r->m_type]; - MZ_CLEAR_OBJ(total_syms); - MZ_CLEAR_OBJ(pTable->m_look_up); - MZ_CLEAR_OBJ(pTable->m_tree); - for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) - total_syms[pTable->m_code_size[i]]++; - used_syms = 0, total = 0; - next_code[0] = next_code[1] = 0; - for (i = 1; i <= 15; ++i) - { - used_syms += total_syms[i]; - next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); - } - if ((65536 != total) && (used_syms > 1)) - { - TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); - } - for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) - { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; - if (!code_size) - continue; - cur_code = next_code[code_size]++; - for (l = code_size; l > 0; l--, cur_code >>= 1) - rev_code = (rev_code << 1) | (cur_code & 1); - if (code_size <= TINFL_FAST_LOOKUP_BITS) - { - mz_int16 k = (mz_int16)((code_size << 9) | sym_index); - while (rev_code < TINFL_FAST_LOOKUP_SIZE) - { - pTable->m_look_up[rev_code] = k; - rev_code += (1 << code_size); - } - continue; - } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) - { - pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; - tree_cur = tree_next; - tree_next -= 2; - } - rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); - for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) - { - tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) - { - pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; - tree_cur = tree_next; - tree_next -= 2; - } - else - tree_cur = pTable->m_tree[-tree_cur - 1]; - } - tree_cur -= ((rev_code >>= 1) & 1); - pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; - } - if (r->m_type == 2) - { - for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) - { - mz_uint s; - TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); - if (dist < 16) - { - r->m_len_codes[counter++] = (mz_uint8)dist; - continue; - } - if ((dist == 16) && (!counter)) - { - TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); - } - num_extra = "\02\03\07"[dist - 16]; - TINFL_GET_BITS(18, s, num_extra); - s += "\03\03\013"[dist - 16]; - TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); - counter += s; - } - if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) - { - TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); - } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); - TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); - } - } - for (;;) - { - mz_uint8 *pSrc; - for (;;) - { - if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) - { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); - if (counter >= 256) - break; - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = (mz_uint8)counter; - } - else - { - int sym2; - mz_uint code_len; -#if TINFL_USE_64BIT_BITBUF - if (num_bits < 30) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 4; - num_bits += 32; - } -#else - if (num_bits < 15) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 2; - num_bits += 16; - } -#endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; - do - { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; - } while (sym2 < 0); - } - counter = sym2; - bit_buf >>= code_len; - num_bits -= code_len; - if (counter & 256) - break; - -#if !TINFL_USE_64BIT_BITBUF - if (num_bits < 15) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 2; - num_bits += 16; - } -#endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; - do - { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; - } while (sym2 < 0); - } - bit_buf >>= code_len; - num_bits -= code_len; - - pOut_buf_cur[0] = (mz_uint8)counter; - if (sym2 & 256) - { - pOut_buf_cur++; - counter = sym2; - break; - } - pOut_buf_cur[1] = (mz_uint8)sym2; - pOut_buf_cur += 2; - } - } - if ((counter &= 511) == 256) - break; - - num_extra = s_length_extra[counter - 257]; - counter = s_length_base[counter - 257]; - if (num_extra) - { - mz_uint extra_bits; - TINFL_GET_BITS(25, extra_bits, num_extra); - counter += extra_bits; - } - - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); - num_extra = s_dist_extra[dist]; - dist = s_dist_base[dist]; - if (num_extra) - { - mz_uint extra_bits; - TINFL_GET_BITS(27, extra_bits, num_extra); - dist += extra_bits; - } - - dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - { - TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); - } - - pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); - - if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) - { - while (counter--) - { - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; - } - continue; - } -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES - else if ((counter >= 9) && (counter <= dist)) - { - const mz_uint8 *pSrc_end = pSrc + (counter & ~7); - do - { - ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; - ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; - pOut_buf_cur += 8; - } while ((pSrc += 8) < pSrc_end); - if ((counter &= 7) < 3) - { - if (counter) - { - pOut_buf_cur[0] = pSrc[0]; - if (counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - continue; - } - } -#endif - do - { - pOut_buf_cur[0] = pSrc[0]; - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur[2] = pSrc[2]; - pOut_buf_cur += 3; - pSrc += 3; - } while ((int)(counter -= 3) > 2); - if ((int)counter > 0) - { - pOut_buf_cur[0] = pSrc[0]; - if ((int)counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - } - } - } while (!(r->m_final & 1)); - - /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ - /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ - TINFL_SKIP_BITS(32, num_bits & 7); - while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) - { - --pIn_buf_cur; - num_bits -= 8; - } - bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); - MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ - - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - for (counter = 0; counter < 4; ++counter) - { - mz_uint s; - if (num_bits) - TINFL_GET_BITS(41, s, 8); - else - TINFL_GET_BYTE(42, s); - r->m_z_adler32 = (r->m_z_adler32 << 8) | s; - } - } - TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); - - TINFL_CR_FINISH - -common_exit: - /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ - /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ - /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ - if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) - { - while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) - { - --pIn_buf_cur; - num_bits -= 8; - } - } - r->m_num_bits = num_bits; - r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); - r->m_dist = dist; - r->m_counter = counter; - r->m_num_extra = num_extra; - r->m_dist_from_out_buf_start = dist_from_out_buf_start; - *pIn_buf_size = pIn_buf_cur - pIn_buf_next; - *pOut_buf_size = pOut_buf_cur - pOut_buf_next; - if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) - { - const mz_uint8 *ptr = pOut_buf_next; - size_t buf_len = *pOut_buf_size; - mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; - size_t block_len = buf_len % 5552; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; - s1 += ptr[1], s2 += s1; - s1 += ptr[2], s2 += s1; - s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; - s1 += ptr[5], s2 += s1; - s1 += ptr[6], s2 += s1; - s1 += ptr[7], s2 += s1; - } - for (; i < block_len; ++i) - s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; - buf_len -= block_len; - block_len = 5552; - } - r->m_check_adler32 = (s2 << 16) + s1; - if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) - status = TINFL_STATUS_ADLER32_MISMATCH; - } - return status; -} - -/* Higher level helper functions. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tinfl_decompressor decomp; - void *pBuf = NULL, *pNew_buf; - size_t src_buf_ofs = 0, out_buf_capacity = 0; - *pOut_len = 0; - tinfl_init(&decomp); - for (;;) - { - size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, - (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) - { - MZ_FREE(pBuf); - *pOut_len = 0; - return NULL; - } - src_buf_ofs += src_buf_size; - *pOut_len += dst_buf_size; - if (status == TINFL_STATUS_DONE) - break; - new_out_buf_capacity = out_buf_capacity * 2; - if (new_out_buf_capacity < 128) - new_out_buf_capacity = 128; - pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); - if (!pNew_buf) - { - MZ_FREE(pBuf); - *pOut_len = 0; - return NULL; - } - pBuf = pNew_buf; - out_buf_capacity = new_out_buf_capacity; - } - return pBuf; -} - -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) -{ - tinfl_decompressor decomp; - tinfl_status status; - tinfl_init(&decomp); - status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; -} - -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - int result = 0; - tinfl_decompressor decomp; - mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); - size_t in_buf_ofs = 0, dict_ofs = 0; - if (!pDict) - return TINFL_STATUS_FAILED; - tinfl_init(&decomp); - for (;;) - { - size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, - (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); - in_buf_ofs += in_buf_size; - if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) - break; - if (status != TINFL_STATUS_HAS_MORE_OUTPUT) - { - result = (status == TINFL_STATUS_DONE); - break; - } - dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); - } - MZ_FREE(pDict); - *pIn_buf_size = in_buf_ofs; - return result; -} - -tinfl_decompressor *tinfl_decompressor_alloc() -{ - tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); - if (pDecomp) - tinfl_init(pDecomp); - return pDecomp; -} - -void tinfl_decompressor_free(tinfl_decompressor *pDecomp) -{ - MZ_FREE(pDecomp); -} - -#ifdef __cplusplus -} -#endif -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * Copyright 2016 Martin Raiber - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - - -#ifndef MINIZ_NO_ARCHIVE_APIS - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- .ZIP archive reading */ - -#ifdef MINIZ_NO_STDIO -#define MZ_FILE void * -#else -#include - -#if defined(_MSC_VER) || defined(__MINGW64__) -static FILE *mz_fopen(const char *pFilename, const char *pMode) -{ - FILE *pFile = NULL; - fopen_s(&pFile, pFilename, pMode); - return pFile; -} -static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) -{ - FILE *pFile = NULL; - if (freopen_s(&pFile, pPath, pMode, pStream)) - return NULL; - return pFile; -} -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN mz_fopen -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#define MZ_FTELL64 _ftelli64 -#define MZ_FSEEK64 _fseeki64 -#define MZ_FILE_STAT_STRUCT _stat -#define MZ_FILE_STAT _stat -#define MZ_FFLUSH fflush -#define MZ_FREOPEN mz_freopen -#define MZ_DELETE_FILE remove -#elif defined(__MINGW32__) -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN(f, m) fopen(f, m) -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#define MZ_FTELL64 ftello64 -#define MZ_FSEEK64 fseeko64 -#define MZ_FILE_STAT_STRUCT _stat -#define MZ_FILE_STAT _stat -#define MZ_FFLUSH fflush -#define MZ_FREOPEN(f, m, s) freopen(f, m, s) -#define MZ_DELETE_FILE remove -#elif defined(__TINYC__) -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN(f, m) fopen(f, m) -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#define MZ_FTELL64 ftell -#define MZ_FSEEK64 fseek -#define MZ_FILE_STAT_STRUCT stat -#define MZ_FILE_STAT stat -#define MZ_FFLUSH fflush -#define MZ_FREOPEN(f, m, s) freopen(f, m, s) -#define MZ_DELETE_FILE remove -#elif defined(__GNUC__) && _LARGEFILE64_SOURCE -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN(f, m) fopen64(f, m) -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#define MZ_FTELL64 ftello64 -#define MZ_FSEEK64 fseeko64 -#define MZ_FILE_STAT_STRUCT stat64 -#define MZ_FILE_STAT stat64 -#define MZ_FFLUSH fflush -#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) -#define MZ_DELETE_FILE remove -#elif defined(__APPLE__) && _LARGEFILE64_SOURCE -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN(f, m) fopen(f, m) -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#define MZ_FTELL64 ftello -#define MZ_FSEEK64 fseeko -#define MZ_FILE_STAT_STRUCT stat -#define MZ_FILE_STAT stat -#define MZ_FFLUSH fflush -#define MZ_FREOPEN(p, m, s) freopen(p, m, s) -#define MZ_DELETE_FILE remove - -#else -#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") -#ifndef MINIZ_NO_TIME -#include -#endif -#define MZ_FOPEN(f, m) fopen(f, m) -#define MZ_FCLOSE fclose -#define MZ_FREAD fread -#define MZ_FWRITE fwrite -#ifdef __STRICT_ANSI__ -#define MZ_FTELL64 ftell -#define MZ_FSEEK64 fseek -#else -#define MZ_FTELL64 ftello -#define MZ_FSEEK64 fseeko -#endif -#define MZ_FILE_STAT_STRUCT stat -#define MZ_FILE_STAT stat -#define MZ_FFLUSH fflush -#define MZ_FREOPEN(f, m, s) freopen(f, m, s) -#define MZ_DELETE_FILE remove -#endif /* #ifdef _MSC_VER */ -#endif /* #ifdef MINIZ_NO_STDIO */ - -#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) - -/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ -enum -{ - /* ZIP archive identifiers and record sizes */ - MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, - MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, - MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, - MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, - MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, - MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, - - /* ZIP64 archive identifier and record sizes */ - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, - MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, - MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, - MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, - MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, - - /* Central directory header record offsets */ - MZ_ZIP_CDH_SIG_OFS = 0, - MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, - MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, - MZ_ZIP_CDH_BIT_FLAG_OFS = 8, - MZ_ZIP_CDH_METHOD_OFS = 10, - MZ_ZIP_CDH_FILE_TIME_OFS = 12, - MZ_ZIP_CDH_FILE_DATE_OFS = 14, - MZ_ZIP_CDH_CRC32_OFS = 16, - MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, - MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, - MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, - MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, - MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, - MZ_ZIP_CDH_DISK_START_OFS = 34, - MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, - MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, - MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, - - /* Local directory header offsets */ - MZ_ZIP_LDH_SIG_OFS = 0, - MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, - MZ_ZIP_LDH_BIT_FLAG_OFS = 6, - MZ_ZIP_LDH_METHOD_OFS = 8, - MZ_ZIP_LDH_FILE_TIME_OFS = 10, - MZ_ZIP_LDH_FILE_DATE_OFS = 12, - MZ_ZIP_LDH_CRC32_OFS = 14, - MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, - MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, - MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, - MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, - MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, - - /* End of central directory offsets */ - MZ_ZIP_ECDH_SIG_OFS = 0, - MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, - MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, - MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, - MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, - MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, - MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, - MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, - - /* ZIP64 End of central directory locator offsets */ - MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ - MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ - MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ - MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ - - /* ZIP64 End of central directory header offsets */ - MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ - MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ - MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ - MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ - MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ - MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ - MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ - MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ - MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ - MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ - MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, - MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, - MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, - MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, - MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, - MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, - MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 -}; - -typedef struct -{ - void *m_p; - size_t m_size, m_capacity; - mz_uint m_element_size; -} mz_zip_array; - -struct mz_zip_internal_state_tag -{ - mz_zip_array m_central_dir; - mz_zip_array m_central_dir_offsets; - mz_zip_array m_sorted_central_dir_offsets; - - /* The flags passed in when the archive is initially opened. */ - uint32_t m_init_flags; - - /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ - mz_bool m_zip64; - - /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ - mz_bool m_zip64_has_extended_info_fields; - - /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ - MZ_FILE *m_pFile; - mz_uint64 m_file_archive_start_ofs; - - void *m_pMem; - size_t m_mem_size; - size_t m_mem_capacity; -}; - -#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size - -#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) -static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) -{ - MZ_ASSERT(index < pArray->m_size); - return index; -} -#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] -#else -#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] -#endif - -static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) -{ - memset(pArray, 0, sizeof(mz_zip_array)); - pArray->m_element_size = element_size; -} - -static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) -{ - pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); - memset(pArray, 0, sizeof(mz_zip_array)); -} - -static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) -{ - void *pNew_p; - size_t new_capacity = min_new_capacity; - MZ_ASSERT(pArray->m_element_size); - if (pArray->m_capacity >= min_new_capacity) - return MZ_TRUE; - if (growing) - { - new_capacity = MZ_MAX(1, pArray->m_capacity); - while (new_capacity < min_new_capacity) - new_capacity *= 2; - } - if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) - return MZ_FALSE; - pArray->m_p = pNew_p; - pArray->m_capacity = new_capacity; - return MZ_TRUE; -} - -static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) -{ - if (new_capacity > pArray->m_capacity) - { - if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) - return MZ_FALSE; - } - return MZ_TRUE; -} - -static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) -{ - if (new_size > pArray->m_capacity) - { - if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) - return MZ_FALSE; - } - pArray->m_size = new_size; - return MZ_TRUE; -} - -static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) -{ - return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); -} - -static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) -{ - size_t orig_size = pArray->m_size; - if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) - return MZ_FALSE; - memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); - return MZ_TRUE; -} - -#ifndef MINIZ_NO_TIME -static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); - tm.tm_isdst = -1; - tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; - tm.tm_mon = ((dos_date >> 5) & 15) - 1; - tm.tm_mday = dos_date & 31; - tm.tm_hour = (dos_time >> 11) & 31; - tm.tm_min = (dos_time >> 5) & 63; - tm.tm_sec = (dos_time << 1) & 62; - return mktime(&tm); -} - -#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS -static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) -{ -#ifdef _MSC_VER - struct tm tm_struct; - struct tm *tm = &tm_struct; - errno_t err = localtime_s(tm, &time); - if (err) - { - *pDOS_date = 0; - *pDOS_time = 0; - return; - } -#else - struct tm *tm = localtime(&time); -#endif /* #ifdef _MSC_VER */ - - *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); - *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); -} -#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ - -#ifndef MINIZ_NO_STDIO -#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS -static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) -{ - struct MZ_FILE_STAT_STRUCT file_stat; - - /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ - if (MZ_FILE_STAT(pFilename, &file_stat) != 0) - return MZ_FALSE; - - *pTime = file_stat.st_mtime; - - return MZ_TRUE; -} -#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ - -static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) -{ - struct utimbuf t; - - memset(&t, 0, sizeof(t)); - t.actime = access_time; - t.modtime = modified_time; - - return !utime(pFilename, &t); -} -#endif /* #ifndef MINIZ_NO_STDIO */ -#endif /* #ifndef MINIZ_NO_TIME */ - -static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) -{ - if (pZip) - pZip->m_last_error = err_num; - return MZ_FALSE; -} - -static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) -{ - (void)flags; - if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!pZip->m_pAlloc) - pZip->m_pAlloc = miniz_def_alloc_func; - if (!pZip->m_pFree) - pZip->m_pFree = miniz_def_free_func; - if (!pZip->m_pRealloc) - pZip->m_pRealloc = miniz_def_realloc_func; - - pZip->m_archive_size = 0; - pZip->m_central_directory_file_ofs = 0; - pZip->m_total_files = 0; - pZip->m_last_error = MZ_ZIP_NO_ERROR; - - if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); - pZip->m_pState->m_init_flags = flags; - pZip->m_pState->m_zip64 = MZ_FALSE; - pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; - - pZip->m_zip_mode = MZ_ZIP_MODE_READING; - - return MZ_TRUE; -} - -static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) -{ - const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; - const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); - mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); - mz_uint8 l = 0, r = 0; - pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - pE = pL + MZ_MIN(l_len, r_len); - while (pL < pE) - { - if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) - break; - pL++; - pR++; - } - return (pL == pE) ? (l_len < r_len) : (l < r); -} - -#define MZ_SWAP_UINT32(a, b) \ - do \ - { \ - mz_uint32 t = a; \ - a = b; \ - b = t; \ - } \ - MZ_MACRO_END - -/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ -static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) -{ - mz_zip_internal_state *pState = pZip->m_pState; - const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; - const mz_zip_array *pCentral_dir = &pState->m_central_dir; - mz_uint32 *pIndices; - mz_uint32 start, end; - const mz_uint32 size = pZip->m_total_files; - - if (size <= 1U) - return; - - pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); - - start = (size - 2U) >> 1U; - for (;;) - { - mz_uint64 child, root = start; - for (;;) - { - if ((child = (root << 1U) + 1U) >= size) - break; - child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); - if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) - break; - MZ_SWAP_UINT32(pIndices[root], pIndices[child]); - root = child; - } - if (!start) - break; - start--; - } - - end = size - 1; - while (end > 0) - { - mz_uint64 child, root = 0; - MZ_SWAP_UINT32(pIndices[end], pIndices[0]); - for (;;) - { - if ((child = (root << 1U) + 1U) >= end) - break; - child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); - if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) - break; - MZ_SWAP_UINT32(pIndices[root], pIndices[child]); - root = child; - } - end--; - } -} - -static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) -{ - mz_int64 cur_file_ofs; - mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; - mz_uint8 *pBuf = (mz_uint8 *)buf_u32; - - /* Basic sanity checks - reject files which are too small */ - if (pZip->m_archive_size < record_size) - return MZ_FALSE; - - /* Find the record by scanning the file from the end towards the beginning. */ - cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); - for (;;) - { - int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); - - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) - return MZ_FALSE; - - for (i = n - 4; i >= 0; --i) - { - mz_uint s = MZ_READ_LE32(pBuf + i); - if (s == record_sig) - { - if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) - break; - } - } - - if (i >= 0) - { - cur_file_ofs += i; - break; - } - - /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ - if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) - return MZ_FALSE; - - cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); - } - - *pOfs = cur_file_ofs; - return MZ_TRUE; -} - -static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) -{ - mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; - mz_uint64 cdir_ofs = 0; - mz_int64 cur_file_ofs = 0; - const mz_uint8 *p; - - mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; - mz_uint8 *pBuf = (mz_uint8 *)buf_u32; - mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); - mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; - - mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; - - mz_uint64 zip64_end_of_central_dir_ofs = 0; - - /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ - if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - - if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) - return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); - - /* Read and verify the end of central directory record. */ - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - - if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) - { - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) - { - if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) - { - zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); - if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - - if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) - { - if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) - { - pZip->m_pState->m_zip64 = MZ_TRUE; - } - } - } - } - } - - pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); - cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); - num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); - cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); - cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); - cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); - - if (pZip->m_pState->m_zip64) - { - mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); - mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); - mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); - mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); - mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); - - if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (zip64_total_num_of_disks != 1U) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - - /* Check for miniz's practical limits */ - if (zip64_cdir_total_entries > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - - pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; - - if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - - cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; - - /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ - if (zip64_size_of_central_directory > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - - cdir_size = (mz_uint32)zip64_size_of_central_directory; - - num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); - - cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); - - cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); - } - - if (pZip->m_total_files != cdir_entries_on_this_disk) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - - if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - - if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - pZip->m_central_directory_file_ofs = cdir_ofs; - - if (pZip->m_total_files) - { - mz_uint i, n; - /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ - if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || - (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - if (sort_central_dir) - { - if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - /* Now create an index into the central directory file records, do some basic sanity checking on each record */ - p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; - for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) - { - mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; - mz_uint64 comp_size, decomp_size, local_header_ofs; - - if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); - - if (sort_central_dir) - MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; - - comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); - filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); - - if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && - (ext_data_size) && - (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) - { - /* Attempt to find zip64 extended information field in the entry's extra data */ - mz_uint32 extra_size_remaining = ext_data_size; - - if (extra_size_remaining) - { - const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; - - do - { - mz_uint32 field_id; - mz_uint32 field_data_size; - - if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - field_id = MZ_READ_LE16(pExtra_data); - field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); - - if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) - { - /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ - pZip->m_pState->m_zip64 = MZ_TRUE; - pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; - break; - } - - pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; - extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; - } while (extra_size_remaining); - } - } - - /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ - if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) - { - if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - } - - disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); - if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - - if (comp_size != MZ_UINT32_MAX) - { - if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - } - - bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - - if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - n -= total_header_size; - p += total_header_size; - } - } - - if (sort_central_dir) - mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); - - return MZ_TRUE; -} - -void mz_zip_zero_struct(mz_zip_archive *pZip) -{ - if (pZip) - MZ_CLEAR_OBJ(*pZip); -} - -static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) -{ - mz_bool status = MZ_TRUE; - - if (!pZip) - return MZ_FALSE; - - if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - { - if (set_last_error) - pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; - - return MZ_FALSE; - } - - if (pZip->m_pState) - { - mz_zip_internal_state *pState = pZip->m_pState; - pZip->m_pState = NULL; - - mz_zip_array_clear(pZip, &pState->m_central_dir); - mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); - mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); - -#ifndef MINIZ_NO_STDIO - if (pState->m_pFile) - { - if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) - { - if (MZ_FCLOSE(pState->m_pFile) == EOF) - { - if (set_last_error) - pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; - status = MZ_FALSE; - } - } - pState->m_pFile = NULL; - } -#endif /* #ifndef MINIZ_NO_STDIO */ - - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - } - pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; - - return status; -} - -mz_bool mz_zip_reader_end(mz_zip_archive *pZip) -{ - return mz_zip_reader_end_internal(pZip, MZ_TRUE); -} -mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) -{ - if ((!pZip) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_reader_init_internal(pZip, flags)) - return MZ_FALSE; - - pZip->m_zip_type = MZ_ZIP_TYPE_USER; - pZip->m_archive_size = size; - - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - -static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); - memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); - return s; -} - -mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) -{ - if (!pMem) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - - if (!mz_zip_reader_init_internal(pZip, flags)) - return MZ_FALSE; - - pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; - pZip->m_archive_size = size; - pZip->m_pRead = mz_zip_mem_read_func; - pZip->m_pIO_opaque = pZip; - pZip->m_pNeeds_keepalive = NULL; - -#ifdef __cplusplus - pZip->m_pState->m_pMem = const_cast(pMem); -#else - pZip->m_pState->m_pMem = (void *)pMem; -#endif - - pZip->m_pState->m_mem_size = size; - - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - -#ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); - - file_ofs += pZip->m_pState->m_file_archive_start_ofs; - - if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) - return 0; - - return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); -} - -mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) -{ - return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); -} - -mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) -{ - mz_uint64 file_size; - MZ_FILE *pFile; - - if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pFile = MZ_FOPEN(pFilename, "rb"); - if (!pFile) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - - file_size = archive_size; - if (!file_size) - { - if (MZ_FSEEK64(pFile, 0, SEEK_END)) - { - MZ_FCLOSE(pFile); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); - } - - file_size = MZ_FTELL64(pFile); - } - - /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ - - if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - { - MZ_FCLOSE(pFile); - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - } - - if (!mz_zip_reader_init_internal(pZip, flags)) - { - MZ_FCLOSE(pFile); - return MZ_FALSE; - } - - pZip->m_zip_type = MZ_ZIP_TYPE_FILE; - pZip->m_pRead = mz_zip_file_read_func; - pZip->m_pIO_opaque = pZip; - pZip->m_pState->m_pFile = pFile; - pZip->m_archive_size = file_size; - pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; - - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - -mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) -{ - mz_uint64 cur_file_ofs; - - if ((!pZip) || (!pFile)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - - cur_file_ofs = MZ_FTELL64(pFile); - - if (!archive_size) - { - if (MZ_FSEEK64(pFile, 0, SEEK_END)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); - - archive_size = MZ_FTELL64(pFile) - cur_file_ofs; - - if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - } - - if (!mz_zip_reader_init_internal(pZip, flags)) - return MZ_FALSE; - - pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; - pZip->m_pRead = mz_zip_file_read_func; - - pZip->m_pIO_opaque = pZip; - pZip->m_pState->m_pFile = pFile; - pZip->m_archive_size = archive_size; - pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; - - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - -#endif /* #ifndef MINIZ_NO_STDIO */ - -static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) -{ - if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) - return NULL; - return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); -} - -mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) -{ - mz_uint m_bit_flag; - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); - if (!p) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return MZ_FALSE; - } - - m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; -} - -mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) -{ - mz_uint bit_flag; - mz_uint method; - - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); - if (!p) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return MZ_FALSE; - } - - method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); - bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - - if ((method != 0) && (method != MZ_DEFLATED)) - { - mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); - return MZ_FALSE; - } - - if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) - { - mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - return MZ_FALSE; - } - - if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) - { - mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - -mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) -{ - mz_uint filename_len, attribute_mapping_id, external_attr; - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); - if (!p) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return MZ_FALSE; - } - - filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - if (filename_len) - { - if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') - return MZ_TRUE; - } - - /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ - /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ - /* FIXME: Remove this check? Is it necessary - we already check the filename. */ - attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; - (void)attribute_mapping_id; - - external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); - if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) - { - return MZ_TRUE; - } - - return MZ_FALSE; -} - -static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) -{ - mz_uint n; - const mz_uint8 *p = pCentral_dir_header; - - if (pFound_zip64_extra_data) - *pFound_zip64_extra_data = MZ_FALSE; - - if ((!p) || (!pStat)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* Extract fields from the central directory record. */ - pStat->m_file_index = file_index; - pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); - pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); - pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); - pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); -#ifndef MINIZ_NO_TIME - pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); -#endif - pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); - pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); - pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); - pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); - - /* Copy as much of the filename and comment as possible. */ - n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); - memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); - pStat->m_filename[n] = '\0'; - - n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); - n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); - pStat->m_comment_size = n; - memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); - pStat->m_comment[n] = '\0'; - - /* Set some flags for convienance */ - pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); - pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); - pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); - - /* See if we need to read any zip64 extended information fields. */ - /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ - if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) - { - /* Attempt to find zip64 extended information field in the entry's extra data */ - mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); - - if (extra_size_remaining) - { - const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - - do - { - mz_uint32 field_id; - mz_uint32 field_data_size; - - if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - field_id = MZ_READ_LE16(pExtra_data); - field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); - - if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) - { - const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; - mz_uint32 field_data_remaining = field_data_size; - - if (pFound_zip64_extra_data) - *pFound_zip64_extra_data = MZ_TRUE; - - if (pStat->m_uncomp_size == MZ_UINT32_MAX) - { - if (field_data_remaining < sizeof(mz_uint64)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - pStat->m_uncomp_size = MZ_READ_LE64(pField_data); - pField_data += sizeof(mz_uint64); - field_data_remaining -= sizeof(mz_uint64); - } - - if (pStat->m_comp_size == MZ_UINT32_MAX) - { - if (field_data_remaining < sizeof(mz_uint64)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - pStat->m_comp_size = MZ_READ_LE64(pField_data); - pField_data += sizeof(mz_uint64); - field_data_remaining -= sizeof(mz_uint64); - } - - if (pStat->m_local_header_ofs == MZ_UINT32_MAX) - { - if (field_data_remaining < sizeof(mz_uint64)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); - pField_data += sizeof(mz_uint64); - field_data_remaining -= sizeof(mz_uint64); - } - - break; - } - - pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; - extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; - } while (extra_size_remaining); - } - } - - return MZ_TRUE; -} - -static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) -{ - mz_uint i; - if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) - return 0 == memcmp(pA, pB, len); - for (i = 0; i < len; ++i) - if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) - return MZ_FALSE; - return MZ_TRUE; -} - -static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) -{ - const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; - mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); - mz_uint8 l = 0, r = 0; - pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - pE = pL + MZ_MIN(l_len, r_len); - while (pL < pE) - { - if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) - break; - pL++; - pR++; - } - return (pL == pE) ? (int)(l_len - r_len) : (l - r); -} - -static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) -{ - mz_zip_internal_state *pState = pZip->m_pState; - const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; - const mz_zip_array *pCentral_dir = &pState->m_central_dir; - mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); - const uint32_t size = pZip->m_total_files; - const mz_uint filename_len = (mz_uint)strlen(pFilename); - - if (pIndex) - *pIndex = 0; - - if (size) - { - /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ - /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ - mz_int64 l = 0, h = (mz_int64)size - 1; - - while (l <= h) - { - mz_int64 m = l + ((h - l) >> 1); - uint32_t file_index = pIndices[(uint32_t)m]; - - int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); - if (!comp) - { - if (pIndex) - *pIndex = file_index; - return MZ_TRUE; - } - else if (comp < 0) - l = m + 1; - else - h = m - 1; - } - } - - return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); -} - -int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) -{ - mz_uint32 index; - if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) - return -1; - else - return (int)index; -} - -mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) -{ - mz_uint file_index; - size_t name_len, comment_len; - - if (pIndex) - *pIndex = 0; - - if ((!pZip) || (!pZip->m_pState) || (!pName)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* See if we can use a binary search */ - if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && - (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && - ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) - { - return mz_zip_locate_file_binary_search(pZip, pName, pIndex); - } - - /* Locate the entry by scanning the entire central directory */ - name_len = strlen(pName); - if (name_len > MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - comment_len = pComment ? strlen(pComment) : 0; - if (comment_len > MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - for (file_index = 0; file_index < pZip->m_total_files; file_index++) - { - const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); - mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); - const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - if (filename_len < name_len) - continue; - if (comment_len) - { - mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); - const char *pFile_comment = pFilename + filename_len + file_extra_len; - if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) - continue; - } - if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) - { - int ofs = filename_len - 1; - do - { - if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) - break; - } while (--ofs >= 0); - ofs++; - pFilename += ofs; - filename_len -= ofs; - } - if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) - { - if (pIndex) - *pIndex = file_index; - return MZ_TRUE; - } - } - - return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); -} - -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) -{ - int status = TINFL_STATUS_DONE; - mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; - mz_zip_archive_file_stat file_stat; - void *pRead_buf; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - tinfl_decompressor inflator; - - if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; - - /* A directory or zero length file */ - if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) - return MZ_TRUE; - - /* Encryption and patch files are not supported. */ - if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - - /* This function only supports decompressing stored and deflate. */ - if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); - - /* Ensure supplied output buffer is large enough. */ - needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; - if (buf_size < needed_size) - return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); - - /* Read and parse the local directory entry. */ - cur_file_ofs = file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) - { - /* The file is stored or the caller has requested the compressed data. */ - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) - { - if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) - return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); - } -#endif - - return MZ_TRUE; - } - - /* Decompress the file either directly from memory or from a file input buffer. */ - tinfl_init(&inflator); - - if (pZip->m_pState->m_pMem) - { - /* Read directly from the archive in memory. */ - pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; - read_buf_size = read_buf_avail = file_stat.m_comp_size; - comp_remaining = 0; - } - else if (pUser_read_buf) - { - /* Use a user provided read buffer. */ - if (!user_read_buf_size) - return MZ_FALSE; - pRead_buf = (mz_uint8 *)pUser_read_buf; - read_buf_size = user_read_buf_size; - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - else - { - /* Temporarily allocate a read buffer. */ - read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); - if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - - do - { - /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ - size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); - if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - status = TINFL_STATUS_FAILED; - mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); - break; - } - cur_file_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - read_buf_ofs = 0; - } - in_buf_size = (size_t)read_buf_avail; - status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); - read_buf_avail -= in_buf_size; - read_buf_ofs += in_buf_size; - out_buf_ofs += out_buf_size; - } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); - - if (status == TINFL_STATUS_DONE) - { - /* Make sure the entire file was decompressed, and check its CRC. */ - if (out_buf_ofs != file_stat.m_uncomp_size) - { - mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); - status = TINFL_STATUS_FAILED; - } -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) - { - mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); - status = TINFL_STATUS_FAILED; - } -#endif - } - - if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - - return status == TINFL_STATUS_DONE; -} - -mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) -{ - mz_uint32 file_index; - if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) - return MZ_FALSE; - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); -} - -mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) -{ - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); -} - -mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) -{ - return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); -} - -void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) -{ - mz_uint64 comp_size, uncomp_size, alloc_size; - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); - void *pBuf; - - if (pSize) - *pSize = 0; - - if (!p) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return NULL; - } - - comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - - alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; - if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) - { - mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - return NULL; - } - - if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - return NULL; - } - - if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return NULL; - } - - if (pSize) - *pSize = (size_t)alloc_size; - return pBuf; -} - -void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) -{ - mz_uint32 file_index; - if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) - { - if (pSize) - *pSize = 0; - return MZ_FALSE; - } - return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); -} - -mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) -{ - int status = TINFL_STATUS_DONE; - mz_uint file_crc32 = MZ_CRC32_INIT; - mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; - mz_zip_archive_file_stat file_stat; - void *pRead_buf = NULL; - void *pWrite_buf = NULL; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - - if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; - - /* A directory or zero length file */ - if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) - return MZ_TRUE; - - /* Encryption and patch files are not supported. */ - if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - - /* This function only supports decompressing stored and deflate. */ - if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); - - /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ - cur_file_ofs = file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - /* Decompress the file either directly from memory or from a file input buffer. */ - if (pZip->m_pState->m_pMem) - { - pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; - read_buf_size = read_buf_avail = file_stat.m_comp_size; - comp_remaining = 0; - } - else - { - read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); - if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - - if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) - { - /* The file is stored or the caller has requested the compressed data. */ - if (pZip->m_pState->m_pMem) - { - if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) - { - mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); - status = TINFL_STATUS_FAILED; - } - else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - { -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); -#endif - } - - cur_file_ofs += file_stat.m_comp_size; - out_buf_ofs += file_stat.m_comp_size; - comp_remaining = 0; - } - else - { - while (comp_remaining) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - status = TINFL_STATUS_FAILED; - break; - } - -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - { - file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); - } -#endif - - if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); - status = TINFL_STATUS_FAILED; - break; - } - - cur_file_ofs += read_buf_avail; - out_buf_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - } - } - } - else - { - tinfl_decompressor inflator; - tinfl_init(&inflator); - - if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - status = TINFL_STATUS_FAILED; - } - else - { - do - { - mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - status = TINFL_STATUS_FAILED; - break; - } - cur_file_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - read_buf_ofs = 0; - } - - in_buf_size = (size_t)read_buf_avail; - status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); - read_buf_avail -= in_buf_size; - read_buf_ofs += in_buf_size; - - if (out_buf_size) - { - if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) - { - mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); - status = TINFL_STATUS_FAILED; - break; - } - -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); -#endif - if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) - { - mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); - status = TINFL_STATUS_FAILED; - break; - } - } - } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); - } - } - - if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) - { - /* Make sure the entire file was decompressed, and check its CRC. */ - if (out_buf_ofs != file_stat.m_uncomp_size) - { - mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); - status = TINFL_STATUS_FAILED; - } -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - else if (file_crc32 != file_stat.m_crc32) - { - mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); - status = TINFL_STATUS_FAILED; - } -#endif - } - - if (!pZip->m_pState->m_pMem) - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - - if (pWrite_buf) - pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); - - return status == TINFL_STATUS_DONE; -} - -mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) -{ - mz_uint32 file_index; - if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) - return MZ_FALSE; - - return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); -} - -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) -{ - mz_zip_reader_extract_iter_state *pState; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - - /* Argument sanity check */ - if ((!pZip) || (!pZip->m_pState)) - return NULL; - - /* Allocate an iterator status structure */ - pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); - if (!pState) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - return NULL; - } - - /* Fetch file details */ - if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - /* Encryption and patch files are not supported. */ - if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) - { - mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - /* This function only supports decompressing stored and deflate. */ - if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) - { - mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - /* Init state - save args */ - pState->pZip = pZip; - pState->flags = flags; - - /* Init state - reset variables to defaults */ - pState->status = TINFL_STATUS_DONE; -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - pState->file_crc32 = MZ_CRC32_INIT; -#endif - pState->read_buf_ofs = 0; - pState->out_buf_ofs = 0; - pState->pRead_buf = NULL; - pState->pWrite_buf = NULL; - pState->out_blk_remain = 0; - - /* Read and parse the local directory entry. */ - pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - - /* Decompress the file either directly from memory or from a file input buffer. */ - if (pZip->m_pState->m_pMem) - { - pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; - pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; - pState->comp_remaining = pState->file_stat.m_comp_size; - } - else - { - if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) - { - /* Decompression required, therefore intermediate read buffer required */ - pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); - if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - } - else - { - /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ - pState->read_buf_size = 0; - } - pState->read_buf_avail = 0; - pState->comp_remaining = pState->file_stat.m_comp_size; - } - - if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) - { - /* Decompression required, init decompressor */ - tinfl_init( &pState->inflator ); - - /* Allocate write buffer */ - if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - if (pState->pRead_buf) - pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - return NULL; - } - } - - return pState; -} - -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) -{ - mz_uint32 file_index; - - /* Locate file index by name */ - if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) - return NULL; - - /* Construct iterator */ - return mz_zip_reader_extract_iter_new(pZip, file_index, flags); -} - -size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) -{ - size_t copied_to_caller = 0; - - /* Argument sanity check */ - if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) - return 0; - - if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) - { - /* The file is stored or the caller has requested the compressed data, calc amount to return. */ - copied_to_caller = MZ_MIN( buf_size, pState->comp_remaining ); - - /* Zip is in memory....or requires reading from a file? */ - if (pState->pZip->m_pState->m_pMem) - { - /* Copy data to caller's buffer */ - memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); - pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; - } - else - { - /* Read directly into caller's buffer */ - if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) - { - /* Failed to read all that was asked for, flag failure and alert user */ - mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); - pState->status = TINFL_STATUS_FAILED; - copied_to_caller = 0; - } - } - -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - /* Compute CRC if not returning compressed data only */ - if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); -#endif - - /* Advance offsets, dec counters */ - pState->cur_file_ofs += copied_to_caller; - pState->out_buf_ofs += copied_to_caller; - pState->comp_remaining -= copied_to_caller; - } - else - { - do - { - /* Calc ptr to write buffer - given current output pos and block size */ - mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - - /* Calc max output size - given current output pos and block size */ - size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - - if (!pState->out_blk_remain) - { - /* Read more data from file if none available (and reading from file) */ - if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) - { - /* Calc read size */ - pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); - if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) - { - mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); - pState->status = TINFL_STATUS_FAILED; - break; - } - - /* Advance offsets, dec counters */ - pState->cur_file_ofs += pState->read_buf_avail; - pState->comp_remaining -= pState->read_buf_avail; - pState->read_buf_ofs = 0; - } - - /* Perform decompression */ - in_buf_size = (size_t)pState->read_buf_avail; - pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); - pState->read_buf_avail -= in_buf_size; - pState->read_buf_ofs += in_buf_size; - - /* Update current output block size remaining */ - pState->out_blk_remain = out_buf_size; - } - - if (pState->out_blk_remain) - { - /* Calc amount to return. */ - size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); - - /* Copy data to caller's buffer */ - memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); - -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - /* Perform CRC */ - pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); -#endif - - /* Decrement data consumed from block */ - pState->out_blk_remain -= to_copy; - - /* Inc output offset, while performing sanity check */ - if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) - { - mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); - pState->status = TINFL_STATUS_FAILED; - break; - } - - /* Increment counter of data copied to caller */ - copied_to_caller += to_copy; - } - } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); - } - - /* Return how many bytes were copied into user buffer */ - return copied_to_caller; -} - -mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) -{ - int status; - - /* Argument sanity check */ - if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) - return MZ_FALSE; - - /* Was decompression completed and requested? */ - if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) - { - /* Make sure the entire file was decompressed, and check its CRC. */ - if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) - { - mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); - pState->status = TINFL_STATUS_FAILED; - } -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - else if (pState->file_crc32 != pState->file_stat.m_crc32) - { - mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); - pState->status = TINFL_STATUS_FAILED; - } -#endif - } - - /* Free buffers */ - if (!pState->pZip->m_pState->m_pMem) - pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); - if (pState->pWrite_buf) - pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); - - /* Save status */ - status = pState->status; - - /* Free context */ - pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); - - return status == TINFL_STATUS_DONE; -} - -#ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) -{ - (void)ofs; - - return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); -} - -mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) -{ - mz_bool status; - mz_zip_archive_file_stat file_stat; - MZ_FILE *pFile; - - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; - - if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); - - pFile = MZ_FOPEN(pDst_filename, "wb"); - if (!pFile) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - - status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); - - if (MZ_FCLOSE(pFile) == EOF) - { - if (status) - mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); - - status = MZ_FALSE; - } - -#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) - if (status) - mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); -#endif - - return status; -} - -mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) -{ - mz_uint32 file_index; - if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) - return MZ_FALSE; - - return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); -} - -mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) -{ - mz_zip_archive_file_stat file_stat; - - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; - - if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); - - return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); -} - -mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) -{ - mz_uint32 file_index; - if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) - return MZ_FALSE; - - return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); -} -#endif /* #ifndef MINIZ_NO_STDIO */ - -static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) -{ - mz_uint32 *p = (mz_uint32 *)pOpaque; - (void)file_ofs; - *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); - return n; -} - -mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) -{ - mz_zip_archive_file_stat file_stat; - mz_zip_internal_state *pState; - const mz_uint8 *pCentral_dir_header; - mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; - mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - mz_uint64 local_header_ofs = 0; - mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; - mz_uint64 local_header_comp_size, local_header_uncomp_size; - mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; - mz_bool has_data_descriptor; - mz_uint32 local_header_bit_flags; - - mz_zip_array file_data_array; - mz_zip_array_init(&file_data_array, 1); - - if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (file_index > pZip->m_total_files) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); - - if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) - return MZ_FALSE; - - /* A directory or zero length file */ - if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) - return MZ_TRUE; - - /* Encryption and patch files are not supported. */ - if (file_stat.m_is_encrypted) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); - - /* This function only supports stored and deflate. */ - if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); - - if (!file_stat.m_is_supported) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); - - /* Read and parse the local directory entry. */ - local_header_ofs = file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); - local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); - local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); - local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); - local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); - has_data_descriptor = (local_header_bit_flags & 8) != 0; - - if (local_header_filename_len != strlen(file_stat.m_filename)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - if (local_header_filename_len) - { - if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - goto handle_failure; - } - - /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ - if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) - { - mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); - goto handle_failure; - } - } - - if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) - { - mz_uint32 extra_size_remaining = local_header_extra_len; - const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; - - if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - goto handle_failure; - } - - do - { - mz_uint32 field_id, field_data_size, field_total_size; - - if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - field_id = MZ_READ_LE16(pExtra_data); - field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); - field_total_size = field_data_size + sizeof(mz_uint16) * 2; - - if (field_total_size > extra_size_remaining) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) - { - const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); - - if (field_data_size < sizeof(mz_uint64) * 2) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - goto handle_failure; - } - - local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); - local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); - - found_zip64_ext_data_in_ldir = MZ_TRUE; - break; - } - - pExtra_data += field_total_size; - extra_size_remaining -= field_total_size; - } while (extra_size_remaining); - } - - /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ - /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ - if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) - { - mz_uint8 descriptor_buf[32]; - mz_bool has_id; - const mz_uint8 *pSrc; - mz_uint32 file_crc32; - mz_uint64 comp_size = 0, uncomp_size = 0; - - mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; - - if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - goto handle_failure; - } - - has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); - pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; - - file_crc32 = MZ_READ_LE32(pSrc); - - if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) - { - comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); - uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); - } - else - { - comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); - uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); - } - - if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) - { - mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); - goto handle_failure; - } - } - else - { - if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) - { - mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); - goto handle_failure; - } - } - - mz_zip_array_clear(pZip, &file_data_array); - - if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) - { - if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) - return MZ_FALSE; - - /* 1 more check to be sure, although the extract checks too. */ - if (uncomp_crc32 != file_stat.m_crc32) - { - mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); - return MZ_FALSE; - } - } - - return MZ_TRUE; - -handle_failure: - mz_zip_array_clear(pZip, &file_data_array); - return MZ_FALSE; -} - -mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) -{ - mz_zip_internal_state *pState; - uint32_t i; - - if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - /* Basic sanity checks */ - if (!pState->m_zip64) - { - if (pZip->m_total_files > MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - if (pZip->m_archive_size > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - } - else - { - if (pZip->m_total_files >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - } - - for (i = 0; i < pZip->m_total_files; i++) - { - if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) - { - mz_uint32 found_index; - mz_zip_archive_file_stat stat; - - if (!mz_zip_reader_file_stat(pZip, i, &stat)) - return MZ_FALSE; - - if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) - return MZ_FALSE; - - /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ - if (found_index != i) - return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); - } - - if (!mz_zip_validate_file(pZip, i, flags)) - return MZ_FALSE; - } - - return MZ_TRUE; -} - -mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) -{ - mz_bool success = MZ_TRUE; - mz_zip_archive zip; - mz_zip_error actual_err = MZ_ZIP_NO_ERROR; - - if ((!pMem) || (!size)) - { - if (pErr) - *pErr = MZ_ZIP_INVALID_PARAMETER; - return MZ_FALSE; - } - - mz_zip_zero_struct(&zip); - - if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) - { - if (pErr) - *pErr = zip.m_last_error; - return MZ_FALSE; - } - - if (!mz_zip_validate_archive(&zip, flags)) - { - actual_err = zip.m_last_error; - success = MZ_FALSE; - } - - if (!mz_zip_reader_end_internal(&zip, success)) - { - if (!actual_err) - actual_err = zip.m_last_error; - success = MZ_FALSE; - } - - if (pErr) - *pErr = actual_err; - - return success; -} - -#ifndef MINIZ_NO_STDIO -mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) -{ - mz_bool success = MZ_TRUE; - mz_zip_archive zip; - mz_zip_error actual_err = MZ_ZIP_NO_ERROR; - - if (!pFilename) - { - if (pErr) - *pErr = MZ_ZIP_INVALID_PARAMETER; - return MZ_FALSE; - } - - mz_zip_zero_struct(&zip); - - if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) - { - if (pErr) - *pErr = zip.m_last_error; - return MZ_FALSE; - } - - if (!mz_zip_validate_archive(&zip, flags)) - { - actual_err = zip.m_last_error; - success = MZ_FALSE; - } - - if (!mz_zip_reader_end_internal(&zip, success)) - { - if (!actual_err) - actual_err = zip.m_last_error; - success = MZ_FALSE; - } - - if (pErr) - *pErr = actual_err; - - return success; -} -#endif /* #ifndef MINIZ_NO_STDIO */ - -/* ------------------- .ZIP archive writing */ - -#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS - -static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) -{ - p[0] = (mz_uint8)v; - p[1] = (mz_uint8)(v >> 8); -} -static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) -{ - p[0] = (mz_uint8)v; - p[1] = (mz_uint8)(v >> 8); - p[2] = (mz_uint8)(v >> 16); - p[3] = (mz_uint8)(v >> 24); -} -static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) -{ - mz_write_le32(p, (mz_uint32)v); - mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); -} - -#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) -#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) -#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) - -static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_zip_internal_state *pState = pZip->m_pState; - mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); - - if (!n) - return 0; - - /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ - if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); - return 0; - } - - if (new_size > pState->m_mem_capacity) - { - void *pNew_block; - size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); - - while (new_capacity < new_size) - new_capacity *= 2; - - if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) - { - mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - return 0; - } - - pState->m_pMem = pNew_block; - pState->m_mem_capacity = new_capacity; - } - memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); - pState->m_mem_size = (size_t)new_size; - return n; -} - -static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) -{ - mz_zip_internal_state *pState; - mz_bool status = MZ_TRUE; - - if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) - { - if (set_last_error) - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return MZ_FALSE; - } - - pState = pZip->m_pState; - pZip->m_pState = NULL; - mz_zip_array_clear(pZip, &pState->m_central_dir); - mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); - mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); - -#ifndef MINIZ_NO_STDIO - if (pState->m_pFile) - { - if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) - { - if (MZ_FCLOSE(pState->m_pFile) == EOF) - { - if (set_last_error) - mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); - status = MZ_FALSE; - } - } - - pState->m_pFile = NULL; - } -#endif /* #ifndef MINIZ_NO_STDIO */ - - if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); - pState->m_pMem = NULL; - } - - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; - return status; -} - -mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) -{ - mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; - - if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) - { - if (!pZip->m_pRead) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - if (pZip->m_file_offset_alignment) - { - /* Ensure user specified file offset alignment is a power of 2. */ - if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - if (!pZip->m_pAlloc) - pZip->m_pAlloc = miniz_def_alloc_func; - if (!pZip->m_pFree) - pZip->m_pFree = miniz_def_free_func; - if (!pZip->m_pRealloc) - pZip->m_pRealloc = miniz_def_realloc_func; - - pZip->m_archive_size = existing_size; - pZip->m_central_directory_file_ofs = 0; - pZip->m_total_files = 0; - - if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); - - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); - - pZip->m_pState->m_zip64 = zip64; - pZip->m_pState->m_zip64_has_extended_info_fields = zip64; - - pZip->m_zip_type = MZ_ZIP_TYPE_USER; - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) -{ - return mz_zip_writer_init_v2(pZip, existing_size, 0); -} - -mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) -{ - pZip->m_pWrite = mz_zip_heap_write_func; - pZip->m_pNeeds_keepalive = NULL; - - if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) - pZip->m_pRead = mz_zip_mem_read_func; - - pZip->m_pIO_opaque = pZip; - - if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) - return MZ_FALSE; - - pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; - - if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) - { - if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) - { - mz_zip_writer_end_internal(pZip, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - pZip->m_pState->m_mem_capacity = initial_allocation_size; - } - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) -{ - return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); -} - -#ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); - - file_ofs += pZip->m_pState->m_file_archive_start_ofs; - - if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); - return 0; - } - - return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); -} - -mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) -{ - return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); -} - -mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) -{ - MZ_FILE *pFile; - - pZip->m_pWrite = mz_zip_file_write_func; - pZip->m_pNeeds_keepalive = NULL; - - if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) - pZip->m_pRead = mz_zip_file_read_func; - - pZip->m_pIO_opaque = pZip; - - if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) - return MZ_FALSE; - - if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) - { - mz_zip_writer_end(pZip); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - } - - pZip->m_pState->m_pFile = pFile; - pZip->m_zip_type = MZ_ZIP_TYPE_FILE; - - if (size_to_reserve_at_beginning) - { - mz_uint64 cur_ofs = 0; - char buf[4096]; - - MZ_CLEAR_OBJ(buf); - - do - { - size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) - { - mz_zip_writer_end(pZip); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - cur_ofs += n; - size_to_reserve_at_beginning -= n; - } while (size_to_reserve_at_beginning); - } - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) -{ - pZip->m_pWrite = mz_zip_file_write_func; - pZip->m_pNeeds_keepalive = NULL; - - if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) - pZip->m_pRead = mz_zip_file_read_func; - - pZip->m_pIO_opaque = pZip; - - if (!mz_zip_writer_init_v2(pZip, 0, flags)) - return MZ_FALSE; - - pZip->m_pState->m_pFile = pFile; - pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); - pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; - - return MZ_TRUE; -} -#endif /* #ifndef MINIZ_NO_STDIO */ - -mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) -{ - mz_zip_internal_state *pState; - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) - { - /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ - if (!pZip->m_pState->m_zip64) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - /* No sense in trying to write to an archive that's already at the support max size */ - if (pZip->m_pState->m_zip64) - { - if (pZip->m_total_files == MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - else - { - if (pZip->m_total_files == MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - - if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); - } - - pState = pZip->m_pState; - - if (pState->m_pFile) - { -#ifdef MINIZ_NO_STDIO - (void)pFilename; - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); -#else - if (pZip->m_pIO_opaque != pZip) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) - { - if (!pFilename) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ - if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) - { - /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - } - } - - pZip->m_pWrite = mz_zip_file_write_func; - pZip->m_pNeeds_keepalive = NULL; -#endif /* #ifdef MINIZ_NO_STDIO */ - } - else if (pState->m_pMem) - { - /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ - if (pZip->m_pIO_opaque != pZip) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState->m_mem_capacity = pState->m_mem_size; - pZip->m_pWrite = mz_zip_heap_write_func; - pZip->m_pNeeds_keepalive = NULL; - } - /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ - else if (!pZip->m_pWrite) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* Start writing new files at the archive's current central directory location. */ - /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ - pZip->m_archive_size = pZip->m_central_directory_file_ofs; - pZip->m_central_directory_file_ofs = 0; - - /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ - /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ - /* TODO: We could easily maintain the sorted central directory offsets. */ - mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); - - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) -{ - return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); -} - -/* TODO: pArchive_name is a terrible name here! */ -mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) -{ - return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); -} - -typedef struct -{ - mz_zip_archive *m_pZip; - mz_uint64 m_cur_archive_file_ofs; - mz_uint64 m_comp_size; -} mz_zip_writer_add_state; - -static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) -{ - mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; - if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) - return MZ_FALSE; - - pState->m_cur_archive_file_ofs += len; - pState->m_comp_size += len; - return MZ_TRUE; -} - -#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) -#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) -static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) -{ - mz_uint8 *pDst = pBuf; - mz_uint32 field_size = 0; - - MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); - MZ_WRITE_LE16(pDst + 2, 0); - pDst += sizeof(mz_uint16) * 2; - - if (pUncomp_size) - { - MZ_WRITE_LE64(pDst, *pUncomp_size); - pDst += sizeof(mz_uint64); - field_size += sizeof(mz_uint64); - } - - if (pComp_size) - { - MZ_WRITE_LE64(pDst, *pComp_size); - pDst += sizeof(mz_uint64); - field_size += sizeof(mz_uint64); - } - - if (pLocal_header_ofs) - { - MZ_WRITE_LE64(pDst, *pLocal_header_ofs); - pDst += sizeof(mz_uint64); - field_size += sizeof(mz_uint64); - } - - MZ_WRITE_LE16(pBuf + 2, field_size); - - return (mz_uint32)(pDst - pBuf); -} - -static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) -{ - (void)pZip; - memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); - return MZ_TRUE; -} - -static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, - mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, - mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, - mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, - mz_uint64 local_header_ofs, mz_uint32 ext_attributes) -{ - (void)pZip; - memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); - return MZ_TRUE; -} - -static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, - const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, - mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, - mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, - mz_uint64 local_header_ofs, mz_uint32 ext_attributes, - const char *user_extra_data, mz_uint user_extra_data_len) -{ - mz_zip_internal_state *pState = pZip->m_pState; - mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; - size_t orig_central_dir_size = pState->m_central_dir.m_size; - mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; - - if (!pZip->m_pState->m_zip64) - { - if (local_header_ofs > 0xFFFFFFFF) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); - } - - /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ - if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - - if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size + user_extra_data_len, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) - { - /* Try to resize the central directory array back into its original state. */ - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - return MZ_TRUE; -} - -static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) -{ - /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ - if (*pArchive_name == '/') - return MZ_FALSE; - - while (*pArchive_name) - { - if ((*pArchive_name == '\\') || (*pArchive_name == ':')) - return MZ_FALSE; - - pArchive_name++; - } - - return MZ_TRUE; -} - -static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) -{ - mz_uint32 n; - if (!pZip->m_file_offset_alignment) - return 0; - n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); - return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); -} - -static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) -{ - char buf[4096]; - memset(buf, 0, MZ_MIN(sizeof(buf), n)); - while (n) - { - mz_uint32 s = MZ_MIN(sizeof(buf), n); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_file_ofs += s; - n -= s; - } - return MZ_TRUE; -} - -mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) -{ - return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); -} - -mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, - mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, - const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) -{ - mz_uint16 method = 0, dos_time = 0, dos_date = 0; - mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; - mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; - size_t archive_name_size; - mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; - tdefl_compressor *pComp = NULL; - mz_bool store_data_uncompressed; - mz_zip_internal_state *pState; - mz_uint8 *pExtra_data = NULL; - mz_uint32 extra_size = 0; - mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; - mz_uint16 bit_flags = 0; - - if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) - bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; - - if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) - bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; - - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - level = level_and_flags & 0xF; - store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - if (pState->m_zip64) - { - if (pZip->m_total_files == MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - else - { - if (pZip->m_total_files == MZ_UINT16_MAX) - { - pState->m_zip64 = MZ_TRUE; - /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ - } - if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) - { - pState->m_zip64 = MZ_TRUE; - /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ - } - } - - if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); - -#ifndef MINIZ_NO_TIME - if (last_modified != NULL) - { - mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); - } - else - { - MZ_TIME_T cur_time; - time(&cur_time); - mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); - } -#endif /* #ifndef MINIZ_NO_TIME */ - - archive_name_size = strlen(pArchive_name); - if (archive_name_size > MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); - - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - - /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ - if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - - if (!pState->m_zip64) - { - /* Bail early if the archive would obviously become too large */ - if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size - + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + - pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len - + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) - { - pState->m_zip64 = MZ_TRUE; - /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ - } - } - - if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) - { - /* Set DOS Subdirectory attribute bit. */ - ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; - - /* Subdirectories cannot contain data. */ - if ((buf_size) || (uncomp_size)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ - if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - if ((!store_data_uncompressed) && (buf_size)) - { - if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return MZ_FALSE; - } - - local_dir_header_ofs += num_alignment_padding_bytes; - if (pZip->m_file_offset_alignment) - { - MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); - } - cur_archive_file_ofs += num_alignment_padding_bytes; - - MZ_CLEAR_OBJ(local_dir_header); - - if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - { - method = MZ_DEFLATED; - } - - if (pState->m_zip64) - { - if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) - { - pExtra_data = extra_data; - extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, - (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); - } - - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += sizeof(local_dir_header); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - cur_archive_file_ofs += archive_name_size; - - if (pExtra_data != NULL) - { - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += extra_size; - } - } - else - { - if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += sizeof(local_dir_header); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - cur_archive_file_ofs += archive_name_size; - } - - if (user_extra_data_len > 0) - { - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += user_extra_data_len; - } - - if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - { - uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); - uncomp_size = buf_size; - if (uncomp_size <= 3) - { - level = 0; - store_data_uncompressed = MZ_TRUE; - } - } - - if (store_data_uncompressed) - { - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - - cur_archive_file_ofs += buf_size; - comp_size = buf_size; - } - else if (buf_size) - { - mz_zip_writer_add_state state; - - state.m_pZip = pZip; - state.m_cur_archive_file_ofs = cur_archive_file_ofs; - state.m_comp_size = 0; - - if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || - (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); - } - - comp_size = state.m_comp_size; - cur_archive_file_ofs = state.m_cur_archive_file_ofs; - } - - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - pComp = NULL; - - if (uncomp_size) - { - mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; - mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; - - MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); - - MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); - MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); - if (pExtra_data == NULL) - { - if (comp_size > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - MZ_WRITE_LE32(local_dir_footer + 8, comp_size); - MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); - } - else - { - MZ_WRITE_LE64(local_dir_footer + 8, comp_size); - MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); - local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; - } - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) - return MZ_FALSE; - - cur_archive_file_ofs += local_dir_footer_size; - } - - if (pExtra_data != NULL) - { - extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, - (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); - } - - if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment, - comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, - user_extra_data_central, user_extra_data_central_len)) - return MZ_FALSE; - - pZip->m_total_files++; - pZip->m_archive_size = cur_archive_file_ofs; - - return MZ_TRUE; -} - -#ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) -{ - mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; - mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; - mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; - mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; - size_t archive_name_size; - mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; - mz_uint8 *pExtra_data = NULL; - mz_uint32 extra_size = 0; - mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; - mz_zip_internal_state *pState; - - if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) - gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; - - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - level = level_and_flags & 0xF; - - /* Sanity checks */ - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) - { - /* Source file is too large for non-zip64 */ - /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ - pState->m_zip64 = MZ_TRUE; - } - - /* We could support this, but why? */ - if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); - - if (pState->m_zip64) - { - if (pZip->m_total_files == MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - else - { - if (pZip->m_total_files == MZ_UINT16_MAX) - { - pState->m_zip64 = MZ_TRUE; - /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ - } - } - - archive_name_size = strlen(pArchive_name); - if (archive_name_size > MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); - - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - - /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ - if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - - if (!pState->m_zip64) - { - /* Bail early if the archive would obviously become too large */ - if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE - + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 - + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) - { - pState->m_zip64 = MZ_TRUE; - /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ - } - } - -#ifndef MINIZ_NO_TIME - if (pFile_time) - { - mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); - } -#endif - - if (uncomp_size <= 3) - level = 0; - - if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) - { - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - - cur_archive_file_ofs += num_alignment_padding_bytes; - local_dir_header_ofs = cur_archive_file_ofs; - - if (pZip->m_file_offset_alignment) - { - MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); - } - - if (uncomp_size && level) - { - method = MZ_DEFLATED; - } - - MZ_CLEAR_OBJ(local_dir_header); - if (pState->m_zip64) - { - if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) - { - pExtra_data = extra_data; - extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, - (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); - } - - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += sizeof(local_dir_header); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - - cur_archive_file_ofs += archive_name_size; - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += extra_size; - } - else - { - if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += sizeof(local_dir_header); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - - cur_archive_file_ofs += archive_name_size; - } - - if (user_extra_data_len > 0) - { - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_archive_file_ofs += user_extra_data_len; - } - - if (uncomp_size) - { - mz_uint64 uncomp_remaining = uncomp_size; - void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); - if (!pRead_buf) - { - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (!level) - { - while (uncomp_remaining) - { - mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); - if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - } - uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); - uncomp_remaining -= n; - cur_archive_file_ofs += n; - } - comp_size = uncomp_size; - } - else - { - mz_bool result = MZ_FALSE; - mz_zip_writer_add_state state; - tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); - if (!pComp) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - state.m_pZip = pZip; - state.m_cur_archive_file_ofs = cur_archive_file_ofs; - state.m_comp_size = 0; - - if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); - } - - for (;;) - { - size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); - tdefl_status status; - tdefl_flush flush = TDEFL_NO_FLUSH; - - if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) - { - mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - break; - } - - uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); - uncomp_remaining -= in_buf_size; - - if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) - flush = TDEFL_FULL_FLUSH; - - status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); - if (status == TDEFL_STATUS_DONE) - { - result = MZ_TRUE; - break; - } - else if (status != TDEFL_STATUS_OKAY) - { - mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); - break; - } - } - - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - - if (!result) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - return MZ_FALSE; - } - - comp_size = state.m_comp_size; - cur_archive_file_ofs = state.m_cur_archive_file_ofs; - } - - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - } - - { - mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; - mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; - - MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); - MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); - if (pExtra_data == NULL) - { - if (comp_size > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - MZ_WRITE_LE32(local_dir_footer + 8, comp_size); - MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); - } - else - { - MZ_WRITE_LE64(local_dir_footer + 8, comp_size); - MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); - local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; - } - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) - return MZ_FALSE; - - cur_archive_file_ofs += local_dir_footer_size; - } - - if (pExtra_data != NULL) - { - extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, - (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); - } - - if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment, comment_size, - uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, - user_extra_data_central, user_extra_data_central_len)) - return MZ_FALSE; - - pZip->m_total_files++; - pZip->m_archive_size = cur_archive_file_ofs; - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) -{ - MZ_FILE *pSrc_file = NULL; - mz_uint64 uncomp_size = 0; - MZ_TIME_T file_modified_time; - MZ_TIME_T *pFile_time = NULL; - mz_bool status; - - memset(&file_modified_time, 0, sizeof(file_modified_time)); - -#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) - pFile_time = &file_modified_time; - if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); -#endif - - pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); - if (!pSrc_file) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - - MZ_FSEEK64(pSrc_file, 0, SEEK_END); - uncomp_size = MZ_FTELL64(pSrc_file); - MZ_FSEEK64(pSrc_file, 0, SEEK_SET); - - status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); - - MZ_FCLOSE(pSrc_file); - - return status; -} -#endif /* #ifndef MINIZ_NO_STDIO */ - -static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) -{ - /* + 64 should be enough for any new zip64 data */ - if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); - - if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) - { - mz_uint8 new_ext_block[64]; - mz_uint8 *pDst = new_ext_block; - mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); - mz_write_le16(pDst + sizeof(mz_uint16), 0); - pDst += sizeof(mz_uint16) * 2; - - if (pUncomp_size) - { - mz_write_le64(pDst, *pUncomp_size); - pDst += sizeof(mz_uint64); - } - - if (pComp_size) - { - mz_write_le64(pDst, *pComp_size); - pDst += sizeof(mz_uint64); - } - - if (pLocal_header_ofs) - { - mz_write_le64(pDst, *pLocal_header_ofs); - pDst += sizeof(mz_uint64); - } - - if (pDisk_start) - { - mz_write_le32(pDst, *pDisk_start); - pDst += sizeof(mz_uint32); - } - - mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); - - if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if ((pExt) && (ext_len)) - { - mz_uint32 extra_size_remaining = ext_len; - const mz_uint8 *pExtra_data = pExt; - - do - { - mz_uint32 field_id, field_data_size, field_total_size; - - if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - field_id = MZ_READ_LE16(pExtra_data); - field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); - field_total_size = field_data_size + sizeof(mz_uint16) * 2; - - if (field_total_size > extra_size_remaining) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) - { - if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - pExtra_data += field_total_size; - extra_size_remaining -= field_total_size; - } while (extra_size_remaining); - } - - return MZ_TRUE; -} - -/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ -mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) -{ - mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; - mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; - mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; - mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; - size_t orig_central_dir_size; - mz_zip_internal_state *pState; - void *pBuf; - const mz_uint8 *pSrc_central_header; - mz_zip_archive_file_stat src_file_stat; - mz_uint32 src_filename_len, src_comment_len, src_ext_len; - mz_uint32 local_header_filename_size, local_header_extra_len; - mz_uint64 local_header_comp_size, local_header_uncomp_size; - mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; - - /* Sanity checks */ - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ - if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* Get pointer to the source central dir header and crack it */ - if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); - src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); - src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); - src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; - - /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ - if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - - if (!pState->m_zip64) - { - if (pZip->m_total_files == MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - else - { - /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ - if (pZip->m_total_files == MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - - if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) - return MZ_FALSE; - - cur_src_file_ofs = src_file_stat.m_local_header_ofs; - cur_dst_file_ofs = pZip->m_archive_size; - - /* Read the source archive's local dir header */ - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - - cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; - - /* Compute the total size we need to copy (filename+extra data+compressed data) */ - local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); - local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); - local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); - src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; - - /* Try to find a zip64 extended information field */ - if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) - { - mz_zip_array file_data_array; - const mz_uint8 *pExtra_data; - mz_uint32 extra_size_remaining = local_header_extra_len; - - mz_zip_array_init(&file_data_array, 1); - if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) - { - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) - { - mz_zip_array_clear(pZip, &file_data_array); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - } - - pExtra_data = (const mz_uint8 *)file_data_array.m_p; - - do - { - mz_uint32 field_id, field_data_size, field_total_size; - - if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - { - mz_zip_array_clear(pZip, &file_data_array); - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - } - - field_id = MZ_READ_LE16(pExtra_data); - field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); - field_total_size = field_data_size + sizeof(mz_uint16) * 2; - - if (field_total_size > extra_size_remaining) - { - mz_zip_array_clear(pZip, &file_data_array); - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - } - - if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) - { - const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); - - if (field_data_size < sizeof(mz_uint64) * 2) - { - mz_zip_array_clear(pZip, &file_data_array); - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - } - - local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); - local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ - - found_zip64_ext_data_in_ldir = MZ_TRUE; - break; - } - - pExtra_data += field_total_size; - extra_size_remaining -= field_total_size; - } while (extra_size_remaining); - - mz_zip_array_clear(pZip, &file_data_array); - } - - if (!pState->m_zip64) - { - /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ - /* We also check when the archive is finalized so this doesn't need to be perfect. */ - mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + - pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; - - if (approx_new_archive_size >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - } - - /* Write dest archive padding */ - if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) - return MZ_FALSE; - - cur_dst_file_ofs += num_alignment_padding_bytes; - - local_dir_header_ofs = cur_dst_file_ofs; - if (pZip->m_file_offset_alignment) - { - MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); - } - - /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; - - /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ - if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - while (src_archive_bytes_remaining) - { - n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - } - cur_src_file_ofs += n; - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - cur_dst_file_ofs += n; - - src_archive_bytes_remaining -= n; - } - - /* Now deal with the optional data descriptor */ - bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); - if (bit_flags & 8) - { - /* Copy data descriptor */ - if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) - { - /* src is zip64, dest must be zip64 */ - - /* name uint32_t's */ - /* id 1 (optional in zip64?) */ - /* crc 1 */ - /* comp_size 2 */ - /* uncomp_size 2 */ - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - } - - n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); - } - else - { - /* src is NOT zip64 */ - mz_bool has_id; - - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - } - - has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); - - if (pZip->m_pState->m_zip64) - { - /* dest is zip64, so upgrade the data descriptor */ - const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); - const mz_uint32 src_crc32 = pSrc_descriptor[0]; - const mz_uint64 src_comp_size = pSrc_descriptor[1]; - const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; - - mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); - mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); - mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); - mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); - - n = sizeof(mz_uint32) * 6; - } - else - { - /* dest is NOT zip64, just copy it as-is */ - n = sizeof(mz_uint32) * (has_id ? 4 : 3); - } - } - - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - } - - cur_src_file_ofs += n; - cur_dst_file_ofs += n; - } - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - - /* Finally, add the new central dir header */ - orig_central_dir_size = pState->m_central_dir.m_size; - - memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); - - if (pState->m_zip64) - { - /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ - const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; - mz_zip_array new_ext_block; - - mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); - - MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); - MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); - MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); - - if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) - { - mz_zip_array_clear(pZip, &new_ext_block); - return MZ_FALSE; - } - - MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) - { - mz_zip_array_clear(pZip, &new_ext_block); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) - { - mz_zip_array_clear(pZip, &new_ext_block); - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) - { - mz_zip_array_clear(pZip, &new_ext_block); - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) - { - mz_zip_array_clear(pZip, &new_ext_block); - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - mz_zip_array_clear(pZip, &new_ext_block); - } - else - { - /* sanity checks */ - if (cur_dst_file_ofs > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - if (local_dir_header_ofs >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - - MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) - { - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - } - - /* This shouldn't trigger unless we screwed up during the initial sanity checks */ - if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) - { - /* TODO: Support central dirs >= 32-bits in size */ - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); - } - - n = (mz_uint32)orig_central_dir_size; - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) - { - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); - } - - pZip->m_total_files++; - pZip->m_archive_size = cur_dst_file_ofs; - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) -{ - mz_zip_internal_state *pState; - mz_uint64 central_dir_ofs, central_dir_size; - mz_uint8 hdr[256]; - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState = pZip->m_pState; - - if (pState->m_zip64) - { - if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - else - { - if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } - - central_dir_ofs = 0; - central_dir_size = 0; - if (pZip->m_total_files) - { - /* Write central directory */ - central_dir_ofs = pZip->m_archive_size; - central_dir_size = pState->m_central_dir.m_size; - pZip->m_central_directory_file_ofs = central_dir_ofs; - if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - pZip->m_archive_size += central_dir_size; - } - - if (pState->m_zip64) - { - /* Write zip64 end of central directory header */ - mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; - - MZ_CLEAR_OBJ(hdr); - MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); - MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ - MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); - if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; - - /* Write zip64 end of central directory locator */ - MZ_CLEAR_OBJ(hdr); - MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); - MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); - MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); - if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - - pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; - } - - /* Write end of central directory record */ - MZ_CLEAR_OBJ(hdr); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); - MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); - -#ifndef MINIZ_NO_STDIO - if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); -#endif /* #ifndef MINIZ_NO_STDIO */ - - pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; - - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; - return MZ_TRUE; -} - -mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) -{ - if ((!ppBuf) || (!pSize)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - *ppBuf = NULL; - *pSize = 0; - - if ((!pZip) || (!pZip->m_pState)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (pZip->m_pWrite != mz_zip_heap_write_func) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (!mz_zip_writer_finalize_archive(pZip)) - return MZ_FALSE; - - *ppBuf = pZip->m_pState->m_pMem; - *pSize = pZip->m_pState->m_mem_size; - pZip->m_pState->m_pMem = NULL; - pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; - - return MZ_TRUE; -} - -mz_bool mz_zip_writer_end(mz_zip_archive *pZip) -{ - return mz_zip_writer_end_internal(pZip, MZ_TRUE); -} - -#ifndef MINIZ_NO_STDIO -mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) -{ - return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); -} - -mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) -{ - mz_bool status, created_new_archive = MZ_FALSE; - mz_zip_archive zip_archive; - struct MZ_FILE_STAT_STRUCT file_stat; - mz_zip_error actual_err = MZ_ZIP_NO_ERROR; - - mz_zip_zero_struct(&zip_archive); - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - - if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) - { - if (pErr) - *pErr = MZ_ZIP_INVALID_PARAMETER; - return MZ_FALSE; - } - - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - { - if (pErr) - *pErr = MZ_ZIP_INVALID_FILENAME; - return MZ_FALSE; - } - - /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ - /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ - if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) - { - /* Create a new archive. */ - if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) - { - if (pErr) - *pErr = zip_archive.m_last_error; - return MZ_FALSE; - } - - created_new_archive = MZ_TRUE; - } - else - { - /* Append to an existing archive. */ - if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) - { - if (pErr) - *pErr = zip_archive.m_last_error; - return MZ_FALSE; - } - - if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) - { - if (pErr) - *pErr = zip_archive.m_last_error; - - mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); - - return MZ_FALSE; - } - } - - status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); - actual_err = zip_archive.m_last_error; - - /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ - if (!mz_zip_writer_finalize_archive(&zip_archive)) - { - if (!actual_err) - actual_err = zip_archive.m_last_error; - - status = MZ_FALSE; - } - - if (!mz_zip_writer_end_internal(&zip_archive, status)) - { - if (!actual_err) - actual_err = zip_archive.m_last_error; - - status = MZ_FALSE; - } - - if ((!status) && (created_new_archive)) - { - /* It's a new archive and something went wrong, so just delete it. */ - int ignoredStatus = MZ_DELETE_FILE(pZip_filename); - (void)ignoredStatus; - } - - if (pErr) - *pErr = actual_err; - - return status; -} - -void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) -{ - mz_uint32 file_index; - mz_zip_archive zip_archive; - void *p = NULL; - - if (pSize) - *pSize = 0; - - if ((!pZip_filename) || (!pArchive_name)) - { - if (pErr) - *pErr = MZ_ZIP_INVALID_PARAMETER; - - return NULL; - } - - mz_zip_zero_struct(&zip_archive); - if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) - { - if (pErr) - *pErr = zip_archive.m_last_error; - - return NULL; - } - - if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) - { - p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); - } - - mz_zip_reader_end_internal(&zip_archive, p != NULL); - - if (pErr) - *pErr = zip_archive.m_last_error; - - return p; -} - -void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) -{ - return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); -} - -#endif /* #ifndef MINIZ_NO_STDIO */ - -#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ - -/* ------------------- Misc utils */ - -mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) -{ - return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; -} - -mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) -{ - return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; -} - -mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) -{ - mz_zip_error prev_err; - - if (!pZip) - return MZ_ZIP_INVALID_PARAMETER; - - prev_err = pZip->m_last_error; - - pZip->m_last_error = err_num; - return prev_err; -} - -mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) -{ - if (!pZip) - return MZ_ZIP_INVALID_PARAMETER; - - return pZip->m_last_error; -} - -mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) -{ - return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); -} - -mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) -{ - mz_zip_error prev_err; - - if (!pZip) - return MZ_ZIP_INVALID_PARAMETER; - - prev_err = pZip->m_last_error; - - pZip->m_last_error = MZ_ZIP_NO_ERROR; - return prev_err; -} - -const char *mz_zip_get_error_string(mz_zip_error mz_err) -{ - switch (mz_err) - { - case MZ_ZIP_NO_ERROR: - return "no error"; - case MZ_ZIP_UNDEFINED_ERROR: - return "undefined error"; - case MZ_ZIP_TOO_MANY_FILES: - return "too many files"; - case MZ_ZIP_FILE_TOO_LARGE: - return "file too large"; - case MZ_ZIP_UNSUPPORTED_METHOD: - return "unsupported method"; - case MZ_ZIP_UNSUPPORTED_ENCRYPTION: - return "unsupported encryption"; - case MZ_ZIP_UNSUPPORTED_FEATURE: - return "unsupported feature"; - case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: - return "failed finding central directory"; - case MZ_ZIP_NOT_AN_ARCHIVE: - return "not a ZIP archive"; - case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: - return "invalid header or archive is corrupted"; - case MZ_ZIP_UNSUPPORTED_MULTIDISK: - return "unsupported multidisk archive"; - case MZ_ZIP_DECOMPRESSION_FAILED: - return "decompression failed or archive is corrupted"; - case MZ_ZIP_COMPRESSION_FAILED: - return "compression failed"; - case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: - return "unexpected decompressed size"; - case MZ_ZIP_CRC_CHECK_FAILED: - return "CRC-32 check failed"; - case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: - return "unsupported central directory size"; - case MZ_ZIP_ALLOC_FAILED: - return "allocation failed"; - case MZ_ZIP_FILE_OPEN_FAILED: - return "file open failed"; - case MZ_ZIP_FILE_CREATE_FAILED: - return "file create failed"; - case MZ_ZIP_FILE_WRITE_FAILED: - return "file write failed"; - case MZ_ZIP_FILE_READ_FAILED: - return "file read failed"; - case MZ_ZIP_FILE_CLOSE_FAILED: - return "file close failed"; - case MZ_ZIP_FILE_SEEK_FAILED: - return "file seek failed"; - case MZ_ZIP_FILE_STAT_FAILED: - return "file stat failed"; - case MZ_ZIP_INVALID_PARAMETER: - return "invalid parameter"; - case MZ_ZIP_INVALID_FILENAME: - return "invalid filename"; - case MZ_ZIP_BUF_TOO_SMALL: - return "buffer too small"; - case MZ_ZIP_INTERNAL_ERROR: - return "internal error"; - case MZ_ZIP_FILE_NOT_FOUND: - return "file not found"; - case MZ_ZIP_ARCHIVE_TOO_LARGE: - return "archive is too large"; - case MZ_ZIP_VALIDATION_FAILED: - return "validation failed"; - case MZ_ZIP_WRITE_CALLBACK_FAILED: - return "write calledback failed"; - default: - break; - } - - return "unknown error"; -} - -/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ -mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) -{ - if ((!pZip) || (!pZip->m_pState)) - return MZ_FALSE; - - return pZip->m_pState->m_zip64; -} - -size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) -{ - if ((!pZip) || (!pZip->m_pState)) - return 0; - - return pZip->m_pState->m_central_dir.m_size; -} - -mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) -{ - return pZip ? pZip->m_total_files : 0; -} - -mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) -{ - if (!pZip) - return 0; - return pZip->m_archive_size; -} - -mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) -{ - if ((!pZip) || (!pZip->m_pState)) - return 0; - return pZip->m_pState->m_file_archive_start_ofs; -} - -MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) -{ - if ((!pZip) || (!pZip->m_pState)) - return 0; - return pZip->m_pState->m_pFile; -} - -size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) -{ - if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); -} - -mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) -{ - mz_uint n; - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); - if (!p) - { - if (filename_buf_size) - pFilename[0] = '\0'; - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - return 0; - } - n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - if (filename_buf_size) - { - n = MZ_MIN(n, filename_buf_size - 1); - memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); - pFilename[n] = '\0'; - } - return n + 1; -} - -mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) -{ - return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); -} - -mz_bool mz_zip_end(mz_zip_archive *pZip) -{ - if (!pZip) - return MZ_FALSE; - - if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) - return mz_zip_reader_end(pZip); -#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS - else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) - return mz_zip_writer_end(pZip); -#endif - - return MZ_FALSE; -} - -#ifdef __cplusplus -} -#endif - -#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ -/* end of file miniz-sgb.c */ -#endif -/* ^^^ S2_INTERNAL_MINIZ */ -/* start of file /home/stephan/fossil/cwal/cwal_amalgamation.c */ -#include "string.h" /*memset()*/ -#include "assert.h" -/* start of file cwal_internal.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -#if !defined(WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED 1 -/** - This file documents the internal/private API of libcwal. It's - strictly internal parts are only in the main implementation file, - but the parts in this file are thought to potentially be useful in - future sub-systems which live somewhere between the private API and - public API (not quite public, not quite core). -*/ - -#if defined(__cplusplus) -extern "C" { -#endif - -/** @internal - - CWAL_UNUSED_VAR exists only to squelch, in non-debug builds, - warnings about the existence of vars and function parameters which - are only used in assert() expressions (and thus get filtered out in - non-debug builds). -*/ -#define CWAL_UNUSED_VAR __attribute__((__unused__)) /* avoiding unused var in non-debug build */ - -/** - A callback type for use with cwal_ptr_table_visit(). The elem - argument is the entry being visited over. The state argument is - whatever state the caller passed to cwal_ptr_table_visit(). - - If a visitor function returns non-0, iteration ceases and that - result code is returned to the caller of cwal_ptr_table_visit(). - - Hypothetically, it is legal for a visitor to overwrite *elem, but - that feature is not used in cwal's internals and might or might not - work (or be semantically legal) in any other context. -*/ -typedef int (*cwal_ptr_table_visitor_f)( cwal_value ** elem, void * state ); - -/** @internal - Operation values for cwal_ptr_table_op(). -*/ -enum cwal_ptr_table_ops { - /** Indicates that cwal_ptr_table_op() should remove the requested - item. */ - CWAL_PTR_TABLE_OP_REMOVE = -1, - /** Indicates that cwal_ptr_table_op() should search for the - requested item. */ - CWAL_PTR_TABLE_OP_SEARCH = 0, - /** Indicates that cwal_ptr_table_op() should insert the requested - item. */ - CWAL_PTR_TABLE_OP_INSERT = 1 -}; -typedef enum cwal_ptr_table_ops cwal_ptr_table_ops; - -/** @internal - - Initializes a new pointer table. *T may be NULL, in which case this - function allocates a new cwal_ptr_table. If *T is not NULL then it is - assumed to be a freshly-allocated, non-initialized pointer table - and is initialized as-is. - - Returns 0 on error. On success *T is set to the ptr table (possibly - freshly allocated) and ownership is transfered to the caller, who - must eventually call cwal_ptr_table_destroy() to free its resources. -*/ -int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T, uint16_t hashSize, uint16_t step ); - -/** @internal - - Frees all resources owned by t. t must have been initialized by - cwal_ptr_table_create(), and if that function allocated t, then t - will also be free()'d, otherwise the caller must free t using the - method which complements its allocation technique (e.g. do nothing - for stack-allocated values). - - Returns 0 on success. -*/ -int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t ); - -/** @internal - - Performs a search/remove/insert operation (specified by the op - parameter) on the given ptr table. Returns 0 on success. - - The valid operations are: - - CWAL_PTR_TABLE_OP_REMOVE: remove the given key from the table. - Returns CWAL_RC_NOT_FOUND, and has no side effects, if no matching - entry is found. - - CWAL_PTR_TABLE_OP_SEARCH: If a match is found returns 0, else - returns CWAL_RC_NOT_FOUND. - - CWAL_PTR_TABLE_OP_INSERT: insert the given key into the - table. Returns CWAL_RC_ALREADY_EXISTS, and has no side effects, if - key is already in the table. -*/ -int cwal_ptr_table_op( cwal_engine * e, cwal_ptr_table * t, void * key, cwal_ptr_table_ops op ); - -/** @internal - Equivalent to cwal_ptr_table_op(), passing it (e,t,key,CWAL_PTR_TABLE_OP_SEARCH). -*/ -int cwal_ptr_table_search( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); - -/** @internal - Equivalent to cwal_ptr_table_op(), passing it (e,t,key,CWAL_PTR_TABLE_OP_REMOVE). -*/ -int cwal_ptr_table_remove( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); - -/** @internal - Equivalent to cwal_ptr_table_op(), passing it (e,t,key,CWAL_PTR_TABLE_OP_INSERT). -*/ -int cwal_ptr_table_insert( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); - -/** @internal - - Calculates the amount of memory allocated for use with the given - table. The results are stored in *mallocs (the total number of - memory allocation calls) and *memory (the total number of bytes - allocated for use with t). Either of mallocs or memory may be - NULL. This is an O(N) operation, where N is the number of pages - currently managed by t. - */ -int cwal_ptr_table_mem_cost( cwal_ptr_table const * t, uint32_t * mallocs, uint32_t * memory ); - -/** @internal - - "Visits" each entry in t, calling the given callback for each one - (NULL entries in the table are skipped - only "live" entries are - iterated over). The callback is passed a pointer to a pointer to - the original entry, and if that pointer is re-assigned, that - change is written back to the table when the visitor - returns. (Why? i'm not sure why it was done that way - it is a - dangerous feature and should not be used.) - - Returns 0 on success. if the visitor returns non-0, that code is - returned to the caller of this function. -*/ -int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state ); - - -/** @internal - - Strings are allocated as an instances of this class with N+1 - trailing bytes, where N is the length, in bytes, of the string - being allocated. To convert a cwal_string to c-string we simply - increment the cwal_string pointer. To do the opposite we use (cstr - - sizeof(cwal_string)). Zero-length strings are a special case - handled by a couple of the cwal_string functions. - - The top-most 3 bits of the length field are reserved for - flags. This is how we mark "x-strings" (external strings, held in - memory allocated elsewhere (e.g. static globals or client-interned - strings), z-strings (whose memory comes from cwal_malloc() but is - allocated separately from the string instance (we use this for - "taking" memory from buffer instances), and (as a performance - optimization in some algorithms) ASCII-only strings. -*/ -struct cwal_string { - cwal_size_t length; -}; - -/** @internal - - Internal state flags. Keep these at 16 bits (and use only the - bottom 8 unless you know a given type supports it!) or adjust - various flags types accordingly! Note that some flags apply only - to particular types, and thus certain flags might have the same - values to conserve bit-space. -*/ -enum cwal_internal_flags { -/** - Sentinel value for empty flags. - */ - CWAL_F_NONE = 0, - /** - Used by cwal_value_vtab::flags to indicate that the type contains - a cwal_obase as its first struct member and therefore qualifies - as an "obase" (object base) sub-type. This is a casting-related - optimization. - - "OBases" must meet these requirements: - - - It is allocated using the library-conventional single-malloc - method of (cwal_value,concrete_type) in a single block. See - cwal_value_new() for the many examples. - - - Its concrete value type has a cwal_obase struct as its first - member. This, in combination with the first requirement, allows - us (due to an interesting C rule) to efficiently/safely cast - directly between a cwal_value, its "object type," and a - convenience base type (cwal_obase) which is used in many internal - algorithms to consolidate code for container handling. And... we - don't hold an extra pointer (or two) for this - it comes for free - as a side-effect of these first 2 requirements. - - See cwal_object and cwal_array for examples of using the obase - flag. - - Reminder to self: we can re-use this slot for flags on - cwal_engine, cwal_obase, and cwal_scope :). - */ - CWAL_F_ISA_OBASE = 0x01, - - /** - Set on cwal_engine instances during init if their memcap config - is enabled, to speed up some if/else checking in the allocator. - */ - CWAL_F_TRACK_MEM_SIZE = 0x02, - - /** - Is used as an extra safety mechanism to avoid a double-delete if - the refcounts-with-cycles-counting mechanism breaks. This only - means that we convert a double free() into a leak (or an assert() - in debug builds). - - Used in cwal_engine::flags and cwal_scope::flags. Values use a - different flag with similar semantics. - */ - CWAL_F_IS_DESTRUCTING = 0x04, - - /** - Used for marking cwal_scope::props (via its cwal_obase::flags) to - avoid some weird corner cases involving the vacuum-safe flag - when/if the variable storage Objects are exposed to - clients/scripts. It's needed to be able to differentiate this - case from vacuum-safe in order to avoid a corner case if the - client explicitly sets/unsets the vacuum-safe flag on a prop - storage container. - */ - CWAL_F_IS_PROP_STORAGE = 0x10, - - /** - A flag to briefly note that a value is temporarily locked, e.g. - currently being sorted, and must not be traversed or modified. - As of this writing (20191211), only arrays are/need to be locked - in a way distinct from the is-visiting flag, so we we "could" - re-use this flag's value with other semantics for non-array - types, if needed. - */ - CWAL_F_LOCKED = 0x20 -}; - -/** @internal - Convenience typedef. */ -typedef struct cwal_htable cwal_htable; -/** @internal - An internal helper class for building hashtables of cwal_kvp - entries. -*/ -struct cwal_htable { - /** - Array (hash table) of (cwal_kvp*) values. Its .count property - holds the current total number of entries (noting that they won't - typically be in contiguous list slots). Its .alloced property - holds the length (in entries) of the table. Its .list member is - the raw array storage. - */ - cwal_list list; - /** - Side effect of the chunk recycler: we need to be able to - differentiate between the hashtable size and the amount of memory - allocated for it (which might be larger). This value be less than - or equal to this->list.alloced. - */ - cwal_midsize_t hashSize; -}; -/** A clean cwal_htable instance for cost-copy initialization. */ -#define cwal_htable_empty_m {cwal_list_empty_m,0} - -/** @internal - Convenience typedef. */ -typedef struct cwal_obase cwal_obase; - -/** @internal - "Base" type for types which contain other values (which means they - are subject to cycles). An instance of this struct must be embedded - at the FIRST MEMBER of any such class, and any type-specific - members must come after that. See cwal_array and cwal_object for - examples. - */ -struct cwal_obase { -#if CWAL_OBASE_ISA_HASH - /** - Hashtable of object-level properties. - */ - cwal_htable hprops; -#else - /** - Linked list of key/value pairs held by this object. - */ - cwal_kvp * kvp; -#endif - /** - The "prototype" (analog to a parent class). - */ - cwal_value * prototype; - /** - Internal flags. - - Maintenance note: due to struct padding, this can be either 16 - or 32-bits with no real change in struct size on 64-bit, but - increasing either flags field on 32-bit increases the sizeof - (4 bytes if we increase both fields to 32 bits). Internally we - currently need 16 bits for flags. - - As of 20141205, we are using the top few bits of these via - cwal_container_flags, exposing/modifying the upper byte of - these flags via cwal_container_flags_set() and - cwal_container_flags_get(). - - As of 20191212, we desperately need more room for flags, but - cannot do so without increasing the sizeof by up to 4 on 32-bit - builds, from 12 to 16. We may just have to eat that cost :/. - On 64-bit builds that change wouldn't change the sizeof(). - */ - cwal_flags16_t flags; - /** - Engine-internal flags specifically for container-type-specific - behaviour. - */ - cwal_flags16_t containerFlags; - - /** - Holds client-specified flags. - */ - cwal_flags16_t clientFlags; - - cwal_flags16_t reservedPadding; -}; - -/** @internal - Empty-initialized cwal_obase object. -*/ -#if CWAL_OBASE_ISA_HASH -# define cwal_obase_empty_m {\ - cwal_htable_empty_m/*hprops*/, NULL/*prototype*/,CWAL_F_NONE/*flags*/,\ - 0/*containerFlags*/, 0/*clientFlags*/, 0/*reservedPadding*/\ - } -#else -# define cwal_obase_empty_m { \ - NULL/*kvp*/, NULL/*prototype*/, CWAL_F_NONE/*flags*/,\ - 0/*containerFlags*/, 0/*clientFlags*/, 0/*reservedPadding*/\ - } -#endif - -/** @internal - - Concrete value type for Arrays (type CWAL_TYPE_ARRAY). -*/ -struct cwal_array { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - /** - Holds (cwal_value*). NULL entries ARE semantically legal. - */ - cwal_list list; -}; -/** - Empty-initialized cwal_array object. -*/ -#define cwal_array_empty_m { cwal_obase_empty_m, cwal_list_empty_m } -/** - Empty-initialized cwal_array object. -*/ -extern const cwal_array cwal_array_empty; - -/** @internal - - The metadata for concrete Object values (type CWAL_TYPE_OBJECT). - */ -struct cwal_object { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; -}; -/** - Empty-initialized cwal_object object. -*/ -#define cwal_object_empty_m { cwal_obase_empty_m } -/** - Empty-initialized cwal_object object. -*/ -extern const cwal_object cwal_object_empty; - -/** - Information for binding a native function to the script engine in - the form of a Function value (type CWAL_TYPE_FUNCTION). -*/ -struct cwal_function { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - /** - Client-defined state for the function. - */ - cwal_state state; - - /** - The concrete callback implementation. - */ - cwal_callback_f callback; - - /** - "Rescoper" for the function. Can be set via - cwal_function_set_rescoper(), and gives the client a way to - rescope any function-private data (stored in this struct's - state member) if needed. - - Use case: s2's s2_func_state wants to hold/manage "static" - script state at the function level without using a hidden - property. i.e. we don't want cwal_props_clear() to be able to - nuke that state. - */ - cwal_value_rescoper_f rescoper; -}; - -/** - Empty-initialized cwal_function object. -*/ -#define cwal_function_empty_m { cwal_obase_empty_m, cwal_state_empty_m, NULL, NULL } -/** - Empty-initialized cwal_function object. -*/ -extern const cwal_function cwal_function_empty; - -/** - Concrete type for generic error/exception values. -*/ -struct cwal_exception { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - int code; -}; -/** - Empty-initialized cwal_exception object. -*/ -#define cwal_exception_empty_m { cwal_obase_empty_m, -1 } -/** - Empty-initialized cwal_exception object. -*/ -extern const cwal_exception cwal_exception_empty; - -/** - A key/value pair of cwal_values. While key can be an arbitrary - cwal_value, the engine requires that the key be of a type with a - stable hash code AND stable comparison semantics (as of 201811, - cwal_tuple and cwal_buffer are the only types which are "most - definitely not recommended) for use as keys because modification of - their contents changes how they compare to other values, which - means that they can get "out of place" within a sorted property - list (e.g. cwal_obase::kvp). - - Each of these objects owns its key/value pointers, and they - are cleaned up by cwal_kvp_clean(). A KVP holds a reference - to each of its parts. -*/ -struct cwal_kvp{ - /** - The key. Keys are compared using cwal_value_vtab::compare(). - */ - cwal_value * key; - /** - Arbitrary value. Objects do not have NULL values - a literal - NULL means to "unset" a property. cwal_value_null() can be - used as a value, of course. - */ - cwal_value * value; - /** - Right-hand member in a linked list. cwal_obase and cwal_hash - use this. Nobody else should. - */ - cwal_kvp * right; - /** - We need this for intepreter-level flags like "const" and - "hidden/non-iterable." This was increased from 16 to 32 bits on - 20191210, which does not change the sizeof(), because of - padding, but the public APIs were left as-is (exposing only - cwal_flags16_t). We can still, on 64-bit builds, stuff another - 4(?) bytes in here without increasing the sizeof(), which would - allow us to add a refcount to KVPs, though that wouldn't be as - useful as it may initially sound because we'd need to implement - Copy-on-Write for shared KVPs, which has some backfire cases - for how we use these objects. - - It might be interesting to expose the top 16 of these bits for - use by clients, but a concrete use case for such flags (which - isn't covered by existing cwal-level flags) eludes me - */ - cwal_flags16_t flags; -}; - -/** - Empty-initialized cwal_kvp object. -*/ -#define cwal_kvp_empty_m {NULL,NULL,NULL,0U/*flags*/} -/** - Empty-initialized cwal_kvp object. -*/ -extern const cwal_kvp cwal_kvp_empty; - -/** @internal - - Semantically allocates a new cwal_kvp object, owned by e, though it - may pull a recycled kvp from e's internal recycler. Returns NULL on - error. On success the returned value is empty-initialized. -*/ -cwal_kvp * cwal_kvp_alloc(cwal_engine *e); - -/** @internal - Unrefs kvp->key and kvp->value and sets them to NULL, but does not - free kvp. If !kvp then this is a no-op. -*/ -void cwal_kvp_clean( cwal_engine * e, cwal_kvp * kvp ); - -/** @internal - - Calls cwal_kvp_clean(e,kvp) and then either adds kvp to e's recycle - bin or frees it, depending on the value of allowRecycle and the - capacity/size of the associated recycler list. - - Callers must treat this call as if kvp is free()d by it, whether or - not this function actually does so. -*/ -void cwal_kvp_free( cwal_engine * e, cwal_kvp * kvp, char allowRecycle ); - -/** - Typedef for cwal_value hashing functions. Must return a hash value - for the given value. -*/ -typedef cwal_hash_t (*cwal_value_hash_f)( cwal_value const * v ); - -/** - Returns v's hash value, as computed by v's vtab. - Returns 0 if !v. -*/ -cwal_hash_t cwal_value_hash( cwal_value const * const v ); - -/** - Typedef for cwal_value comparison functions. Has memcmp() - semantics. Ordering of mismatched types (e.g. comparing an array to - an object) is type-dependent, possibly undefined. Implementations - of this function are type-specific and require that the lhs - (left-hand-side) argument be of their specific type (and are - permitted to assert that that is so). When performing conversions, - they should treat the LHS as the primary type for - conversion/precision purposes. e.g. comparison of (int 42) and - (double 42.24) might be different depending on which one is the LHS - because of changes in precision. - - Beware that these comparisons are primarily intended for - cwal-internal use (e.g. in the context of property lists and - hashtables), and are not strictly required to follow the semantics - of any given scripting environment or specificiation. (That said, - the public cwal_value_compare() interface uses these, so the - behaviour must remain stable.) - - Where (lhs,rhs) do not have any sort of natural ordering, - implementations should return any value other than 0, implementing - ECMAScript-like semantics if feasible. - - Implementations are encouraged to do cross-type comparisons where - feasible (e.g. "123"==123 is true), and to delegate to the converse - comparison (swapping lhs/rhs and the return value) when the logic - for the comparison is non-trivial and already implemented for the - other type. Strict-equality comparisons (e.g. "123"===123 is false) - are handled at a higher level which compares type IDs and/or - pointers before passing the values on to this function. Comparisons - are prohibited (by convention) from allocating any memory, and the - API is not set up to be able to report an OOM error to the caller. -*/ -typedef int (*cwal_value_compare_f)( cwal_value const * lhs, cwal_value const * rhs ); - -/** - This type holds the "vtbl" for type-specific operations when - working with cwal_value objects. - - All cwal_values of a given logical type share a pointer to a single - library-internal instance of this class. -*/ -struct cwal_value_vtab -{ - /** - The logical data type associated with this object. - */ - const cwal_type_id typeID; - - /** - A descriptive type name intented primarily for debuggering, and - not (necessarily) as a type's name as a client script language - might see/name it (though, in fact, they are in used that way). - */ - char const * typeName; - - /** - Internal flags. - */ - cwal_flags16_t flags; - - /** - Must free any memory owned by the second argument, which will - be a cwal_value of the concrete type associated with this - instance of this class, but not free the second argument (it is - owned by the engine and may be recycled). The API shall never - pass a NULL value to this function. - - The API guarantees that the scope member of the passed-in value - will be set to the value's pre-cleanup owning scope, but also - that the value is not in the scope value list at that time. The - scope member is, however, needed for proper unreferencing of - child values (which still live in a scope somewhere, very - possibly the same one). Implementations must not do anything - with child values other than unref them, as they may very well - already be dead and in the gc-queue (or recycler) by the time - this is called. The engine delays (via the gc-queue) any - free()ing of those children while a cleanup pass is running, so - handling the memory of a child value is legal, but any usage - other than an unref is semantically strictly illegal. - */ - cwal_finalizer_f cleanup; - - /** - Must return a hash value for this value. Hashes are used only as - a quick comparison, with the compare() method being used for - a more detailed comparison. - */ - cwal_value_hash_f hash; - - /** - Must perform a comparison on its values. The cwal engine - guarantees that the left-hand argument will be of the type - managed by the instance of the cwal_value_tab on which this is - called, but the right-hand argument may be of an arbitrary - type. - - This function checks for equivalence, not identity, and uses - memcmp() semantics: less than 0 means that the left-hand - argument is "less than" the right, 0 means they are equivalent, - and 1 means the the lhs is "greater than" the right. It is - understood, of course, that not all comparisons have meaningful - results, and implementations should always return non-0 for - meaningless comparisons. They are not required to return a - specific value but should behave consistently (e.g. not - swapping the order on every call or some such sillynesss). - - For many non-POD types, 0 can only be returned when both - pointers have the same address (that said, the framework should - short-circuit any such comparison itself). - */ - cwal_value_compare_f compare; - - /** - Called by the framework when it determines that v needs to be - "upscoped." The framework will upscope v but cannot generically - know if v contains any other values which also need to be - upscoped, so this function has the following responsibilities: - - For any child values which are "unmanaged" (e.g. they're not - stored in cwal_object properties), this function must call - cwal_value_rescope( v->scope->e, v->scope, child ). That, in - turn, will trigger the recursive rescoping of any children of - that value. Note that the rescoping problem is not subject to - "cyclic misbehaviours" - the worst that will happen is a cycle - gets visited multiple times, but those after the first will be - no-ops because no re-scoping is necessary: the API only calls - this when upscoping is necessary. - - The framework guarantees the following: - - Before this is called on a value, the following preconditions - exist: - - - v is of the value type represented by this vtab instance. - - - v->scope has been set to the "proper" scope by e. This - function will never be called when v->scope is 0. - - - v->scope->e points to the active engine. - - - This is only called when/if v is actually upscoped. Re-scope - requests which do not require a rescope will not trigger a call - to this function. - - If v owns any "unmanaged" child values (e.g. not kept in the - cwal_obase base class container or as part of a cwal_array) - then those must be rescoped by this function (the framework - does not know about them). They likely also need to be made - vacuum-proof (see cwal_value_make_vacuum_proof()). - - Classes which do not contain other values may set this member - to 0. It is only called if it is not NULL. Classes which do - hold other values _must_ implement it (properly!). - - Must return 0 on success and "truly shouldn't fail" because all - it does it pass each child on to cwal_value_rescope(), which - cannot fail if all arguments are in order and the internal - state of the values seems okay (the engine does a good deal of - extra checking and assert()ing). However, if it fails it should - return an appropriate value from the cwal_rc_e enum. Returning - non-0 will be treated as fatal, possibly assert()ing in debug - builds. - */ - int (*rescope_children)( cwal_value * v ); - - /** - TODOs???: - - // Using JS semantics for true/value - char (*bool_value)( cwal_value const * self ); - - // To-string ops... - unsigned char const * to_byte_array( cwal_value const * self, cwal_size_t * len ); - // which is an optimized form of ... - int to_string( cwal_value const * self, cwal_engine * e, cwal_buffer * dest ); - // with the former being used for (Buffer, String, builtins) - // and the latter for everything else. Either function may be 0 - // to indicate that the operation is not supported - // (e.g. Object, Array, Hashtable, Function...). - - // The problem with adding an allocator is... - int (*allocate)( cwal_engine *, ??? ); - // If we split it into allocation and initialization, might - // that solve it? Or cause more problems? - // cwal_value * (*allocate)( cwal_engine * e ); - // int (*initialize)( cwal_engine * e, cwal_value * v, ... ) - - // Deep copy. - int (*clone)( cwal_engine *e, cwal_value const * self, cwal_value ** tgt ); - - // Property interceptors: - int (*get)( cwal_engine *e, cwal_value const * self, - cwal_value const * key, cwal_value **rv ); - - int (*set)( cwal_engine *e, cwal_value * self, - cwal_value * key, cwal_value *v ); - - But for convenience the get() op also needs a variant taking a - c-string key (otherwise the client has to create values when - searching, more often than not) - */ -}; - -typedef struct cwal_value_vtab cwal_value_vtab; -/** - Empty-initialized cwal_value_vtab object. -*/ -#define cwal_value_vtab_empty_m { \ - CWAL_TYPE_UNDEF/*typeID*/, \ - ""/*typeName*/, \ - 0U/*flags*/, \ - NULL/*cleanup()*/, \ - NULL/*hash()*/, \ - NULL/*compare()*/, \ - NULL/*rescope_children()*/ \ - } -/** - Empty-initialized cwal_value_vtab object. -*/ -extern const cwal_value_vtab cwal_value_vtab_empty; - -/** - cwal_value represents an opaque Value handle within the cwal - framework. Values are all represented by this basic type but they - have certain polymorphic behaviours (via cwal_value_vtab) and many - concrete types have high-level handle representations - (e.g. cwal_object and cwal_array). - - @see cwal_new_VALUE() - @see cwal_value_vtab -*/ -struct cwal_value { - /** - The "vtbl" of type-specific operations. All instances of a - given logical value type share a single vtab instance. - - Results are undefined if this value is NULL or points to the - incorrect vtab instance. - */ - cwal_value_vtab const * vtab; - - /** - The "current owner" scope of this value. Its definition is - as follows: - - When a value is initially allocated its owner is the - currently-active scope. If a value is referenced by a - higher-level (older) scope, it is migrated into that scope - (recursively for containers) so that we can keep cleanup of - cycles working (because all values in a given graph need to be - in the same scope for cleanup to work). It is, on rare - occasion, necessary for code (sometimes even client-side) to - cwal cwal_value_rescope() to re-parent a value. - */ - cwal_scope * scope; - - /** - The left-hand-link for a linked list. Who exactly owns a value, - and which values they own, are largely handled via this - list. Each manager (e.g. scope, recycle bin, or gc queue) holds - the head of a list and adds/removes the entries as needed. - - Note that we require two links because if we only have a single - link then cleanup of a member in a list can lead traversal - through that list into places it must not go (like into the - recycler's memory). - - Design notes: in the original design each value-list manager - had a separate array-style list to manage its values. Switching - to this form (where the values can act as a list) actually - (perhaps non-intuitively) cuts both the number of overall - allocations and memory cost, converts many of the previous - operations from O(N) to O(1), and _also_ removes some - unrecoverable error cases caused by OOM during manipulation of - the owner's list of values. So it's an overall win despite the - cost of having 2 pointers per value. Just remember that it's - not strictly Value-specific overhead... it's overhead Scopes - and other Value owners would have if this class didn't. - */ - cwal_value * left; - - /** - Right-hand link of a linked list. - */ - cwal_value * right; - - /** - We use this to allow us to store a refcount and certain flags. - - Notes about the rc implementation: - - - Instances start out with a refcount of 0 (not 1). Adding them - to a container will increase the refcount. Cleaning up the container - will decrement the count. cwal_value_ref() can be used to obtain - a reference when no container is handy. - - - cwal_value_unref() decrements the refcount (if it is not - already 0) and cleans/frees the value only when the refcount is - 0 (and then it _might_ not destroy the value immediately, - depending on which scope owns it and where (from which scope) - its refcount goes to 0). - - - cwal_value_unhand() decrements the refcount (if it is not - already 0) but does not destroy the value if it reaches 0. If - it reaches 0, "unhanding" re-sets the value into a so-called - "probationary" state, making it subject to being swept up if no - reference is taken before the next cwal_engine_sweep() (or - similar). - - - This number HAS FLAGS ENCODED IN IT, so don't use this value - as-is. How many flags, where they are, and what they mean, are - internal details. Search cwal.c for RCFLAGS for the gory - details. - */ - cwal_refcount_t rcflags; - - /* - Historical notes, no longer entirely relevant but perhaps - interesting: - - ======== - - Potential TODO: if we _need_ flags in this class, we can use the - high byte (or half-byte) of refcount and restrict refcount to - the lower bytes (possibly only 3 resp. 3.5). We need to make - sure refcount uses a 32-bit type in that case, as opposed to - cwal_size_t (which can be changed to uint16_t, which only gives - us a range of up to 4k if we reserve 4 bits!). While 4k might - initially sound like a reasonable upper limit for refcounts, - practice has shown that value prototypes tend to accumulate lots - and lots of references (one for each value which has it as a - prototype), so 4kb quickly becomes a real limit. - - 16 bits (64k) of refcount might be a feasible upper limit, even - for large apps. And nobody will ever need more than 640kb of - RAM, either. - - We could... use a 32-bit type, reserve the bottom 24 bits (16M) - for the refcount, and the top 8 bits for client-side flags. - - Class-level flags are set in the associated cwal_value_vtab - instance. Container classes may have their own instance-specific - flags. - - Reminder to self: interpreters are going to need flags for - marking special values like constants/non-removable. Oh, but - that's going to not like built-in constants. So we'll need to - tag these at the cwal_kvp level (which costs us less, actually, - because we have one set of flags per key/value pair, instead of - per Value instance). - - Ideas of what clients could use flags for: - - - tagging values which want special handling. e.g. if scripts - can override get/set operations, that may be too costly if - performed on all values. A flag could indicate whether a given - value has such an override. Tagging constructor/factory - functions for special handling with the 'new' keyword is - something we could possibly use in s2. - - - in s2, we could use this to tag Classes and instances of classes, - such that we could change property lookup on them (and potentially - reject the addition of new properties to classes). - - Problems: - - - Built in constant values cannot be flagged. The use of the - numeric (-1, 0, 1) values as built-in constants saves up to 50% - of numeric-type allocations in many test scripts, so i don't - want to drop those. If we allow flags only on container types - (plus buffers), we can move the cost/handling there (and maybe - get more flag space). Or we add a way for clients to create - mutable instances of builtin values, such that they can be - tagged. That would require some fixes/changes in other bits - which make assumptions about the uniqueness of, e.g. boolean - values. - - */ -}; - - -/** - Empty-initialized cwal_value object. -*/ -#define cwal_value_empty_m { \ - &cwal_value_vtab_empty/*api*/,\ - 0/*scope*/,\ - 0/*left*/,\ - 0/*right*/, \ - 0/*rcflags*/ \ -} -/** - Empty-initialized cwal_value object. -*/ -extern const cwal_value cwal_value_empty; - - -struct cwal_native { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - void * native; - void const * typeID; - cwal_finalizer_f finalize; - /** - If this member is non-NULL then it is called to allow - the native to rescope properties not visible via its property - list. - */ - cwal_value_rescoper_f rescoper; -}; -#define cwal_native_empty_m {\ - cwal_obase_empty_m, \ - 0/*native*/,\ - 0/*typeID*/,\ - 0/*finalize*/, \ - 0/*rescoper*/ \ - } -extern const cwal_native cwal_native_empty; - -/** @internal - Hash table Value type. -*/ -struct cwal_hash { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - /** - The actual hashtable. Note that if (CWAL_OBASE_ISA_HASH) then - this hashtable is a separate one: that one is the object-level - properties and this one is the "plain" hashtable. The main - difference is that the latter does not participate in prototype - property lookup. - */ - cwal_htable htable; -}; -#define cwal_hash_empty_m { \ - cwal_obase_empty_m/*base*/, \ - cwal_htable_empty_m/*htable*/ \ - } -extern const cwal_hash cwal_hash_empty; - - -/** @internal - - An object-style representation for cwal_buffer. This type is - strictly internal, not exposed to clients. -*/ -struct cwal_buffer_obj { - /** - base MUST be the first member for casting reasons. - */ - cwal_obase base; - /** - The buffer owned/tracked by this object. - */ - cwal_buffer buf; -}; -typedef struct cwal_buffer_obj cwal_buffer_obj; -#define cwal_buffer_obj_empty_m {\ - cwal_obase_empty_m/*base*/, \ - cwal_buffer_empty_m/*buf*/ \ -} -extern const cwal_buffer_obj cwal_buffer_obj_empty; - -/** - Internal state for CWAL_TYPE_TUPLE-typed cwal_values. - -*/ -struct cwal_tuple { - /** - Number of entries in the list (set at init-time and - never changes). - */ - uint16_t n; - /** - Not yet used. Note that removing this does not shrink this - type's sizeof(), due to padding. - */ - uint16_t flags; - /** - List of this->n entries. - */ - cwal_value ** list; -}; -#define cwal_tuple_empty_m {0U,0U,NULL} - -/** @internal - - Internal impl of the weak reference class. -*/ -struct cwal_weak_ref { - void * value; - cwal_type_id typeID; - cwal_refcount_t refcount; - struct cwal_weak_ref * next; -}; - -/** @internal - - Initialized-with-defaults cwal_weak_ref instance. -*/ -#define cwal_weak_ref_empty_m {NULL, CWAL_TYPE_UNDEF, 0U, NULL} - -/** @internal - - Initialized-with-defaults cwal_weak_ref instance. -*/ -extern const cwal_weak_ref cwal_weak_ref_empty; - -/** @internal - - If v is-a obase then its obase part is returned, else NULL is - returned. -*/ -cwal_obase * cwal_value_obase( cwal_value * const v ); - - -/** @internal - - Internal debuggering function which MIGHT dump some useful info - about v (not recursively) to some of the standard streams. - - Do not rely on this function from client code. - - The File/Line params are expected to be the __FILE__/__LINE__ - macros. - - If msg is not NULL then it is included in the output (the exact - placement, beginning or end, is unspecified). - */ -void cwal_dump_value( char const * File, int Line, - cwal_value const * v, char const * msg ); - -/** @def cwal_dump_v(V,M) - - Internal debuggering macro which calls cwal_dump_value() with the - current file/line/function info. -*/ -#if 1 -# define cwal_dump_v(V,M) cwal_dump_value(__func__,__LINE__,(V),(M)) -#else -# define cwal_dump_v(V,M) assert(1) -#endif - - -/** - Searches e for an internalized string matching (zKey,nKey). If - found, it returns 0 and sets any of the output parameters which are - not NULL. - - On success, *out (if out is not NULL) be set to the value matching - the key. Note that only String values can compare equal here, even - if the key would normally compare as equivalent to a value of - another type. e.g. 1=="1" when using cwal_value_compare(), but - using that comparison here would not be possible unless we - allocated a temporary string to compare against. - - It sets *itemIndex (if not NULL) to the index in the strings table - for the given key, regardless of success of failure. The other - output params are only set on success. - - *out (if not NULL) is set to the value in the table. pageIndex (if - *not NULL) is set to the page in which the entry was found. - - Reminder to self: we might be able to stack-allocate an x-string - around zKey and do a cwal_value-keyed search on that. That would - work around the (1!="1") inconsistency. -*/ -int cwal_interned_search( cwal_engine * e, char const * zKey, cwal_size_t nKey, - cwal_value ** out, cwal_ptr_page ** pageIndex, uint16_t * itemIndex ); - -/** - Equivalent to cwal_interned_search() except that it takes a - cwal_value parameter and uses cwal_value_compare() for the hashing - comparisons. A cwal String value inserted this way _will_ compare -*/ -int cwal_interned_search_val( cwal_engine * e, cwal_value const * v, - cwal_value ** out, cwal_ptr_page ** pageIndex, - uint16_t * itemIndex ); -/** - Removes the string matching (zKey,nKey) from e's interned - strings table. If an entry is found 0 is returned and - *out (if not NULL) is set to the entry. - - Returns CWAL_RC_NOT_FOUND if no entry is found. -*/ -int cwal_interned_remove( cwal_engine * e, cwal_value const * v, cwal_value ** out ); - -/** - Inserts the given value, which must be-a String, into - e's in interned strings list. Returns 0 on success. - Returns CWAL_RC_ALREADY_EXISTS if the entry's string - value is already in the table. - -*/ -int cwal_interned_insert( cwal_engine * e, cwal_value * v ); - -/** - Pushes the given cwal_value into e->gc for later destruction. We do - this to avoid prematurely stepping on a being-destroyed Value when - visiting cycles. - - If insertion of p into the gc list fails then this function frees - it immediately. We "could" try to stuff it in the recycle bin, but - that would only potentially delay the problem (of stepping on a - freed value while cycling). - - This function asserts that e->gcInitiator is not 0. -*/ -int cwal_gc_push( cwal_engine * e, cwal_value * p ); - -/** - Passes all entries in e->gc to cwal_value_recycle() for recycling - or freeing. -*/ -int cwal_gc_flush( cwal_engine * e ); - -/** - If v->vtab->typeID (T) is of a recyclable type and e->recycler entry - number cwal_recycler_index(T) has space, v is put there, otherwise - it is cwal_free()'d. This is ONLY to be called when v->refcount - drops to 0 (in place of calling cwal_free()), or results are - undefined. - - If e->gcLevel is not 0 AND v is-a obase then v is placed in e->gc - instead of being recycled so that the destruction process can - finish to completion without getting tangled up in the recycle bin - (been there, debugged that). That is a bit of a shame, actually, - but the good news is that cwal_gc_flush() will try to stick it back - in the recycle bin. Note that non-Objects do not need to be - delayed-destroyed because they cannot contribute to cycles. - - Returns 0 if the value is freed immediately, 1 if it is recycled, - and -1 if v is placed in e->gc. If insertion into e->gc is called - for fails, v is freed immediately (and 0 is returned). (But since - e-gc is now a linked list, insertion cannot fail except on internal - mis-use of the GC bits.) -*/ -int cwal_value_recycle( cwal_engine * e, cwal_value * v ); - -/** - Tracing macros. - */ -#if CWAL_ENABLE_TRACE -#define CWAL_ETR(E) (E)->trace -#define CWAL_TR_SRC(E) CWAL_ETR(E).cFile=__FILE__; CWAL_ETR(E).cLine=__LINE__; CWAL_ETR(E).cFunc=__func__ -#define CWAL_TR_MSG(E,MSG) CWAL_ETR(E).msg = MSG; if((MSG) && *(MSG)) CWAL_ETR(E).msgLen = strlen(MSG) -#define CWAL_TR_EV(E,EV) CWAL_ETR(E).event = (EV); -/*if(!(CWAL_ETR(E).msg)) { CWAL_TR_MSG(E,#EV); }*/ - -#define CWAL_TR_RC(E,RC) CWAL_ETR(E).code = (RC) -#define CWAL_TR_V(E,V) CWAL_ETR(E).value = (V); -#define CWAL_TR_MEM(E,M,SZ) CWAL_ETR(E).memory = (M); CWAL_ETR(E).memorySize = (SZ) -#define CWAL_TR_S(E,S) CWAL_ETR(E).scope = (S) -#define CWAL_TR_SV(E,S,V) CWAL_TR_V(E,V); CWAL_TR_S(E,S) -#define CWAL_TR_VCS(E,V) CWAL_TR_V(E,V); CWAL_TR_S(E,(E)->current) -#define CWAL_TR3(E,EV,MSG) \ - if(MSG && *MSG) { CWAL_TR_MSG(E,MSG); } \ - CWAL_TR_EV(E,EV); \ - if(!(CWAL_ETR(E).scope)) { \ - if(CWAL_ETR(E).value) CWAL_ETR(E).scope = CWAL_ETR(E).value->scope; \ - if(!(CWAL_ETR(E).scope)) CWAL_ETR(E).scope=(E)->current; \ - } \ - CWAL_TR_SRC(E); \ - cwal_trace(E) -#define CWAL_TR2(E,EV) CWAL_TR3(E,EV,"") -#else -#define CWAL_ETR(E) -#define CWAL_TR_SRC(E) -#define CWAL_TR_MSG(E,MSG) -#define CWAL_TR_EV(E,EV) -#define CWAL_TR_RC(E,RC) -#define CWAL_TR_V(E,V) -#define CWAL_TR_S(E,S) -#define CWAL_TR_MEM(E,M,SZ) -#define CWAL_TR_SV(E,S,V) -#define CWAL_TR_VCS(E,V) -#define CWAL_TR3(E,EV,MSG) -#define CWAL_TR2(E,EV) -#endif - -/** @internal - - If the library is built with tracing enabled and tracing is enabled - for e, this function outputs the state of e->trace and then clears - that state. The intention is that various macros initialize the - trace state, then call this to output it. -*/ -void cwal_trace( cwal_engine * e ); - -/** @internal - - cwal_value_take() "takes" a value away from its owning scope, - transfering the scope's reference count point to the caller, - removing the value from any list it is currently in, and settings - its scope to NULL. - - On error non-0 is returned and ownership is not modified. - - This function works only on "managed" values (with an owning - scope), and there is no API for creating/managing non-scope-managed - values from client code. - - Each allocated value is owned by exactly one scope, and this - function effectively steals the value from the owning scope. This - function must not be passed the same value instance more than once - unless the value has been re-scoped since calling this (it will - fail on subsequent calls, and may assert() that v's is in the - expected state). - - In all cases, if this function returns 0 the caller effectively - takes over ownership of v and its memory, and the value IS NOT - VALID for use with most of the API because, after calling this, it - has no owning scope, and many APIs assert() that a value has an - owning scope. - - For built-in values this is a harmless no-op. - - Error conditions: - - CWAL_RC_MISUE: either e or v are NULL. - - CWAL_RC_RANGE: means that v has no owning scope. This - constellation is highly unlikely but "could happen" if the API ever - evolves to allow "unscoped" values (not sure how GC could work - without the scopes, though). - - @see cwal_value_unref() -*/ -int cwal_value_take( cwal_engine * e, cwal_value * v ); - -/** @internal - - An internal helper for routines (specifically JSON) - to traverse an object tree and detect cycles. - - Passed the object which is about to be traversed and a pointer - to an opaque state value. - - If this function returns 0, the value is either not capable of - participating in acyclic traversal (cannot form cyles) or it is - and was flagged as not being in an acyclic traversal. If non-0 is - returned, the value was already flagged as being in an acylic - traversal and was traversed again (by this function), indicating a - cyclic case (i.e. an error). - - If it returns CWAL_RC_CYCLES_DETECTED: the value is already in the - process of acyclic traversal. The caller should fail the operation - with the result code returned by this function - (CWAL_RC_CYCLES_DETECTED) or a semantic equivalent for the given - operation. - - If, and only if, the return code is 0, the caller is obligated to - call cwal_visit_acyclic_end(), passing it the same second - argument. The caller MAY call cwal_visit_acyclic_end() if this - function returns non-0, but is not obligated to. - - @see cwal_visit_props_begin() - @see cwal_visit_list_begin() - @see cwal_visit_acyclic_end() -*/ -int cwal_visit_acyclic_begin( cwal_value * const v, int * const opaque ); - -/** @internal - - MUST be called if, and only if, the previous call to - cwal_visit_acyclic_begin() returned 0. MAY be called if the - previous call to cwal_visit_acyclic_begin() returned non-0, but it - is not required. - - The 2nd argument must be the same value which was passed to - cwal_visit_acyclic_begin(), as it contains state relevant to the - cycle-checking process. - - @see cwal_visit_acyclic_begin() -*/ -void cwal_visit_acyclic_end( cwal_value * const v, int opaque ); - -/** @internal - - Internal helper to flag property visitation/traversal. If this is - called, the value stored in *opaque MUST be passed to - cwal_visit_props_end() in the same logical code block. - - v MUST be a type capable of property iteration (and not a - builtin). - - Calling this obliges the caller to pass the value of *opaque - to cwal_visit_props_end() when visitation is complete. - - The API guarantees that this routine will set *opaque to a non-0 - value, so that callers may use 0 for their own purposes (e.g. - determining whether or not they need to call - cwal_visit_props_end()). - - This function may set the CWAL_RCF_IS_VISITING flag on v, and - records in the 2nd argument whether or not it did so. When - cwal_visit_props_end() is called, if its second argument records - that it set the flag then that call unsets that flag. This allows - properties to be visited multiple times concurrently, with only - the top-most visitation toggling that flag. That flag, in turn, is - checked by routines which would invalidate such iteration, causing - such routines to return an error code rather than invalidating the - in-progress iteration. e.g. trying to set a property value while - the properties are being iterated over will trigger such a case - because the underlying data model does not support modification - during traversal. - - @see cwal_visit_props_end() - @see cwal_visit_acyclic_begin() -*/ -void cwal_visit_props_begin( cwal_value * const v, int * const opaque ); - -/** @internal - - If, and only if, cwal_visit_props_begin() was passed v, the - resulting integer value from that call MUST be passed to this - function when property traversal is complete. - - @see cwal_visit_props_begin() -*/ -void cwal_visit_props_end( cwal_value * const v, int opaque ); - -/** @internal - - Internal helper to flag property visitation. If this is called, - the value stored in *opaque MUST be passed to - cwal_visit_list_end() in the same logical code block. - - v MUST be a type capable of list iteration (and not a builtin). - - Calling this obliges the caller to pass the value of *opaque - to cwal_visit_list_end() when visitation is complete. - - The API guarantees that this routine will set *opaque to a non-0 - value, so that callers may use 0 for their own purposes (e.g. - determining whether or not they need to call - cwal_visit_props_end()). - - If called recursively, only the top-most call will modify the - visitation flag. - - @see cwal_visit_list_end() -*/ -void cwal_visit_list_begin( cwal_value * const v, int * const opaque ); - -/** @internal - - If, and only if, cwal_visit_list_begin() was passed v, the - resulting integer value from that call MUST be passed to this - function when property traversal is complete. - - @see cwal_visit_list_begin() -*/ -void cwal_visit_list_end( cwal_value * const v, int opaque ); - -/** - Internal helper for iterating over cwal_obase properties. -*/ -struct cwal_obase_kvp_iter { - cwal_value * v; - cwal_kvp const * current; -#if CWAL_OBASE_ISA_HASH - cwal_obase * base; - cwal_list const * li; - cwal_midsize_t ndx; -#endif -}; -typedef struct cwal_obase_kvp_iter cwal_obase_kvp_iter; - -/** @internal - - Initializes oks for iteration over v's properties and returns the - first property. Returns NULL if v has no properties. - - This routine may assert that v is currently marked as is-visiting - or is-list-visiting. - - Use cwal_obase_kvp_iter_next() to iterate over subsequent entries. -*/ -cwal_kvp const * cwal_obase_kvp_iter_init( cwal_value * const v, - cwal_obase_kvp_iter * const oks ); - -/** @internal - - Returns the next property in oks's state, or NULL once the end of - the property list has been reached. - - This routine may assert that v is currently marked as is-visiting - or is-list-visiting. - - Results are undefined if oks has has previously been passed to - cwal_obase_kvp_iter_init(). -*/ -cwal_kvp const * cwal_obase_kvp_iter_next( cwal_obase_kvp_iter * const oks ); - - - -/* LICENSE - -This software's source code, including accompanying documentation and -demonstration applications, are licensed under the following -conditions... - -Certain files are imported from external projects and have their own -licensing terms. Namely, the JSON_parser.* files. See their files for -their official licenses, but the summary is "do what you want [with -them] but leave the license text and copyright in place." - -The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) -explicitly disclaims copyright in all jurisdictions which recognize -such a disclaimer. In such jurisdictions, this software is released -into the Public Domain. - -In jurisdictions which do not recognize Public Domain property -(e.g. Germany as of 2011), this software is Copyright (c) 2011-2018 by -Stephan G. Beal, and is released under the terms of the MIT License -(see below). - -In jurisdictions which recognize Public Domain property, the user of -this software may choose to accept it either as 1) Public Domain, 2) -under the conditions of the MIT License (see below), or 3) under the -terms of dual Public Domain/MIT License conditions described here, as -they choose. - -The MIT License is about as close to Public Domain as a license can -get, and is described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - --- - -Copyright (c) 2011-2021 Stephan G. Beal -(https://wanderinghorse.net/home/stephan/) - -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. - ---END OF MIT LICENSE-- - -For purposes of the above license, the term "Software" includes -documentation and demonstration source code which accompanies -this software. ("Accompanies" = is contained in the Software's -primary public source code repository.) - -*/ - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#endif /* WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED */ -/* end of file cwal_internal.h */ -/* start of file cwal.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -#include -#include /* malloc(), free(), qsort() */ -#include -#include -#include - -/** - Tells the internals whether to keep Object properties sorted or - not. Sorting speeds up searches. It is/should be enabled as of - 20170320. - - 202107: this has no effect when CWAL_OBASE_ISA_HASH is true. -*/ -#define CWAL_KVP_TRY_SORTING 1 - -#if 1 -#include -#define MARKER(pfexp) \ - do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ - printf pfexp; \ - } while(0) -#else -#define MARKER(exp) if(0) printf -#endif - -#if defined(__cplusplus) -extern "C" { -#endif - -/** Convenience typedef. - typedef enum cwal_e_options cwal_e_options; */ - - -/* - Public-API constant/shared objects. -*/ -const cwal_array cwal_array_empty = cwal_array_empty_m; -const cwal_buffer cwal_buffer_empty = cwal_buffer_empty_m; -const cwal_callback_args cwal_callback_args_empty = cwal_callback_args_empty_m; -const cwal_callback_hook cwal_callback_hook_empty = cwal_callback_hook_empty_m; -const cwal_engine cwal_engine_empty = cwal_engine_empty_m; -const cwal_engine_tracer cwal_engine_tracer_empty = cwal_engine_tracer_empty_m; -const cwal_engine_vtab cwal_engine_vtab_empty = cwal_engine_vtab_empty_m; -const cwal_error cwal_error_empty = cwal_error_empty_m; -const cwal_exception cwal_exception_empty = cwal_exception_empty_m; -const cwal_exception_info cwal_exception_info_empty = cwal_exception_info_empty_m; -const cwal_function cwal_function_empty = cwal_function_empty_m; -const cwal_hash cwal_hash_empty = cwal_hash_empty_m; -const cwal_kvp cwal_kvp_empty = cwal_kvp_empty_m; -const cwal_list cwal_list_empty = cwal_list_empty_m; -const cwal_native cwal_native_empty = cwal_native_empty_m; -const cwal_object cwal_object_empty = cwal_object_empty_m; -const cwal_output_buffer_state cwal_output_buffer_state_empty = {NULL/*e*/, NULL/*b*/}; -const cwal_ptr_table cwal_ptr_table_empty = cwal_ptr_table_empty_m; -const cwal_recycler cwal_recycler_empty = cwal_recycler_empty_m; -const cwal_scope cwal_scope_empty = cwal_scope_empty_m; -const cwal_state cwal_state_empty = cwal_state_empty_m; -const cwal_trace_state cwal_trace_state_empty = cwal_trace_state_empty_m; -const cwal_value_vtab cwal_value_vtab_empty = cwal_value_vtab_empty_m; -const cwal_memchunk_config cwal_memchunk_config_empty = cwal_memchunk_config_empty_m; -static const cwal_memchunk_overlay cwal_memchunk_overlay_empty = {0,0}; -const cwal_memcap_config cwal_memcap_config_empty = cwal_memcap_config_empty_m; -const cwal_buffer_obj cwal_buffer_obj_empty = cwal_buffer_obj_empty_m; -const cwal_tuple cwal_tuple_empty = cwal_tuple_empty_m; - -#define E_IS_DEAD(E) ((E)->fatalCode) - -/** - An experiment in padding all allocations up to a pointer size so - that the recycler (which might (or might not)) can optimize a - bit. Similar to the string padding, but it's not clear whether we - will get as much benefit here. - - Initial tests show padding to take up notably more memory (~2k in - s2's amalgamated unit tests) and not saving any (or trivially few) - allocations. - - 20160206: enabling this gives, in the s2 unit tests, a microscopic - reduction (<500b) in peak mem (via list and buffer memory) but - shows promise in chunk recycling hits (roughly 4% - improvement). This depends entirely on other options, though: if - value/chunk recycling and string interning are enabled, plus - cwal_memcap_config::forceAllocSizeTracking is enabled, alloc counts - are completely unaffected and the byte difference between using - this and not is in the 2-digit range. -*/ -#define CWAL_ALLOC_DO_PAD 1 -#if 0 -/* Round N to next ptr size, even if it is on a ptr size boundary. */ -/* ==> valgrind errors (uninvestigated) */ -#define CWAL_MEMSZ_PAD(N) \ - ((CWAL_ALLOC_DO_PAD) \ - ? ((N) + sizeof(void*) - ((N) % sizeof(void*))) \ - : (N)) -#else -/* Round to next ptr size if N is not on a ptr size boundary. */ -#define CWAL_MEMSZ_PAD(N) \ - ((CWAL_ALLOC_DO_PAD) \ - ? (((N)%sizeof(void*)) \ - ? ((N) + sizeof(void*) - ((N) % sizeof(void*))) \ - : (N)) \ - : (N)) -#endif - -/** - CWAL_RCFLAGS_BITS = Number of bits to steal from - cwal_value::refcount for use in per-value-instance flags. - - i could possibly be convinced to go up to 8 bits (16M on 32-bit, - noting that cwal_config.h uses 32-bit int for cwal_refcount_t on - 16-bit builds). If we go any higher than 10 bits (~4M refcount max - on 32-bit) it would make sense to switch to 64-bit - cwal_value::refcount on 32-bit platforms, in which case we can - comfortably use as many of these flags as we need (at the cost of - bit-flipping performance on 32-bit platforms). -*/ -#define CWAL_RCFLAGS_BITS 8 -/** CWAL_RCFLAGS_BITS_MASK = lower-byte mask of CWAL_RCFLAGS_BITS bits. */ -#define CWAL_RCFLAGS_BITS_MASK 0xFF - -/** - CWAL_RCFLAG_MAXRC is a mask of bits of cwal_value::refcount - which are usable for reference counting. We actually use the bottom - bits for flags, not the top, just to help provoke any internal - misuse faster. This mask is used to check for overflow when - incrementing the refcount. -*/ -#define CWAL_RCFLAG_MAXRC (((cwal_refcount_t)-1)>>CWAL_RCFLAGS_BITS) - -/** - CWAL_REFCOUNT(V) requires V to be a non-null - (cwal_value*). Evaluates to the (V)->rcflags normalized as a - refcount counter, stripped of the mask bits. - - Warning: the compiler likes to complain about expressions with no - effect, and it's easy to use this macro in such a context (when - combined with other macros from its family). It's also easy to - reformulate such uses so that they don't cause that. -*/ -#define CWAL_REFCOUNT(V) ((V)->rcflags >> CWAL_RCFLAGS_BITS) - -/** - CWAL_REFCOUNT_SHIFTED(V) requires V to be a non-null - (cwal_value*). Evaluates to the (V)->rcflags, shifted and/or masked - into position so that it can be masked with flags. - - e.g. if the reference count is currently 1, then this will be (1 - <rcflags & ~CWAL_RCFLAGS_BITS_MASK) - -/** - CWAL_RCFLAGS(V) requires V to be a non-null (cwal_value*). Evaluates to the - flag bits of (V)->rcflags, stripped of the refcount part. -*/ -#define CWAL_RCFLAGS(V) ((V)->rcflags & CWAL_RCFLAGS_BITS_MASK) - -/** Adjusts (cwal_value*) V's refcount by N, retaining flags stashed there. Evals - to the new refcount, INCLUDING the flags bits. */ -#define CWAL_RCADJ(V,N) ((V)->rcflags = CWAL_RCFLAGS(V) | ((CWAL_REFCOUNT(V)+(N)) << CWAL_RCFLAGS_BITS)) - -/** Decrements (cwal_value*) V's refcount by 1, retaining flags stashed there. Evals to - the new refcount value, sans flags. */ -#define CWAL_RCDECR(V) (CWAL_RCADJ(V,-1), CWAL_REFCOUNT(V)) - -/** Increments (cwal_value*) V's refcount by 1, retaining flags stashed there. Evals to - the new refcount value, sans flags. */ -#define CWAL_RCINCR(V) (CWAL_RCADJ(V,1), CWAL_REFCOUNT(V)) - -/*#define CWAL_RCFLAGS_SET(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (F)))*/ -#define CWAL_RCFLAG_ON(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (CWAL_RCFLAGS(V) | (F)))) - -/*Disable the given flag in the given (cwal_value*). */ -#define CWAL_RCFLAG_OFF(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (CWAL_RCFLAGS(V) & ~(F)))) - -/*True if the given flag is on in the given (cwal_value*), else false. */ -#define CWAL_RCFLAG_HAS(V,F) ((CWAL_RCFLAGS(V) & (F)) ? 1 : 0) - -/*True if the (cwal_value*) V is not NULL and has any rc-flags - set which indicate that it is in the process of being cleaned - up, e.g. during its own destruction or its scope's cleanup. */ -#define CWAL_V_IS_IN_CLEANUP(V) \ - ((V) && (CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_DESTRUCTING) \ - || CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_GC_QUEUED) \ - /* || CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_RECYCLED)) */)) - -#define CWAL_V_IS_RESCOPING(V) \ - ((V) && CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_RESCOPING)) - -/** - Evaluates to true if the non-NULL (cwal_value*) V is suitable for storage - in cwal_scope::mine::headObj (else it goes in headPod, which has finalization - repercussions). -*/ -#if 0 -#define CWAL_V_GOES_IN_HEADOBJ(V) (CWAL_VOBASE(V) \ - || CWAL_TYPE_TUPLE==(V)->vtab->typeID \ - || CWAL_TYPE_UNIQUE==(V)->vtab->typeID) -#else -# define CWAL_V_GOES_IN_HEADOBJ(V) (!!CWAL_VOBASE(V)) -#endif - -/** - Flags for use with CWAL_REFCOUNT() and friends. Reminder: every bit - we steal halves the maximum refcount value! - - See also: CWAL_RCFLAGS_BITS -*/ - enum { - CWAL_RCF_NONE = 0x0, - /** - Set on values which are being destroyed, so that finalization can - DTRT when encounting it multiple times along the way (cycles). - */ - CWAL_RCF_IS_DESTRUCTING = 0x1, - /** - Sanity-checking flag which tells us that a given Value is in the - delayed-gc queue. - */ - CWAL_RCF_IS_GC_QUEUED = 0x2, - /** - Sanity-checking flag which tells us that a given Value is in a - recycling bin. - */ - CWAL_RCF_IS_RECYCLED = 0x4, - /** - Flag set only when a value is in the process of rescoping, so that - cyclic rescoping can break cycles. - */ - CWAL_RCF_IS_RESCOPING = 0x8, - /** - Flags an object that its properties are being iterated over or are - otherwise locked from being modified. - */ - CWAL_RCF_IS_VISITING = 0x10, - /** - Flags an object that one of the following parts is being iterated - over or is otherwise locked from being modified: array list, tuple - list, hashtable (also a list). - */ - CWAL_RCF_IS_VISITING_LIST = 0x20, - - /** - A variant of the visiting-related flags intended to be able to - restrict recursion by catching cycles. Specifically added to - re-enable cycle detection in the JSON bits. This flag is - specifically not intended to be used recursively (it's intended to - catch recursion) and it explicitly is not specific to iteration - over either object-level properties or list entries: it's intended - to be used for both. In the contexts it's used for (JSON), those - two uses are never combined in the same operation, so there is no - semantic ambiguity. - */ - CWAL_RCF_IS_VISITING_ACYCLIC = 0x40, - - /** - This flag tells the engine whether or not a given value is - "vacuum-proof" (immune to cwal_engine_vacuum()). This has to be in - cwal_value::rcflags instead of cwal_obase::flags so that - CWAL_TYPE_UNIQUE and CWAL_TYPE_TUPLE (and similar - not-full-fledged-container types) can be made vacuum-proof. - */ - CWAL_RCF_IS_VACUUM_PROOF = 0x80 - /* CWAL_RCF_IS_LOCKED = 0x100 - we'll need this if we want to lock - tuples the same way as arrays and hashtables, but we can also - justify not giving tuples full-fledged superpowers. */ - }; - -#define cwal_string_empty_m {0U/*length*/} -static const cwal_string cwal_string_empty = cwal_string_empty_m; - -#if 0 -const cwal_var cwal_var_empty = cwal_var_empty_m; -#endif -const cwal_engine_tracer cwal_engine_tracer_FILE = { -cwal_engine_tracer_f_FILE, -cwal_engine_tracer_close_FILE, -0 -}; -const cwal_allocator cwal_allocator_empty = - cwal_allocator_empty_m; -const cwal_allocator cwal_allocator_std = { -cwal_realloc_f_std, -cwal_state_empty_m -}; -const cwal_outputer cwal_outputer_empty = - cwal_outputer_empty_m; -const cwal_outputer cwal_outputer_FILE = { -cwal_output_f_FILE, -cwal_output_flush_f_FILE, -cwal_state_empty_m -}; -#if 0 -const cwal_outputer cwal_outputer_buffer = { -cwal_output_f_buffer, -NULL, -cwal_state_empty_m -}; -#endif - -const cwal_engine_vtab cwal_engine_vtab_basic = { -{ /*allocator*/ -cwal_realloc_f_std, -cwal_state_empty_m -}, -{/* outputer */ -cwal_output_f_FILE, -cwal_output_flush_f_FILE, -{/*state (cwal_state) */ -NULL/*data*/, -NULL/*typeID*/, -cwal_finalizer_f_fclose/*finalize*/ -} -}, -cwal_engine_tracer_empty_m, -cwal_state_empty_m/*state*/, -{/*hook*/ -NULL/*on_init()*/, -NULL/*init_state*/, -NULL/*scope_push()*/, -NULL/*scope_pop()*/, -NULL/*scope_state*/ -}, -{/*interning*/ -cwal_cstr_internable_predicate_f_default/*is_internable()*/, -NULL/*state*/ -}, -cwal_memcap_config_empty_m -}; - -/** - CwalConsts holds some library-level constants and default values - which have no better home. -*/ -static const struct { - /** The minimum/default hash size used by cwal_ptr_table. */ - const uint16_t MinimumHashSize; - /** The minimum step/span used by cwal_ptr_table. */ - const uint16_t MinimumStep; - /** Default length of some arrays on the first memory reservation - op. - */ - const uint16_t InitialArrayLength; - /** - Used as the "allocStamp" value for values which the library - allocates, in order to differentiate them from those allocated - via stack or being embedded in another object. Should point to - some library-internal static/constant pointer (any one will - do). - */ - void const * const AllocStamp; - /** - Largest allowable size for cwal_size_t values in certain - contexts (e.g. cwal_value::refcount). If it gets this high - then something is probably very wrong or the client is trying - too hard to push the boundaries. - */ - const cwal_size_t MaxSizeTCounter; - /** - If true then newly-create cwal_string instances will be - auto-interned. All strings with the same bytes will be shared - across a single cwal_string instance. - */ - char AutoInternStrings; - /** - Property name used to store the message part of an Exception - value. - */ - char const * ExceptionMessageKey; - - /** - Property name used to store the code part of an Exception - value. - */ - char const * ExceptionCodeKey; - - /** - If >0, this sets the maximum size for interning strings. Larger - strings will not be interned. - */ - cwal_size_t MaxInternedStringSize; - - /** - The maximum length of string to recycle. Should be relatively - small unless we implement a close-fit strategy for - recycling. Currently we only recycle strings for use with - "close" size matches (within 1 increment of - CwalConsts.StringPadSize). - */ - cwal_size_t MaxRecycledStringLen; - - /** - If StringPadSize is not 0... - - When strings are allocated, their sizes are padded to be evenly - divisible by this many bytes. When recycling strings, we use - this padding to allow the strings to be re-used for any similar - length which is within 1 of these increments. - - Expected to be an even value, relatively small (under 32, in any - case). - - Tests have shown 4-8 to be good values, saving anywhere from a - few percent to 36%(!!!) of the total allocations in the th1ish - test scripts (compared to a value of 0). Switching from 0 to 4 - gives a notable improvement on the current test scripts. 8 or - 12 don't reduce allocations all that much compared to 4, and - _normally_ (not always) cost more total memory. - */ - cwal_size_t StringPadSize; - - /** - Initial size for cwal_scope::props hashtables. - */ - cwal_size_t DefaultHashtableSize; - /** - "Preferred" load for hashtables before they'll automatically - resize. - */ - double PreferredHashLoad; -} CwalConsts = { -13 /* MinimumHashSize. Chosen almost arbitrarily, weighted towards - small memory footprint per table. */, -sizeof(void*) /* MinimumStep. */, -6 /* InitialArrayLength, starting length of various arrays. */, -&cwal_engine_vtab_empty/*AllocStamp - any internal ptr will do.*/, -#if 16 == CWAL_SIZE_T_BITS -(cwal_size_t)0xCFFF/*MaxSizeTCounter*/, -#else -(cwal_size_t)0xCFFFFFFF/*MaxSizeTCounter*/, -#endif -0/*AutoInternStrings*/, -"message"/*ExceptionMessageKey*/, -"code"/*ExceptionCodeKey*/, -32/*MaxInternedStringSize*/, -#if 0 -32/*MaxRecycledStringLen*/, -#elif 16 == CWAL_SIZE_T_BITS -32/*MaxRecycledStringLen*/, -#elif 1 -64/*MaxRecycledStringLen*/, -#elif 32==CWAL_SIZE_T_BITS || 16==CWAL_SIZE_T_BITS -2*CWAL_SIZE_T_BITS/*MaxRecycledStringLen*/, -/* In basic tests using s2, 64 gives overall better results (tiny - bit of peak, marginal number of mallocs saved), than 32 or 48. -*/ -#else -CWAL_SIZE_T_BITS/*MaxRecycledStringLen*/, -#endif -8/*StringPadSize*/, -11/*DefaultHashtableSize*/, -0.75/*PreferredHashLoad*/ -}; - -const cwal_weak_ref cwal_weak_ref_empty = cwal_weak_ref_empty_m; - -/** - CWAL_BUILTIN_INT_FIRST is the lowest-numbered built-in integer - value, and it must have a value of 0 or - lower. CWAL_BUILTIN_INT_LAST is the highest, and it must have a - value of 0 or higher. CWAL_BUILTIN_INT_COUNT is how many of those - values there are. - - The library currently won't build if CWAL_BUILTIN_INT_COUNT is 0, - but both FIRST and LAST may be 0, in which case the COUNT will be 1 - (the number 0 will be built in). - - The real limit to how many values we "could" build in is the - sizeof(CWAL_BUILTIN_VALS): we don't want it to be 64k+. 100 - built-in integers take up approximately 4k of space on 64-bit. - - Historical tests showed the single biggest gains to be had (in - terms of malloc saving) when then inclusive range [-1,1] is built - in. A range of [-1,10] provides marginally higher results. Above - that tends to make little difference, at least in generic test - scripts. The values lower than -1 are not used nearly as often, and - thus don't benefit as much from building them in. -*/ -#define CWAL_BUILTIN_INT_FIRST (-10) -#define CWAL_BUILTIN_INT_LAST (20) -#define CWAL_BUILTIN_INT_COUNT (CWAL_BUILTIN_INT_LAST + -(CWAL_BUILTIN_INT_FIRST) + 1/*zero*/) - -char const * cwal_version_string(cwal_size_t * length){ - if(length) *length = (cwal_size_t)(sizeof(CWAL_VERSION_STRING) - -sizeof(CWAL_VERSION_STRING[0] - /*NUL byte*/)); - return CWAL_VERSION_STRING; -} - -char const * cwal_cppflags(cwal_size_t * length){ - if(length) *length = (cwal_size_t)(sizeof(CWAL_CPPFLAGS) - -sizeof(CWAL_CPPFLAGS[0] - /*NUL byte*/)); - return CWAL_CPPFLAGS; -} - -char const * cwal_cflags(cwal_size_t * length){ - if(length) *length = (cwal_size_t)(sizeof(CWAL_CFLAGS) - -sizeof(CWAL_CFLAGS[0] - /*NUL byte*/)); - return CWAL_CFLAGS; -} - -char const * cwal_cxxflags(cwal_size_t * length){ - if(length) *length = (cwal_size_t)(sizeof(CWAL_CXXFLAGS) - -sizeof(CWAL_CXXFLAGS[0] - /*NUL byte*/)); - return CWAL_CXXFLAGS; -} - -/** - Internal impl for cwal_value_unref(). Neither e nor v may be NULL - (may trigger an assert()). This is a no-op if v is a builtin. -*/ -static int cwal_value_unref2(cwal_engine * e, cwal_value *v ); - -/** - Was intended to be an internal impl for cwal_value_ref(), but it - didn't quite work out that way. -*/ -static int cwal_value_ref2( cwal_engine *e, cwal_value * cv ); - -/** - Does nothing. Intended as a cwal_value_vtab::cleanup handler - for types which need not cleanup. - - TODO: re-add the zeroing of such values (lost in porting from - cson). -*/ -static void cwal_value_cleanup_noop( cwal_engine * e, void * self ); -/** - Requires that self is-a CWAL_TYPE_INTEGER cwal_value. This function - zeros its numeric value but goes not free() self. -*/ -static void cwal_value_cleanup_integer( cwal_engine * e, void * self ); -/** - Requires that self is-a CWAL_TYPE_DOUBLE cwal_value. This function - zeros its numeric value but goes not free() self. -*/ -static void cwal_value_cleanup_double( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_array. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_array( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_object. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_object( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_native. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_native( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_string. This function removes the - string from e's intering table. -*/ -static void cwal_value_cleanup_string( cwal_engine * e, void * self ); - -/** - Requires that self is-a cwal_buffer. This function calls - cwal_buffer_reserve() to clear the memory owned by the buffer. -*/ -static void cwal_value_cleanup_buffer( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_function. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_function( cwal_engine * e, void * self ); -/** - Requires that self is-a cwal_exception. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_exception( cwal_engine * e, void * self ); - -/** - Requires that self is-a cwal_hash. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_hash( cwal_engine * e, void * V ); - -/** - Requires that self is-a cwal_unique. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_unique( cwal_engine * e, void * V ); - -/** - Requires that self is-a cwal_tuple. This function destroys its - contents but goes not free() self. -*/ -static void cwal_value_cleanup_tuple( cwal_engine * e, void * V ); - -/** - Fetches v's string value as a non-const string. - - cwal_strings are supposed to be immutable, but this form provides - access to the immutable bits, which are v->length bytes long. A - length-0 string is returned as NULL from here, as opposed to - "". (This is a side-effect of the string allocation mechanism.) - Returns NULL if !v or if v is the internal empty-string singleton. -*/ -static char * cwal_string_str_rw(cwal_string *v); - -/* - Hash/compare routines for cwal_value_vtab. -*/ -static cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_bool( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_int( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_double( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_string( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_ptr( cwal_value const * v ); -static cwal_hash_t cwal_value_hash_tuple( cwal_value const * v ); -static int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs ); -/* static int cwal_value_cmp_type_only( cwal_value const * lhs, cwal_value const * rhs ); */ -static int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_nullundef( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_buffer( cwal_value const * lhs, cwal_value const * rhs ); -static int cwal_value_cmp_tuple( cwal_value const * lhs, cwal_value const * rhs ); - -/* - Rescope-children routines for container types. -*/ -static int cwal_rescope_children_obase( cwal_value * v ); -static int cwal_rescope_children_array( cwal_value * v ); -static int cwal_rescope_children_function( cwal_value * v ); -static int cwal_rescope_children_native( cwal_value * v ); -static int cwal_rescope_children_hash( cwal_value * v ); -static int cwal_rescope_children_unique( cwal_value * v ); -static int cwal_rescope_children_tuple( cwal_value * v ); - -/* - Internal "API" (a.k.a. "VTab") instances. One per core type. -*/ -static const cwal_value_vtab cwal_value_vtab_null = - { CWAL_TYPE_NULL, "null", - CWAL_F_NONE, - cwal_value_cleanup_noop, - cwal_value_hash_null_undef, - cwal_value_cmp_nullundef, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_undef = - { CWAL_TYPE_UNDEF, "undefined", - CWAL_F_NONE, - cwal_value_cleanup_noop, - cwal_value_hash_null_undef, - cwal_value_cmp_nullundef, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_bool = - { CWAL_TYPE_BOOL, "bool", - CWAL_F_NONE, - cwal_value_cleanup_noop, - cwal_value_hash_bool, - cwal_value_cmp_bool, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_integer = - { CWAL_TYPE_INTEGER, "integer", - CWAL_F_NONE, - cwal_value_cleanup_integer, - cwal_value_hash_int, - cwal_value_cmp_int, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_double = - { CWAL_TYPE_DOUBLE, "double", - CWAL_F_NONE, - cwal_value_cleanup_double, - cwal_value_hash_double, - cwal_value_cmp_double, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_string = - { CWAL_TYPE_STRING, "string", - CWAL_F_NONE, - cwal_value_cleanup_string, - cwal_value_hash_string, - cwal_value_cmp_string, - NULL/*rescope_children()*/ - }; -static const cwal_value_vtab cwal_value_vtab_array = - { CWAL_TYPE_ARRAY, "array", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_array, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_array - }; -static const cwal_value_vtab cwal_value_vtab_object = - { CWAL_TYPE_OBJECT, "object", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_object, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_obase - }; -static const cwal_value_vtab cwal_value_vtab_native = - { CWAL_TYPE_NATIVE, "native", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_native, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_native - }; -static const cwal_value_vtab cwal_value_vtab_hash = - { CWAL_TYPE_HASH, "hash", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_hash, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_hash - }; -static const cwal_value_vtab cwal_value_vtab_buffer = - { CWAL_TYPE_BUFFER, "buffer", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_buffer, - cwal_value_hash_ptr, - cwal_value_cmp_buffer, - cwal_rescope_children_obase - }; -static const cwal_value_vtab cwal_value_vtab_function = - { CWAL_TYPE_FUNCTION, "function", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_function, - cwal_value_hash_ptr, - cwal_value_cmp_func, - cwal_rescope_children_function - }; -static const cwal_value_vtab cwal_value_vtab_exception = - { CWAL_TYPE_EXCEPTION, "exception", - CWAL_F_ISA_OBASE, - cwal_value_cleanup_exception, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_obase - }; -static const cwal_value_vtab cwal_value_vtab_unique = - { CWAL_TYPE_UNIQUE, "unique", - CWAL_F_NONE, - cwal_value_cleanup_unique, - cwal_value_hash_ptr, - cwal_value_cmp_ptr_only, - cwal_rescope_children_unique - }; -static const cwal_value_vtab cwal_value_vtab_tuple = - { CWAL_TYPE_TUPLE, "tuple", - CWAL_F_NONE, - cwal_value_cleanup_tuple, - cwal_value_hash_tuple, - cwal_value_cmp_tuple, - cwal_rescope_children_tuple - }; - -/** - Internal constant cwal_values, used for copy-initialization of new - Value instances. -*/ -static const cwal_value cwal_value_undef = { &cwal_value_vtab_undef, NULL, NULL, NULL, 0 }; -/* static const cwal_value cwal_value_null_empty = { &cwal_value_vtab_null, NULL, NULL, NULL, 0 }; */ -/* static const cwal_value cwal_value_bool_empty = { &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }; */ -static const cwal_value cwal_value_integer_empty = { &cwal_value_vtab_integer, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_double_empty = { &cwal_value_vtab_double, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_string_empty = { &cwal_value_vtab_string, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_array_empty = { &cwal_value_vtab_array, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_object_empty = { &cwal_value_vtab_object, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_native_empty = { &cwal_value_vtab_native, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_hash_empty = { &cwal_value_vtab_hash, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_buffer_empty = { &cwal_value_vtab_buffer, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_function_empty = { &cwal_value_vtab_function, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_exception_empty = { &cwal_value_vtab_exception, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_value_unique_empty = { &cwal_value_vtab_unique, NULL, NULL, NULL, 0 }; -static const cwal_value cwal_tuple_value_empty = { &cwal_value_vtab_tuple, NULL, NULL, NULL, 0 }; - -/* - Internal convenience macros... -*/ - -/** - Cast ((cwal_value*)V) to (T*). It assumes the caller knows WTF he - is doing. Evaluates to 0 if !(V). - - This op relies on the fact that memory (V+1) contains a (T) - value. i.e. it relies on the allocation mechanism used by - cwal_value_new() and that V was allocated by that function, is a - builtin/shared value instance, or is NULL. -*/ -#define CWAL_VVPCAST(T,V) ((T*)((V) ? ((T*)((V)+1)) : (T*)0)) - -/** - CWAL_VALPART(CONCRETE) - - Cast (concrete_value_type*) CONCRETE to a - (cwal_value*). CWAL_VALPART() relies on the fact that ALL cwal_value - types which use it are allocated in a single memory chunk with - (cwal_value,concrete_type), in that order. Do not use this macro - for types which are not allocated that way. - - AFAIK, it is also legal for CONCRETE to be (cwal_obase*), provided - that pointer was allocated as part of one of the container types - (object, array, etc.). This relies on them having their (cwal_obase - base) member as the first member of the struct. - - e.g. assuming an Object value: - - cwal_obase * base = CWAL_VOBASE(val); - cwal_object * obj = cwal_value_get_object(val); - assert( CWAL_VALPART(base) == val ); - assert( base == &obj->base ); -*/ - -#if 1 -#define CWAL_VALPART(CONCRETE) ((CONCRETE) ? ((cwal_value *)(((unsigned char *)(CONCRETE))-sizeof(cwal_value))) : 0) -#else -/* i'm not convinced that this one is standards-conformant. - But it looks nicer. -*/ -#define CWAL_VALPART(CONCRETE) ((CONCRETE) ? (((cwal_value *)(CONCRETE))-1) : 0) -#endif - -/** - CWAL_INT(V) casts CWAL_TYPE_INTEGER (cwal_value*) V to a - (cwal_int_t*). -*/ -#define CWAL_INT(V) (CWAL_VVPCAST(cwal_int_t,(V))) - -/** - Requires that V be one of the special cwal_values TRUE or FALSE. - Evaluates to 1 if it is the TRUE value, else false. -*/ -#define CWAL_BOOL(V) ((&CWAL_BUILTIN_VALS.vTrue==(V)) ? 1 : 0) - -/** - CWAL_DBL(V) casts CWAL_TYPE_DOUBLE (cwal_value*) V to a (cwal_double_t*). -*/ -#define CWAL_DBL(V) CWAL_VVPCAST(cwal_double_t,(V)) - -/* - workarounds for false gcc warning: - - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47214 - - Summary: gcc's nonnull warning triggers incorrectly - for cases like somefunc( X ? Y : NULL ) because it cannot - know that X will never be false in that context. - - The _NONULL variants work like the non-_NONULL variants - but MUST NOT be passed a NULL value. -*/ -#define CWAL_VVPCAST_NONULL(T,V) ((T*)((V)+1)) -#define CWAL_DBL_NONULL(V) CWAL_VVPCAST_NONULL(cwal_double_t,(V)) -#define CWAL_INT_NONULL(V) (CWAL_VVPCAST_NONULL(cwal_int_t,(V))) - -/** - For cwal_string we store flags in the cwal_string::length member, - rather than add a flag to them (for memory reasons). We reserve the - top three bits and encode the length in the remaining ones. So the - maximum length of a given String value is 2^(CWAL_SIZE_T_BITS-3). -*/ -#if 16 == CWAL_SIZE_T_BITS -/* Max string length: 8k */ -# define CWAL_STRLEN_MASK ((cwal_size_t)0x1FFFU) -# define CWAL_STR_XMASK ((cwal_size_t)0x8000U) -# define CWAL_STR_ZMASK ((cwal_size_t)0x4000U) -# define CWAL_STR_ASCII_MASK ((cwal_size_t)0x2000U) -#elif 32 == CWAL_SIZE_T_BITS -/* Max string length: 0.5GB */ -# define CWAL_STRLEN_MASK ((cwal_size_t)0x1FFFFFFFU) -# define CWAL_STR_XMASK ((cwal_size_t)0x80000000U) -# define CWAL_STR_ZMASK ((cwal_size_t)0x40000000U) -# define CWAL_STR_ASCII_MASK ((cwal_size_t)0x20000000U) -#elif 64 == CWAL_SIZE_T_BITS -#if 0 -/* Portability: stick with 32-bit lengths. */ -# define CWAL_STRLEN_MASK ((cwal_size_t)0x1FFFFFFFU) -# define CWAL_STR_XMASK ((cwal_size_t)0x80000000U) -# define CWAL_STR_ZMASK ((cwal_size_t)0x40000000U) -# define CWAL_STR_ASCII_MASK ((cwal_size_t)0x20000000U) -#else -/* Max string length: 32-bits so that we can use cwal_midsize_t - for all string lengths. */ -# define CWAL_STRLEN_MASK ((cwal_size_t)0x1FFFFFFFU) -# define CWAL_STR_XMASK ((cwal_size_t)0x8000000000000000U) -# define CWAL_STR_ZMASK ((cwal_size_t)0x4000000000000000U) -# define CWAL_STR_ASCII_MASK ((cwal_size_t)0x2000000000000000U) -#endif -#endif - -/* - CWAL_STR_ASCII_MASK is a tag for strings which are pure ASCII (all - bytes are in the inclusive range [0,127]), in which case we can - speed up many operations which currently always have to read UTF8 - char-by-char. To disable this feature, set CWAL_STR_ASCII_MASK to 0 - and make sure all test code which assert()s that a given string is - ASCII is disabled. -*/ - -/* #define CWAL_STRLEN_MASK ((cwal_size_t)~(CWAL_STR_XMASK \ - | CWAL_STR_ZMASK | CWAL_STR_ASCII_MASK)) */ - - -/** - CWAL_STR(V) casts CWAL_TYPE_STRING (cwal_value*) V to a (cwal_string*). - If V is NULL or not-a String it evals to 0. -*/ -#define CWAL_STR(V) (((V) && (V)->vtab && (CWAL_TYPE_STRING==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_string,(V)) : 0) - -/** - Evaluates to true if S (which must be a valid (cwal_string*)) is an - x-string, else false. -*/ -#define CWAL_STR_ISX(S) ((CWAL_STR_XMASK & (S)->length) ? 1 : 0) -/** - Evaluates to true if S (which must be a valid (cwal_string*)) is a - z-string, else false. -*/ -#define CWAL_STR_ISZ(S) ((CWAL_STR_ZMASK & (S)->length) ? 1 : 0) -/** - Evaluates to true if S (which must be a valid (cwal_string*)) is a - either an x-string or z-string, else false. -*/ -#define CWAL_STR_ISXZ(S) (CWAL_STR_ISX(S) || CWAL_STR_ISZ(S)) - -/** - Evaluates to true if S (which must be a valid (cwal_string*)) has been - marked as being ASCII. -*/ -#define CWAL_STR_ISASCII(S) ((CWAL_STR_ASCII_MASK & (S)->length) ? 1 : 0) - -/** - Evaluates to the absolute value of S->length, in bytes, where S - must be a non-NULL (cwal_string [const]*). This is required instead - direct access to S->length because we encode non-size info in the - length field for X- and Z-strings, plus the is-ASCII flag. -*/ -#define CWAL_STRLEN(S) ((cwal_midsize_t)((S)->length & CWAL_STRLEN_MASK)) - -/** Evaluates to non-0 if the cwal_size_t value LEN is too long for a string length. */ -#define CWAL_STRLEN_TOO_LONG(LEN) (((cwal_size_t)(LEN)) & ~CWAL_STRLEN_MASK) - -/** - CWAL_OBJ(V) casts CWAL_TYPE_OBJECT (cwal_value*) V to a (cwal_object*). -*/ -#define CWAL_OBJ(V) (((V) && (V)->vtab && (CWAL_TYPE_OBJECT==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_object,(V)) : 0) - -/** - CWAL_ARRAY(V) casts CWAL_TYPE_ARRAY (cwal_value*) V to a (cwal_array*). -*/ -#define CWAL_ARRAY(V) (((V) && (V)->vtab && (CWAL_TYPE_ARRAY==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_array,(V)) : 0) - -/** - CWAL_HASH(V) casts CWAL_TYPE_HASH (cwal_value*) V to a (cwal_hash*). -*/ -#define CWAL_HASH(V) (((V) && (V)->vtab && (CWAL_TYPE_HASH==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_hash,(V)) : 0) - -/** - CWAL_OBASE(O) casts the OBase-type pointer OB to a (cwal_obase*). - This relies on OB being an obase-compliant type, with a cwal_obase - member being the first struct member of OB. -*/ -#define CWAL_OBASE(OB) ((cwal_obase*)(OB)) - -/** - Evaluates to true if ((cwal_value*) V)->vtab is not 0 and has the - CWAL_F_ISA_OBASE flag, else false. -*/ -#define CWAL_V_IS_OBASE(V) (((V) && (V)->vtab && (CWAL_F_ISA_OBASE & (V)->vtab->flags)) ? 1 : 0) - -/** - If CWAL_V_IS_OBASE(V), evaluates to V's (cwal_obase*) part, else it - evaluates to 0. For this to work all "object base" types must have - a cwal_obase member named 'base' as their VERY FIRST structure - member because we rely on a C's rule/allowance that a struct can be - cast to a pointer to its first member. -*/ -#define CWAL_VOBASE(V) (CWAL_V_IS_OBASE(V) ? CWAL_VVPCAST(cwal_obase,(V)) : 0) - -/** - Casts CWAL_TYPE_NATIVE value V to (cwal_native*). -*/ -#define CWAL_V2NATIVE(V) CWAL_VVPCAST(cwal_native,(V)) -/** - CWAL_BUFOBJ(V) casts CWAL_TYPE_BUFFER (cwal_value*) V to a (cwal_buffer_obj*). -*/ -#define CWAL_BUFOBJ(V) (((V) && (V)->vtab && (CWAL_TYPE_BUFFER==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_buffer_obj,(V)) : 0) - -/** - CWAL_UNIQUE_VALPP(V) casts gets the wrapped (cwal_value**) part of - CWAL_TYPE_UNIQUE (cwal_value*) V. If V is-not-a Unique, it evals - to 0. -*/ -#define CWAL_UNIQUE_VALPP(V) (((V) && (V)->vtab && (CWAL_TYPE_UNIQUE==(V)->vtab->typeID)) ? (CWAL_VVPCAST(cwal_value*,(V))) : 0) - -/** - CWAL_TUPLE(V) casts (cwal_value*) V to (cwal_tuple*). - If V is-not-a Tuple, it evals to 0. -*/ -#define CWAL_TUPLE(V) (((V) && (V)->vtab && (CWAL_TYPE_TUPLE==(V)->vtab->typeID)) ? (CWAL_VVPCAST(cwal_tuple,(V))) : 0) - -/** - If (cwal_value*) V is not NULL and has a scope, this evaluates to - its (cwal_engine*), else to 0. Note that built-in values have no - scope and are not specific to a cwal_engine instance. -*/ -#define CWAL_VENGINE(V) ((V) ? ((V)->scope ? (V)->scope->e : 0) : 0) - -/** - Evaluates to true if (cwal_value [const] *)V is currently flagged - as being visited (object-level properties). -*/ -#define CWAL_V_IS_VISITING(V) CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_VISITING) -/** - Evaluates to true if (cwal_value [const] *)V is currently flagged - as being list-visited (arrays and hashtable entries). -*/ -#define CWAL_V_IS_VISITING_LIST(V) CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_VISITING_LIST) - -/** - CWAL_OB_xxx(OBTYPE) all require OBTYPE to be a pointer to a - cwal_obase-deriving type, typically an array or hashtable. - - Semantic ambiguity here: this flag is currently only used to lock - array/list parts. If we also want to lock properties, this flag - becomes ambiguous for list-using types. It would be conceivable to - have the list part locked while the properties are not, and vice - versa. -*/ -#define CWAL_OB_LOCK(OBTYPE) (OBTYPE)->base.flags |= CWAL_F_LOCKED -#define CWAL_OB_UNLOCK(OBTYPE) (OBTYPE)->base.flags &= ~CWAL_F_LOCKED -#define CWAL_OB_IS_LOCKED(OBTYPE) (CWAL_F_LOCKED & (OBTYPE)->base.flags) - -#define METRICS_REQ_INCR(E,TYPE) ++(E)->metrics.requested[TYPE] - -#define CWAL_V_IS_VACUUM_SAFE(V) \ - (CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_VACUUM_PROOF)) - -/** - Must only be used when (cwal_engine::flags & - CWAL_F_TRACK_MEM_SIZE). This returns an address sizeof(void*) - bytes before void pointer M, cast to a (cwal_memsize_t*). -*/ -#define MEMSZ_PTR_FROM_MEM(M) (cwal_memsize_t*)((unsigned char *)(M) - sizeof(void*)) - -/** - Returns a (cwal_value*) to the given index from - CWAL_BUILTIN_VALS.memInt. -*/ -#define CWAL_BUILTIN_INT_VAL(NDX) ((cwal_value*)&CWAL_BUILTIN_VALS.memInt[NDX]) - -/** - Some "special" shared cwal_value instances. - - Note that they are not const because they are used as - shared-allocation objects in non-const contexts. However, the public - API provides no way of modifying them, and clients who modify values - directly are subject to The Wrath of Undefined Behaviour. -*/ -static struct CWAL_BUILTIN_VALS_ { - /** - Gets set to a true value when this struct gets initialized by - cwal_init_builtin_values(), to ensure that we only initialize - this once. Pedantic side-note: it's potentially possible that - two engine instances in different threads, being initialized at - the same time, try to initialize this data concurrently. That's - okay, as each initialization will set the data to the exact - same state and same addresses, so there's no real harm done. We - intentionally don't update the 'inited' member until the end up - of the init process, as its harmless if it's inited multiple - times concurrently but not harmless if we update this flag at - the start of the process and another thread tries to use this - data before it's completely initialized by the thread which set - that flag. - */ - int inited; - /** - Each of the memXXX entries holds the raw block of memory - intended for (cwal_value + concrete_type). These are - initialized by cwal_init_builtin_values(). - */ -#define sz_int sizeof(cwal_value)+sizeof(cwal_int_t) - unsigned char memInt[CWAL_BUILTIN_INT_COUNT - ? CWAL_BUILTIN_INT_COUNT - : 1/*dummy build placeholder*/][sz_int]; - unsigned char memDblM1[sizeof(cwal_value)+sizeof(cwal_double_t)]; - unsigned char memDbl0[sizeof(cwal_value)+sizeof(cwal_double_t)]; - unsigned char memDbl1[sizeof(cwal_value)+sizeof(cwal_double_t)]; - unsigned char memEmptyString[sizeof(cwal_value)+sizeof(cwal_string)+1/*NUL byte*/]; -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS - unsigned char memAsciiPrintable[128/* one entry each for ASCII [0,127] */] - [(sizeof(cwal_value)+sizeof(cwal_string)+2) - /* ==> "X\0", where X is an ASCII char 0..127. */]; -#else - unsigned char memAsciiPrintable[1][1/*dummy placeholder*/]; -#endif - unsigned char memTuple0[sizeof(cwal_value)+sizeof(cwal_tuple)]; -#undef sz_int - /** - Each of the vXXX pointers points to memory held in the - similarly-named memXXX member. - */ - cwal_value * vDblM1 /* TODO: eliminate these, as was done for integers. */; - cwal_value * vDbl0; - cwal_value * vDbl1; - cwal_value * vEmptyString; - - /** - Points to the cwal_string part of this->memEmptyString. - */ - cwal_string * sEmptyString; - cwal_value * vTuple0; - cwal_value vTrue; - cwal_value vFalse; - cwal_value vNull; - cwal_value vUndef; - - /** - Double values -1.0, 0.0, and 1.0. - */ - struct { - cwal_double_t mOne; - cwal_double_t zero; - cwal_double_t one; - } dbls; - - /** - Each of the wref.wXXX entries is a shared cwal_weak_ref - instance pointing to a similarly-named vXXX entry. We do not - allocate new cwal_weak_ref instances if they wrap a Value which - itself is a built-in value. It's hard to imagine a use case for - someone trying to weak-ref a boolean value, but the generic - nature of the Value system makes it conceivably possible, so - here it is... - */ - struct { - cwal_weak_ref wTrue; - cwal_weak_ref wFalse; - cwal_weak_ref wNull; - cwal_weak_ref wUndef; - cwal_weak_ref wStrEmpty - /* Reminder to self: we don't currently have entries here for - the built-in length-1 strings (memAsciiPrintable). Nor the - length-0 Tuple, it seems. Oh, well. We removed the integer - weak refs on 20171202 when that numeric range was made - built-time configurable. */; - cwal_weak_ref wDblM1; - cwal_weak_ref wDbl0; - cwal_weak_ref wDbl1; - } wref; - -} CWAL_BUILTIN_VALS = { -0/*inited*/, -{/*memInt*/ {0}}, -{/*memDblM1*/ 0}, -{/*memDbl0*/ 0}, -{/*memDbl1*/ 0}, -{/*memEmptyString*/ 0}, -{/*memAsciiPrintable*/{0}}, -{/*memTuple0*/0}, -NULL/*vDblM1*/, -NULL/*vDbl0*/, -NULL/*vDbl1*/, -NULL/*vEmptyString*/, -NULL/*sEmptyString*/, -NULL/*vTuple0*/, -{/*vTrue*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }, -{/*vFalse*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }, -{/*vNull*/ &cwal_value_vtab_null, NULL, NULL, NULL, 0 }, -{/*vUndef*/ &cwal_value_vtab_undef, NULL, NULL, NULL, 0 }, -#if CWAL_DISABLE_FLOATING_POINT -{/*dbls*/-1,0,1}, -#else -{/*dbls*/-1.0,0.0,1.0}, -#endif -{/*wref*/ -cwal_weak_ref_empty_m/* wTrue */, -cwal_weak_ref_empty_m/* wFalse */, -cwal_weak_ref_empty_m/* wNull */, -cwal_weak_ref_empty_m/* wUndef */, -cwal_weak_ref_empty_m/* wStrEmpty */, -cwal_weak_ref_empty_m/* wDblM1 */, -cwal_weak_ref_empty_m/* wDbl0 */, -cwal_weak_ref_empty_m/* wDbl1 */ -} -}; - -static void cwal_init_builtin_values(){ - struct CWAL_BUILTIN_VALS_ * h = &CWAL_BUILTIN_VALS; - cwal_value * v; - - {/* Set up empty string */ - memset(h->memEmptyString, 0, sizeof(h->memEmptyString)) - /* ensure that the NUL terminator is set */; - v = h->vEmptyString = (cwal_value*)h->memEmptyString; - *v = cwal_value_string_empty; - h->sEmptyString = CWAL_STR(v); - assert(h->sEmptyString); - assert(0==CWAL_STRLEN(h->sEmptyString)); - assert(0==*cwal_string_cstr(h->sEmptyString)); - } - -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS - {/* set up length-1 ASCII strings */ - int i = 0; - cwal_string * s; - char * cp; - memset(h->memAsciiPrintable, 0, sizeof(h->memAsciiPrintable)); - for( ; i <= 127; ++i ){ - v = (cwal_value *)(h->memAsciiPrintable[i]); - *v = cwal_value_string_empty; - s = CWAL_STR(v); - *s = cwal_string_empty; - s->length = CWAL_STR_ASCII_MASK | 1; - assert(1 == CWAL_STRLEN(s)); - cp = cwal_string_str_rw(s); - cp[0] = (char)i; - cp[1] = 0; - assert(1 == CWAL_STRLEN(s)); - assert(cp == cwal_string_cstr(s)); - } - v = 0; - } -#endif - - { - /* Set up integers [CWAL_BUILTIN_INT_FIRST .. CWAL_BUILTIN_INT_LAST]. - Maintenance reminder: the memcpy() is needed, rather than direct - copy, to avoid a "type punning" error in certain compilation - environments. */ - cwal_int_t v = CWAL_BUILTIN_INT_FIRST, ndx = 0; - cwal_int_t const last = CWAL_BUILTIN_INT_LAST; - assert(CWAL_BUILTIN_INT_FIRST <= 0); - assert(CWAL_BUILTIN_INT_LAST >= 0); - assert(CWAL_BUILTIN_INT_COUNT > 0); - assert(sizeof(h->memInt)/sizeof(h->memInt[0]) == - (CWAL_BUILTIN_INT_COUNT ? CWAL_BUILTIN_INT_COUNT : 1)); - for( ; v <= last; ++v, ++ndx ){ - cwal_value * cv = CWAL_BUILTIN_INT_VAL(ndx); - *cv = cwal_value_integer_empty; - memcpy(CWAL_INT_NONULL(cv), &v, sizeof(cwal_int_t)); - assert( v == cwal_value_get_integer(cv) ); - } - } - - /* Set up doubles (-1, 0, 1) */ -#if CWAL_DISABLE_FLOATING_POINT -#define dbl_m1 -1 -#define dbl_0 0 -#define dbl_1 1 -#else -#define dbl_m1 -1.0 -#define dbl_0 0.0 -#define dbl_1 1.0 -#endif -#define NUM(N,V) { \ - const cwal_double_t dv = V; \ - h->vDbl##N = v = (cwal_value*)h->memDbl##N; \ - *v = cwal_value_double_empty; \ - memcpy(CWAL_DBL_NONULL(v), &dv, sizeof(cwal_double_t)); \ - } (void)0 - NUM(M1,dbl_m1); - NUM(0,dbl_0); - NUM(1,dbl_1); -#undef NUM -#undef dbl_m1 -#undef dbl_0 -#undef dbl_1 - - { - /** Sets up shared weak refs */ - cwal_weak_ref * r; -#define REF(N,V) r = &h->wref.N; *r = cwal_weak_ref_empty; \ - r->value = V; r->typeID = (V)->vtab->typeID - REF(wTrue,&h->vTrue); - REF(wFalse,&h->vFalse); - REF(wNull,&h->vNull); - REF(wUndef,&h->vUndef); - REF(wStrEmpty,h->vEmptyString); - REF(wDblM1,h->vDblM1); - REF(wDbl0,h->vDbl0); - REF(wDbl1,h->vDbl1); - } - - { /* Empty Tuple... */ - memset(h->memTuple0, 0, sizeof(h->memTuple0)); - v = h->vTuple0 = (cwal_value*)h->memTuple0; - *v = cwal_tuple_value_empty; - assert(CWAL_TUPLE(v)); - assert(0==CWAL_TUPLE(h->vTuple0)->n); - } - - h->inited = 1 - /* We do this at the end, instead of the start, to cover a - specific threading corner case: If two (or more) threads - end up triggering this routine concurrently, we let all of - them modify the memory. Because they all set it to the - same values, we really don't care which one finishes - first. Once any of them have set h->inited=1, - CWAL_BUILTIN_VALS contains the memory we want. Even if a - slower thread is still re-initializing it after a faster - thread has returned, it is overwriting the contents with - the same values, so writing them while the faster thread is - (potentially) using them "should" be harmless. - - Note that this race can only potentially happen once during - the life of the application, during the initialization of - the first cwal_engine instance(s), and only if they are - initialized in concurrent threads. - */; - -} - -/** - CWAL_MEM_IS_BUILTIN(V) determines if the (void const *) V is one of - the special built-in/constant values. It does so by simply checking - if its address lies within range of the stack-allocated - special-value holders, so it's about as fast as it can be. - - - Maintenance reminders: - - - Profiling shows that cwal_value_is_builtin() is by far the - most-called routine in the library and accounts for about 1% of the - total calls in my test app. That is the only reason it has become a - macro. Client-side code never uses (never really has a need for, - other than basic curiosity) cwal_value_is_builtin(). -*/ -#define CWAL_MEM_IS_BUILTIN(V) \ - ((((void const *)(V) >= (void const *)&CWAL_BUILTIN_VALS) \ - && ( (void const *)(V) < (void const *)(&CWAL_BUILTIN_VALS+1)) \ - ) ? 1 : 0) - - -/** - Intended for use in assertions to ensure that (cwal_value const *) - V is either a builtin value or has an owning scope. -*/ -#define V_SEEMS_OK(V) ((V)->scope || CWAL_MEM_IS_BUILTIN(V)) - -/** - Type used for counting memory chunk sizes. -*/ -typedef uint32_t cwal_memsize_t; - - -bool cwal_value_is_builtin( void const * m ){ - return CWAL_MEM_IS_BUILTIN(m); -} - -/** - Static state for the recycling mechanism. Here we only - store some calculated values which apply to all engines. - - Gets populated by cwal_setup_recycler_indexes(). -*/ -static struct { - /** - A map of cwal_type_id to integer array indexes - for cwal_engine::recycler[]. It gets populated - once during engine initialization and holds values - valid for all engine instances. - */ - int indexes[CWAL_TYPE_end]; - /** - The total number of recycling bins calculated by - cwal_setup_recycler_indexes(). cwal_engine::recycler - must have at least this many elements. - */ - cwal_size_t recyclerCount; -} cwalRecyclerInfo = { -{-1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1 }, -0 -}; - -/** - Calculates an optimized value recycling bin layout for - cwal_engine::recycler and stores the result in cwalRecyclerInfo.indexes. - All non-recyclable types get a -1 put in - cwalRecyclerInfo.indexes[thatTypeId]. All others get a value - representing an index into cwal_engine::recycler. All types with - the same base Value size are grouped together in the same index. -*/ -static void cwal_setup_recycler_indexes(){ - if(cwalRecyclerInfo.recyclerCount) return; - else{ - static const cwal_type_id tlist[] = { - /* - Reminders to self: only set up VALUE TYPES this way because - we cannot mix Value and non-Value types in one recycler - because usage of their linked list members - breaks. CWAL_TYPE_STRING must not be in this list, but - CWAL_TYPE_XSTRING and ZSTRING must be (both have the same - size and will be grouped together). - */ - CWAL_TYPE_INTEGER, - CWAL_TYPE_DOUBLE, - CWAL_TYPE_ARRAY, - CWAL_TYPE_OBJECT, - CWAL_TYPE_NATIVE, - CWAL_TYPE_BUFFER, - CWAL_TYPE_FUNCTION, - CWAL_TYPE_EXCEPTION, - CWAL_TYPE_HASH, - CWAL_TYPE_XSTRING, - CWAL_TYPE_ZSTRING, - CWAL_TYPE_UNIQUE, - CWAL_TYPE_TUPLE, - CWAL_TYPE_UNDEF /* list sentinel - gets excluded below */ - }; -#define ASIZE(A) (sizeof(A)/sizeof(A[0])) - cwal_size_t xlist[ASIZE(tlist)]; - cwal_size_t zlist[ASIZE(tlist)]; - cwal_size_t i, x, zCount = 0; - memset(zlist, 0, sizeof(zlist)); -#ifdef DEBUG - /* Make sure the inlined intialization is as expected... */ - for(i = 0; i < CWAL_TYPE_end; ++i){ - assert(-1==cwalRecyclerInfo.indexes[i]); - /* cwalRecyclerInfo.indexes[i] = -1; */ - /* cwalRecyclerSizes[i] = cwal_type_id_sizeof(i); */ - } -#endif - /* Collect the sizes of each type... */ - for(i = 0; CWAL_TYPE_UNDEF != tlist[i] ; ++i){ - xlist[i] = cwal_type_id_sizeof(tlist[i]); - assert(xlist[i]); - /*MARKER(("xlist[%s] = %d\n", - cwal_type_id_name(tlist[i]), (int)xlist[i]));*/ - }; - xlist[i] = 0; - /* Bubblesort the sizes... */ - for(i = 0; i < ASIZE(xlist)-1; ++i){ - for( x = 1; x < ASIZE(xlist)-1; ++x){ - if(xlist[x] < xlist[x-1]){ - cwal_size_t const tmp = xlist[x-1]; - xlist[x-1] = xlist[x]; - xlist[x] = tmp; - } - } - } -#if 0 - for(i = 0; xlist[i] && i < CWAL_TYPE_end; ++i){ - MARKER(("Sorted index %d %d\n", - (int)i, (int)xlist[i])); - } -#endif - /* Remove dupes... */ - for( x = 0; x < ASIZE(xlist)-1; ++x ){ - int gotIt = 0; - for(i = 0; zlist[i] && i < ASIZE(zlist)-1; ++i){ - if( zlist[i] == xlist[x] ){ - gotIt = 1; - break; - } - } - if(!gotIt) zlist[zCount++] = xlist[x]; - } -#if 0 - for(i = 0; i < CWAL_TYPE_end && zlist[i]; ++i){ - MARKER(("Index #%d sizeof=%d\n", - (int)i, (int)zlist[i])); - } -#endif - /* Match up sizes to types, to group recycle bins... */ - for(i = 0; zlist[i]; ++i){ - /* MARKER(("#%d = %d\n", (int)i, (int)zlist[i])); */ - for(x = 0; CWAL_TYPE_UNDEF != tlist[x]; ++x){ - cwal_size_t const sz = cwal_type_id_sizeof(tlist[x]); - if(sz == zlist[i]){ - /*MARKER(("Size match (%d): %s\n", (int)sz, - cwal_type_id_name(tlist[x])));*/ - cwalRecyclerInfo.indexes[tlist[x]] = (int)i; - } - } - } - assert(cwalRecyclerInfo.indexes[CWAL_TYPE_XSTRING] - ==cwalRecyclerInfo.indexes[CWAL_TYPE_ZSTRING]); - /* assert(cwalRecyclerSizes[CWAL_TYPE_OBJECT]); */ - - cwalRecyclerInfo.indexes[CWAL_TYPE_KVP] = zCount++; - cwalRecyclerInfo.indexes[CWAL_TYPE_WEAK_REF] = zCount++; - cwalRecyclerInfo.indexes[CWAL_TYPE_SCOPE] = zCount++; - - cwalRecyclerInfo.recyclerCount = zCount; -#ifdef DEBUG - { - /* Make sure cwal_engine::recycler is sized big enough. */ - cwal_engine * e = 0; - assert(zCount <= sizeof(e->recycler)/sizeof(e->recycler[0])); - } -#endif -#if 0 - for(i = 0; i < CWAL_TYPE_end; ++i){ - MARKER(("Recycler: %d %s index=%d\n", - (int)i, - cwal_type_id_name((cwal_type_id)i), - (int)cwalRecyclerInfo.indexes[i] - )); - } -#endif - } -#undef ASIZE -} - -/** - Returns the index in cwal_engine::recycler[] for the given - type ID, or -1 if there is no recycler for that type. - - Special case: for CWAL_TYPE_STRING it returns the x-/z-string - index. Cases which refer to the real string recycler need to be - careful NOT to use this routine, but cwal_recycler_get() - instead. -*/ -static int cwal_recycler_index( cwal_type_id typeID ){ - static int once = 0; - if(!once){ - cwal_setup_recycler_indexes(); - once=1; - /* - Pedantic side note: if two threads init two cwal_engine - instances at the same time, they will both init the recycler - indexes, but will set the same memory to the same values, so - it makes no difference. We set once=1 last to allow two - threads to do that, rather than to have the second thread - continue before the first is filling up cwalRecyclerInfo. - */ - } - switch( typeID ){ - case CWAL_TYPE_STRING: - return cwalRecyclerInfo.indexes[CWAL_TYPE_XSTRING] - /* Special case because x/z-strings get tagged - with this type after construction. */ - ; - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_XSTRING: - case CWAL_TYPE_ZSTRING: - /* we only handle x/z-strings via e->recycler, - and STRING via e->reString. */ - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_HASH: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_UNIQUE: - case CWAL_TYPE_TUPLE: - - case CWAL_TYPE_KVP: - case CWAL_TYPE_SCOPE: - case CWAL_TYPE_WEAK_REF: - - assert(cwalRecyclerInfo.indexes[typeID]>=0); - return cwalRecyclerInfo.indexes[typeID]; - default: - return -1; - } -} - -/** - Overwrites all state in buf with defaults (zeroes) but - retains the buf->self member. -*/ -static void cwal_buffer_wipe_keep_self( cwal_buffer * buf ){ - void * self = buf->self; - *buf = cwal_buffer_empty; - buf->self = self; -} - -int cwal_is_dead(cwal_engine const * e){ - return e ? e->fatalCode : CWAL_RC_MISUSE; -} - -/** @internal - - Checks whether child refers to a newer scope than parent, and if - it does then it moves child to par. If par is 0 this function - sets *res (if not NULL) to 1 and returns. - - Returns 0 on success and the only error case is if popping child - from its parent or pushing to its new parent fails (which since the - transition from arrays to linked lists for child values, has no - failure cases other than memory corruption or similar internal - mismanagement of cwal's bits). It sets res (if not NULL) to one of: - - -1: means that par takes over child, adding it to its list setting - child->scope to par. - - 0: no action is necessary. Child already belongs to par. - - 1: child is a built-in, belongs to par, or belongs to a scope with - a lower scope->level (in which case that scope is its owner). In any - such case, this function does not modify child's scope. - - Note that this does NOT change child's reference count in any case. -*/ -static int cwal_value_xscope( cwal_engine * e, cwal_scope * par, cwal_value * child, int * res ); - - -/** - Internal debuggering routine which dumps out info about the value v - (which must not be NULL) to stdout or stderr. If msg is provided it - is appended to the output. -*/ -void cwal_dump_value( char const * File, int Line, cwal_value const * v, - char const * msg ){ - FILE * out = stdout; - cwal_obase const * b = CWAL_VOBASE(v); - if(File && *File && (Line>0)){ - fprintf(out, "%s():%d: ", File, Line ); - } - fprintf(out, "%s@%p (scope=#%d) refCount=%u", - v->vtab->typeName, - (void*)v, - v->scope - ? (int)v->scope->level - : (int)(CWAL_MEM_IS_BUILTIN(v) ? 0 : -666), - (unsigned)CWAL_REFCOUNT(v)); - if(b){ - fprintf( out, " flags=%02x", b->flags ); - } - if( cwal_value_is_string(v) ){ - cwal_string const * s = cwal_value_get_string(v); - if(s && CWAL_STRLEN(s)){ - const cwal_size_t len = CWAL_STRLEN(s); - fprintf( out, " strlen=%u", (unsigned)len); - if( len <= 30 ){ - fprintf( out, " bytes=[%.*s]", (int)len, cwal_string_cstr(s) ); - } - }else{ - fprintf( out, " (STILL INITIALIZING?)" ); - } - } - else if(cwal_value_is_integer(v)){ - fprintf( out, " int=%"CWAL_INT_T_PFMT, - cwal_value_get_integer(v)); - } - else if(cwal_value_is_double(v)){ - fprintf( out, " double=%"CWAL_DOUBLE_T_PFMT, - cwal_value_get_double(v)); - } - else if(cwal_value_is_bool(v)){ - fprintf( out, " bool=%s", - cwal_value_get_double(v) ? "true" : "false"); - }else if(cwal_value_is_tuple(v)){ - cwal_tuple const * p = CWAL_TUPLE(v); - assert(p); - fprintf( out, " n=%d", (int)p->n ); - } - if(msg && *msg) fprintf( out, "\t%s\n", msg ); - else fputc( '\n', out ); - fflush(out); -} - -#define dump_val(V,M) cwal_dump_value(__FILE__,__LINE__,(V),(M)) - -/** - Returns the recycler for the given type, distinguishing between - CWAL_TYPE_STRING (==>e->reString) and CWAL_TYPE_XSTRING/ZSTRING - (==>e->recycler[something]). BE CAREFUL: do not put x/z-strings - in the other string bin, or vice versa! -*/ -static cwal_recycler * cwal_recycler_get( cwal_engine * e, cwal_type_id t ){ - if(CWAL_TYPE_STRING == t){ - return &e->reString; - } - else { - const int ndx = cwal_recycler_index(t); - /* assert(ndx>=0); */ - return (ndx >= 0) ? &e->recycler[ndx] : 0; - } -} - - -/** - Either adds mem to e->reChunk or (if recycling is disabled or capacity/quota - is reached) frees mem using cwal_free(). -*/ -static void cwal_memchunk_add(cwal_engine * e, void * mem, cwal_size_t size ){ - cwal_memchunk_recycler * re = &e->reChunk; - assert(mem); - assert(size>0); - assert((cwal_size_t)-1 != size); - size = CWAL_MEMSZ_PAD(size); - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - /* If we have the size info in the memory block then use that. - This allows us to make up for list and string allocations truncating - sizes, effectively "losing" bytes (making them unusable, but still - part of a tracked block) each time they do that. */ - cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(mem); - /* static int counter1 = 0; ++counter1; */ - if(*sz != size+sizeof(void*)){ - /* static int counter2 = 0; */ - assert(*sz); - assert(e->memcap.currentMem >= *sz); - assert(size < *sz-sizeof(void*)); - /*MARKER(("#%d of %d Replacing size %d with %d from memory stamp.\n", - ++counter2, counter1, (int)size, (int)(*sz - sizeof(void*))));*/ - e->metrics.recoveredSlackBytes += *sz - size - sizeof(void*); - ++e->metrics.recoveredSlackCount; - size = *sz - sizeof(void*) - /* -sizeof is needed to avoid a semantic conflict in - cwal_memchunk_overlay::size - vs. *MEMSZ_PTR_FROM_MEM(mem). If we don't account - for it here, we end up corrupting malloc()-internal - state! - */; - } - } - if(!re->config.maxChunkCount - || !e->current /* i.e. during finalization */ - || size < sizeof(cwal_memchunk_overlay) - || (re->config.maxTotalSize < (size + re->currentTotal)) - || (re->config.maxChunkSize < size) - || (re->headCount >= re->config.maxChunkCount) - ){ - /* MARKER(("Freeing chunk @%p of size %d\n", mem, (int)size)); */ - cwal_free(e,mem); - }else{ - cwal_memchunk_overlay * ovl; - /* MARKER(("Adding mem chunk @%p of size %d\n", mem, (int)size)); */ - assert(re->headCount < re->config.maxChunkCount); - /* memset(mem, 0, size); */ - ovl = (cwal_memchunk_overlay *)mem; - *ovl = cwal_memchunk_overlay_empty; - ovl->size = size; - if(!re->metrics.smallestChunkSize - || (size < re->metrics.smallestChunkSize)){ - re->metrics.smallestChunkSize = size; - } - if(size > re->metrics.largestChunkSize){ - re->metrics.largestChunkSize = size; - } - if( (re->currentTotal += size) > - re->metrics.peakTotalSize ){ - re->metrics.peakTotalSize = re->currentTotal; - } - if( ++re->headCount > re->metrics.peakChunkCount ){ - re->metrics.peakChunkCount = re->headCount; - } - re->metrics.runningAverageSize = (re->metrics.runningAverageSize + size)/2; - /* Insert ovl into the linked list, sorted by size... */ - if(!re->head){ - assert(1==re->headCount) /* 1 b/c we increment it above */; - re->head = ovl; - }else{ - cwal_memchunk_overlay * h = re->head; - cwal_memchunk_overlay * prev = 0; - char gotIt = 0; - for( ; h; prev = h, h = h->next ){ - if(h->size>=ovl->size){ - gotIt = 1; - if(prev){ - prev->next = ovl; - }else{ - assert(h==re->head); - re->head = ovl; - } - ovl->next = h; - break; - } - } - if(!gotIt){ - /* End of list: ovl is biggest */ - assert(prev); - assert(!prev->next); - assert(prev->size < ovl->size); - prev->next = ovl; - } - } - /* MARKER(("Added mem chunk @%p #%d of size %d\n", mem, (int)re->headCount, (int)size)); */ - } - return; -} - -/** - Requests a chunk of memory from the memchunk cache. *size must be - the size of the block we would "like" to have. A value of zero - means to give back the first chunk (an O(1) op). - - deltaPolicy specifies a size tolerance. Legal values are: - - 0: no tolerance - only an exact size match qualifies. - - N>100: a percent larger the buffer may be. If a larger buffer is - found within that that size, it is returned. It is ignored if - !*size. If deltaPolicy>999 then any chunk at least as large as *size - will be accepted. - - N<100: at least *size, but no bigger than *size+N. - - N<0: the first buffer in the list will match, the same as if - *size==0. - - If non-NULL is returned, *size is updated to the size of the chunk. - - Potential TODO: change sig to include a param which specifies how - to interpret deltaPolicy (e.g. absolute vs percent vs - smaller-or-larger). - - Potential TODO: if !*size, interpret deltaPolicy as a preferred value -*/ -static void * cwal_memchunk_request(cwal_engine * e, cwal_size_t * size, - int deltaPolicy, - char const * debugDescription){ - cwal_memchunk_recycler * re = &e->reChunk; - cwal_memchunk_overlay * ovl = 0; - cwal_memchunk_overlay * left = 0; - void * rc = 0; - ++re->metrics.requests; - for( ovl = re->head; ovl; left = ovl, ovl = ovl->next ){ - ++re->metrics.searchComparisons; - if(deltaPolicy<0 - || !*size - || *size == ovl->size) break; - else if( deltaPolicy>100 ){ - /* Chunk size within (*size, *size+(deltaPolicy/100)) */ - cwal_size_t const max = (*size * (cwal_size_t)(deltaPolicy/100.0)); - assert(ovl->size!=*size && "Is caught above."); - if( ovl->size>*size - && (deltaPolicy>999 || ovl->size <= max) - ){ - break; - }else if(ovl->size > max){ - /* We won't find a match above this point */ - ovl = 0; - break; - } - }else if(deltaPolicy>0){ - /* Interpret as absolute byte count */ - if(ovl->size>*size - && ovl->size <= *size + deltaPolicy ){ - break; - }else if(ovl->size > *size * deltaPolicy){ - /* We won't find a match above this point */ - ovl = 0; - break; - } - } - } - if(!ovl) ++re->metrics.searchMisses; - else{ - /*MARKER(("Donating chunk of size %d for request of size %d. Context=%s\n", - (int)ch->size, (int)*size, debugDescription));*/ - if(debugDescription){/*avoid unused param warning*/} - assert(re->headCount>0); - if(left){ - assert(re->headCount>1); - assert(left->next == ovl); - left->next = ovl->next; - } - if(ovl->next){ - assert(re->headCount > (left ? 2 : 1)); - } - if(re->head == ovl){ - re->head = ovl->next; - } - *size = ovl->size; - memset(ovl, 0, *size); - rc = ovl; - assert(re->currentTotal >= *size); - re->currentTotal -= *size; - --re->headCount; - if(!re->headCount){ - assert(!re->head); - } - ++re->metrics.totalChunksServed; - re->metrics.totalBytesServed += *size; - re->metrics.runningAverageResponseSize = - (re->metrics.runningAverageResponseSize + *size)/2; - } - return rc; -} - - -static void cwal_memchunk_freeit( cwal_engine * e, cwal_memchunk_overlay * ovl ){ - cwal_memchunk_recycler * re = &e->reChunk; - assert(!ovl->next); - assert(re->headCount>0); - assert(re->currentTotal >= ovl->size); - --re->headCount; - re->currentTotal -= ovl->size; - *ovl = cwal_memchunk_overlay_empty; - cwal_free(e, ovl); -} - -/** - Frees up all entries in e->reChunk. -*/ -static void cwal_memchunks_free( cwal_engine * e ){ - cwal_memchunk_recycler * re = &e->reChunk; - cwal_memchunk_overlay * ovl; - for( ; (ovl = re->head); ){ - re->head = ovl->next; - ovl->next = 0; - cwal_memchunk_freeit(e, ovl); - } - assert(0==re->headCount); - assert(0==re->currentTotal); - re->head = 0; -} - - -int cwal_engine_memchunk_config( cwal_engine * e, - cwal_memchunk_config const * conf){ - if(!e || !conf) return CWAL_RC_MISUSE; - else{ - /* Adjust size... */ - cwal_memchunk_recycler * re = &e->reChunk; - cwal_memchunk_overlay * ovl; - if(!conf->maxChunkCount - || !conf->maxTotalSize - || !conf->maxChunkSize){ - /* The simplest case: disable the recycler. */ - cwal_memchunks_free( e ); - re->config = *conf; - return 0; - } - - if(re->config.maxChunkSize > conf->maxChunkSize){ - /* Trim now-too-large chunks. */ - cwal_memchunk_overlay * prev = 0; - cwal_memchunk_overlay * next = 0; - ovl = re->head; - assert(ovl); - for( ; ovl; ovl = next){ - next = ovl->next; - if(ovl->size > conf->maxChunkSize){ - if(prev) prev->next = next; - ovl->next = 0; - cwal_memchunk_freeit(e, ovl); - }else{ - prev = ovl; - } - } - } - - if(re->currentTotal > conf->maxTotalSize){ - /* Trim chunks until we're under the limit. */ - cwal_memchunk_overlay * prev = 0; - cwal_memchunk_overlay * next = 0; - ovl = re->head; - assert(ovl); - for( ; ovl && (re->currentTotal > conf->maxTotalSize); - ovl = next ){ - next = ovl->next; - if(ovl->size > conf->maxChunkSize){ - if(prev) prev->next = next; - ovl->next = 0; - cwal_memchunk_freeit(e, ovl); - }else{ - prev = ovl; - } - } - } - while(conf->maxChunkCount - ? (re->headCount > conf->maxChunkCount) - : !!re->headCount){ - /* Too many chunks. Lop off the smallest ones. */ - assert(re->head); - ovl = re->head; - re->head = ovl->next; - ovl->next = 0; - cwal_memchunk_freeit(e, ovl); - } - re->config = *conf; - return 0; - } -} - -cwal_kvp * cwal_kvp_alloc(cwal_engine *e){ - cwal_kvp * kvp; - cwal_recycler * re; - ++e->metrics.requested[CWAL_TYPE_KVP]; - re = cwal_recycler_get(e, CWAL_TYPE_KVP); - assert(re); - if(re->list){ - ++re->hits; - ++e->metrics.valuesRecycled; - kvp = (cwal_kvp*)re->list; - re->list = kvp->right; - kvp->right = 0; - --re->count; - } - else{ - ++re->misses; - ++e->metrics.valuesRecycleMisses; - kvp = (cwal_kvp*)cwal_malloc(e, sizeof(cwal_kvp)); - if(kvp){ - ++e->metrics.allocated[CWAL_TYPE_KVP]; - e->metrics.bytes[CWAL_TYPE_KVP] += sizeof(cwal_kvp); - } - } - if( kvp ) { - *kvp = cwal_kvp_empty; - } - return kvp; -} - -void cwal_kvp_clean( cwal_engine * e, /* cwal_scope * fromScope, */ - cwal_kvp * kvp ){ - if( kvp ){ - cwal_value * key = kvp->key; - cwal_value * value = kvp->value; - *kvp = cwal_kvp_empty; - if(key) cwal_value_unref2(e, key); - if(value) cwal_value_unref2(e, value); - } -} - -void cwal_kvp_free( cwal_engine * e, /* cwal_scope * fromScope, */ - cwal_kvp * kvp, char allowRecycle ){ - if( kvp ){ - cwal_kvp_clean(e/* , fromScope */, kvp); - if(allowRecycle){ - cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_KVP); - assert(re); - if(re->count < re->maxLength){ - assert(!kvp->right); - kvp->right = (cwal_kvp*)re->list; - re->list = kvp; - ++re->count; - }else{ - cwal_free(e, kvp); - } - } - else{ - cwal_free(e, kvp); - } - } -} - -cwal_value * cwal_kvp_key( cwal_kvp const * kvp ){ - return kvp ? kvp->key : NULL; -} - -cwal_value * cwal_kvp_value( cwal_kvp const * kvp ){ - return kvp ? kvp->value : NULL; -} - -int cwal_kvp_value_set( cwal_kvp * const kvp, - cwal_value * const v ){ - if(!kvp || !v) return CWAL_RC_MISUSE; - else{ - assert(kvp->key); - assert(kvp->value); - cwal_value_ref(v); - cwal_value_unref(kvp->value); - kvp->value = v; - return 0; - } -} - -int cwal_kvp_value_set2( cwal_kvp * const kvp, - cwal_value * const v ){ - if(!kvp || !v) return CWAL_RC_MISUSE; - else if(CWAL_VAR_F_CONST & kvp->flags){ - return CWAL_RC_CONST_VIOLATION; - } - return cwal_kvp_value_set(kvp, v); -} - -cwal_flags16_t cwal_kvp_flags( cwal_kvp const * kvp ){ - return kvp ? (cwal_flags16_t)(CWAL_VAR_F_PRESERVE & kvp->flags) : 0; -} - -cwal_flags16_t cwal_kvp_flags_set( cwal_kvp * kvp, cwal_flags16_t flags ){ - cwal_flags16_t const rc = (cwal_flags16_t)(CWAL_VAR_F_PRESERVE & kvp->flags); - if(CWAL_VAR_F_PRESERVE != flags){ - kvp->flags = flags; - } - return rc; -} - -#if !CWAL_OBASE_ISA_HASH -/** @internal - - Searches for the given key in the given kvp list. - - Returns the found item if a match is found, NULL if not. If prev is - not NULL then *prev is set to the returned value's left-hand-side - kvp from the linked list (or the end of the list, if no match is - found). A *prev value of NULL and return value of non-NULL - indicates that the result kvp was found at the head of the list. -*/ -static cwal_kvp * cwal_kvp_search( cwal_kvp * kvp, char const * key, - cwal_midsize_t keyLen, cwal_kvp ** prev){ - if(!kvp || !key ) return NULL; - else{ - cwal_kvp * left = 0; - char const * cKey; - cwal_size_t cLen; - int cmp; - for( left = 0; kvp; left = kvp, kvp = kvp->right ){ - assert( kvp->key ); - assert(kvp->right != kvp); - cKey = cwal_value_get_cstr(kvp->key, &cLen); - if(prev) *prev=left; - if(!cKey) continue /* we don't know where non-strings sort in this world! */; - else if(0 == (cmp = cwal_compare_cstr( key, keyLen, cKey, cLen ))){ - return kvp; - } -#if CWAL_KVP_TRY_SORTING - if(cmp<0) break; -#endif - } - if(prev) *prev = CWAL_KVP_TRY_SORTING ? left : 0; - return 0; - } -} - -/** - Variant of cwal_kvp_search() which takes a cwal_value key and uses - key->vtab->compare() to check for equivalence. -*/ -static cwal_kvp * cwal_kvp_search_v( cwal_kvp * kvp, - cwal_value const * key, - cwal_kvp ** prev){ - if(!kvp || !key ) return NULL; - else{ - cwal_kvp * left; - int cmp; - const int keyIsBool = CWAL_TYPE_BOOL==key->vtab->typeID; - for( left = 0; kvp; left = kvp, kvp = kvp->right ){ - const int kvpIsBool = CWAL_TYPE_BOOL==kvp->key->vtab->typeID; - assert(kvp->key); - assert(kvp->right != kvp); - if(key==kvp->key){ - cmp = 0; - }else if(keyIsBool || kvpIsBool){ - /* - 20190706: it was *finally* discovered (by accident) - that: - - var o = {}; - o[true]; - - Matches the first truthy property key! In our case, - it was the s2 object prototype's constructor method. - - Likewise: - - o[true] = 3; // bool-typed property key - o['x']; // ==> 3 - - Because this lookup explicitly does type-loose - comparisons and most things are equivalent to true - in that context. So... now we special-case a - type-strict lookup if either of key or kvp->key is-a - boolean. - */ - if(keyIsBool && kvpIsBool){ - cmp = key->vtab->compare(key, kvp->key); - /* And fall through. */ - }else{ - /* Do not allow a match if either key is a bool - but the other one is not. */ - continue; - } - }else{ - cmp = key->vtab->compare(key, kvp->key); - } - if(prev) *prev=left; - if(0==cmp) return kvp; -#if CWAL_KVP_TRY_SORTING - else if(cmp<0) break; -#endif - } - if(prev) *prev = CWAL_KVP_TRY_SORTING ? left : 0; - return 0; - } -} - -/** - Functionally identical to cwal_kvp_unset_v() but takes - its key in C-string form. -*/ -static int cwal_kvp_unset( cwal_engine * e, /* cwal_scope * fromScope, */ - cwal_kvp ** list, - char const * key, cwal_midsize_t keyLen ) { - /* assert(fromScope); */ - assert(e); - assert(key); - if( !e || !key || !list ) return CWAL_RC_MISUSE; - else if(!*list) return CWAL_RC_NOT_FOUND; - else { - cwal_kvp * left = 0; - cwal_kvp * kvp; - kvp = cwal_kvp_search( *list, key, keyLen, &left ); - if( !kvp ) return CWAL_RC_NOT_FOUND; - else if(left) left->right = kvp->right; - else { - assert(*list == kvp); - *list = kvp->right; - } - kvp->right = NULL; - cwal_kvp_free( e/* , fromScope */, kvp, 1 ); - return 0; - } -} - -/** - Unsets a the given key in the given kvp list. list must point to - the head of a cwal_kvp list, and if the head is the unset kvp then - *list is updated to point to kvp->right. - - Returns 0 on successs, CWAL_RC_NOT_FOUND if - no match is found. -*/ -static int cwal_kvp_unset_v( cwal_engine * e, cwal_kvp ** list, - cwal_value * key ) { - assert(e); - assert(key); - if( !e || !key || !list ) return CWAL_RC_MISUSE; - else if(!*list) return CWAL_RC_NOT_FOUND; - else { - cwal_kvp * left = 0; - cwal_kvp * kvp; - kvp = cwal_kvp_search_v( *list, key, &left ); - if( ! kvp ) return CWAL_RC_NOT_FOUND; - if(left) left->right = kvp->right; - else { - assert(*list==kvp); - *list = kvp->right; - } - kvp->right = NULL; - cwal_kvp_free( e, kvp, 1 ); - return 0; - } -} -#endif /* !CWAL_OBASE_ISA_HASH */ - -/** - Returns the client-supplied hash table size "trimmed" to some - "convenient" prime number (more or less arbitrarily chosen by this - developer - feel free to change/extend this range). -*/ -static uint16_t cwal_trim_hash_size( uint16_t hashSize ){ - if(hashSize < CwalConsts.MinimumHashSize) return CwalConsts.MinimumHashSize; -#define P(N) else if( hashSize <= N ) return N - /* TODO? add more granularity here. */ - P(17); P(37); P(53); P(71); - P(151); P(211); P(281); P(311); - P(379); P(433); P(503); P(547); - P(587); P(613); P(683); P(719); - P(751); P(1033); - P(1549); P(2153); - else return 3163; -#undef P -} - -/** - Returns the cwal_ptr_table step size "trimmed" to some "acceptable" - range. -*/ -static uint16_t cwal_trim_step( uint16_t step ){ - if(step < CwalConsts.MinimumStep) step = CwalConsts.MinimumStep; - return step; -} - - -/** - Allocates a new cwal_ptr_page object, owned by e. Returned NULL on - error. On success the returned value is empty-initialized. - - The page has n entries (hash table size) and the given step size. - - n MUST currently be the same as the owning table's hash size, but - there are plans to potentially change that, allowing new page sizes - to grow if collision counts become too high. -*/ -static cwal_ptr_page * cwal_ptr_page_create( cwal_engine * e, uint16_t n ){ - const uint32_t asz = (sizeof(void*) * n); - const uint32_t psz = asz + sizeof(cwal_ptr_page); - cwal_ptr_page * p = (cwal_ptr_page*)cwal_malloc(e, psz); - if(p){ - memset( p, 0, psz ); - p->list = (void**)(p+1); - p->entryCount = 0; - } - return p; -} - -/** - Allocates a new page for the given table. - - Returns CWAL_RC_OK on success. The only conceivable non-bug error - case here is CWAL_RC_OOM. -*/ -static int cwal_ptr_table_add_page( cwal_engine * e, cwal_ptr_table * t ){ - cwal_ptr_page * p = cwal_ptr_page_create( e, t->hashSize ); - if(!p) return CWAL_RC_OOM; - else { - cwal_ptr_page * tail = t->pg.tail; - if(!tail){ - assert(!t->pg.head); - t->pg.head = t->pg.tail = p; - }else{ - assert(t->pg.head); - assert(!tail->next); - t->pg.tail = tail->next = p; - } - assert(t->pg.tail->next != t->pg.head); - return 0; - } -} - -int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T, - uint16_t hashSize, - uint16_t step){ - int rc; - cwal_ptr_table * t; - if(!e || !T) return CWAL_RC_MISUSE; - hashSize = cwal_trim_hash_size(hashSize); - step = cwal_trim_step(step); - t = *T; - if(t){ - *t = cwal_ptr_table_empty; - }else { - cwal_size_t reqSize = sizeof(cwal_ptr_table); - t = (cwal_ptr_table*)cwal_memchunk_request(e, &reqSize, 0, - "cwal_ptr_table_create()"); - if(!t){ - t = (cwal_ptr_table*)cwal_malloc(e, reqSize); - }else{ - assert(reqSize==sizeof(cwal_ptr_table)); - } - if(!t) { - rc = CWAL_RC_OOM; - goto error; - } - *t = cwal_ptr_table_empty; - t->allocStamp = CwalConsts.AllocStamp; - } - - t->hashSize = hashSize; - t->step = step; - rc = cwal_ptr_table_add_page( e, t ); - if(rc) goto error; - assert( t->pg.head ); - if(!*T) *T = t; - return CWAL_RC_OK; - - error: - assert(0 != rc && "You seem to have failed to set an error code!"); - if(t && (t!=*T)){ - cwal_memchunk_add(e, t, sizeof(cwal_ptr_table)); - /* cwal_free(e, t); */ - } - return rc; -} - -int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t ){ - if(!e || !t) return CWAL_RC_MISUSE; - else{ - cwal_size_t const psz = sizeof(cwal_ptr_page) + - (sizeof(void*) * t->hashSize); - void const * stamp = t->allocStamp; - cwal_ptr_page * p = t->pg.head; - assert(t->hashSize || !p); - t->pg.head = t->pg.tail = NULL; - while( p ){ - cwal_ptr_page * n = p->next; - /* cwal_free(e, p); */ - cwal_memchunk_add(e, p, psz); - p = n; - } - *t = cwal_ptr_table_empty; - if( CwalConsts.AllocStamp == stamp ){ - cwal_free( e, t ); - /* cwal_memchunk_add(e, t, sizeof(cwal_ptr_table)); */ - }else{ - /* Assume t was stack allocated or is part of another - object.*/ - t->allocStamp = stamp; - } - return CWAL_RC_OK; - } -} - -int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state ){ - if(!t || !f) return CWAL_RC_MISUSE; - else{ - cwal_size_t i; - cwal_ptr_page * page = t->pg.head; - cwal_value ** val; - cwal_size_t seen; - int rc = 0; - for( ; page; page = page->next ){ - seen = 0; - for( i = 0; - (0==rc) - && (i < t->hashSize) - && (seen < page->entryCount); ++i ){ - val = (cwal_value**)&page->list[i]; - if(!*val) continue; - ++seen; - rc = f( val, state ); - page->list[i] = *val; - } - - } - return rc; - } -} - - -int cwal_ptr_table_mem_cost( cwal_ptr_table const * t, - uint32_t * mallocs, - uint32_t * memory ){ - enum { SV = sizeof(void*) }; - uint32_t a = 1; - uint32_t m = sizeof(cwal_ptr_table); - if(!t) return CWAL_RC_MISUSE; - else{ - cwal_ptr_page const * p = t->pg.head; - for( ; p; p = p->next ){ - ++a; - m += sizeof(cwal_ptr_page) - + (t->hashSize*SV); - } - } - if(mallocs) *mallocs = a; - if(memory) *memory = m; - return CWAL_RC_OK; -} - -static uint16_t cwal_ptr_table_hash( void const * key, - uint16_t step, - uint16_t hashSize){ -#if CWAL_VOID_PTR_IS_BIG - /* IF THE DEBUGGER LEADS YOU NEAR HERE... - try changing ptr_int_t back to uint64_t. - */ - typedef uint64_t ptr_int_t; -#else - typedef uint32_t ptr_int_t; -#endif -#if 1 - const ptr_int_t k = ((ptr_int_t)key); - const uint32_t v1 = (uint32_t)(k / step); - const uint16_t v2 = v1 % hashSize; - /* - MARKER("key=%p step=%u hashSize=%u k=%lu v1=%u v2=%u\n", - key, step, hashSize, k, v1, v2); - */ - return v2; -#else - return ((ptr_int_t)key) / step % hashSize; -#endif -} - - -int cwal_ptr_table_op( cwal_engine * e, - cwal_ptr_table * t, - void * key, - cwal_ptr_table_ops op ){ - cwal_ptr_page * pPage; - void * pRet = NULL; - uint16_t iKey; - int rc = 0; - if(!e || !t || !key) return CWAL_RC_MISUSE; - iKey = cwal_ptr_table_hash( key, t->step, t->hashSize ); - assert( iKey < t->hashSize ); - assert( t->pg.head ); - /* - TODO?: honor page-specific hashSize values, re-calculating the - hash value. if the step value changes while iterating. - */ - switch( op ){ - /* FIXME: refactor the 3 loops below into one loop at the start. - */ - case CWAL_PTR_TABLE_OP_SEARCH: - rc = CWAL_RC_NOT_FOUND; - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = pPage->list[iKey]; - if(!pRet) break; - else if(pRet == key){ - rc = CWAL_RC_OK; - break; - } - } - break; - case CWAL_PTR_TABLE_OP_INSERT:{ - assert(t->pg.head); - rc = CWAL_RC_OK; - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = pPage->list[iKey]; -#if 0 - MARKER("COMPARING STASHED %p AGAINST KEY %p\n", (void*)pRet, (void*)key); -#endif - if(!pRet) goto insert; - else if(pRet == key){ - rc = CWAL_RC_ALREADY_EXISTS; - break; - } - } -#if 0 - MARKER("INSERT NO AVAILABLE SLOT CONTAINS %p. " - "ADDING PAGE for hash %u rc=%d=%s\n", - (void *)key, iKey, rc, cwal_rc_cstr(rc)); -#endif - if(rc) break; - /* We reached the end of the tables and found - no empty slot. Add a page and insert it there. */ - rc = cwal_ptr_table_add_page( e, t ); - if(rc) break; - pPage = t->pg.tail; - insert: -#if 0 - MARKER("INSERTING %p (hash=%u) in list %p\n", (void*)key, iKey, (void *)pPage); -#endif - rc = CWAL_RC_OK; - assert(NULL != pPage); - pPage->list[iKey] = key; - ++pPage->entryCount; - } break; - case CWAL_PTR_TABLE_OP_REMOVE: - rc = CWAL_RC_NOT_FOUND; - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = pPage->list[iKey]; - if(!pRet) break; - else if(pRet == key){ - cwal_ptr_page * prevPage = pPage; - assert(pPage->entryCount>0); - /* hijack the loop to shift all other entries - down... */ - pPage->list[iKey] = NULL; - if(!pPage->next){ - --pPage->entryCount; - }else{ - /* Potential TODO: move empty pages - to the back of the list. */ - for( pPage = pPage->next; pPage; - prevPage = pPage, pPage = pPage->next ){ - if(!(prevPage->list[iKey] = pPage->list[iKey])){ - --prevPage->entryCount; - break; - } - else if(!pPage->next){ - pPage->list[iKey] = 0; - --pPage->entryCount; - } - } - } - rc = CWAL_RC_OK; -#if 0 - MARKER("REMOVING ENTRY %p for hash %u FROM PAGE #%u\n", (void*)pRet, iKey, x+1); -#endif - break; - } - } - break; - } - - return rc; -} - -int cwal_ptr_table_search( cwal_engine * e, - cwal_ptr_table * t, - cwal_value * key ){ - return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_SEARCH ); -} -int cwal_ptr_table_insert( cwal_engine * e, - cwal_ptr_table * t, - cwal_value * key ){ - return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_INSERT ); -} -int cwal_ptr_table_remove( cwal_engine * e, - cwal_ptr_table * t, - cwal_value * key ){ - return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_REMOVE ); -} - -/** - Adds an entry in e->weakp for p, initializing e->weakp - if needed. -*/ -static int cwal_weak_annotate( cwal_engine * e, void * p ){ - int rc; - cwal_ptr_table * pt = &e->weakp; - if(!pt->pg.head){ - cwal_size_t const hashSize = 151 - /* We don't expect many weak refs, so we'll try - a relatively small hash size. */; - rc = cwal_ptr_table_create(e, &pt, hashSize, sizeof(void *) ); - if(rc) return rc; - assert(pt->pg.head); - } - rc = cwal_ptr_table_op( e, pt, p, CWAL_PTR_TABLE_OP_INSERT ); - /* MARKER(("Annotating %s weak ptr @%p insert rc=%d\n",cwal_type_id_name(tid),p,rc)); */ - switch(rc){ - case 0: - case CWAL_RC_ALREADY_EXISTS: - return 0; - default: - return rc; - } -} - -/** - If p is found in e->weakr[tid] then its (shared) cwal_weak_ref is - returned, otherwise 0 is returned. -*/ -static cwal_weak_ref * cwal_weak_ref_search( cwal_engine * e, void *p, cwal_type_id tid ){ - cwal_weak_ref * r; - assert(e && p && (tid>=CWAL_TYPE_UNDEF && tidweakr[tid]; - for( ; r && (r->value != p); r = r->next ){} - return r; -} - -/** - Removes the entry for p added by cwal_weak_annotate() and - unsets the value field of each cwal_weak_ref in - e->weakr[t] where value==p. -*/ -static int cwal_weak_unregister( cwal_engine * e, void * p, - cwal_type_id t ){ - assert(p); - assert(t>=CWAL_TYPE_UNDEF && tweakp.pg.head) return 0; - else{ - - int const rc = cwal_ptr_table_op( e, &e->weakp, p, - CWAL_PTR_TABLE_OP_REMOVE ); - switch(rc){ - case 0:{ - /* MARKER(("Invalidating %s weak ptr to %p\n",cwal_type_id_name(t),p)); */ - cwal_weak_ref * r = cwal_weak_ref_search( e, p, t ); - if(r) r->value = NULL - /* because we share instances for any given (p,t) - combination, there can be no more matches in the - list. */ - ; - return 0; - } - case CWAL_RC_NOT_FOUND: - return 0; - default: - return rc; - } - } -} - -bool cwal_is_weak_referenced( cwal_engine * e, void * p ){ - return (e && p) - ? (0==cwal_ptr_table_op( e, &e->weakp, p, - CWAL_PTR_TABLE_OP_SEARCH )) - : 0; -} - -/** - Makes wr the head of the cwal_weak_ref chain starting at - e->weakr[wr->typeID]. Returns 0 on success, and the only non-bug - error cases are CWAL_RC_OOM. -*/ -static int cwal_weak_ref_insert( cwal_engine * e, cwal_weak_ref * wr ){ - int rc; - assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeIDvalue); - if(rc) return rc; - else{ - cwal_weak_ref * list; - assert(!wr->next); - list = e->weakr[wr->typeID]; - wr->next = list; - e->weakr[wr->typeID] = wr; - return 0; - } -} - -/** - Removes wr from e->weakr[wr->typeID] but does not free wr. -*/ -static void cwal_weak_ref_remove( cwal_engine * e, cwal_weak_ref * wr ){ - cwal_weak_ref * list; - cwal_weak_ref * prev = NULL; - assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeIDweakr[wr->typeID]; - for( ; list; prev = list, list = list->next ){ - if(wr != list) continue; - else if(prev) prev->next = wr->next; - else e->weakr[wr->typeID] = wr->next; - wr->next = NULL; - break; - } - assert(wr == list); -} - -/** - Zeroes out r and either adds r to e->recycler (if there is space) or - frees it (if no recycling space is available). - - Preconditions: - - - neither argument may be 0. - - r->next must be 0. - - Postconditions: r must be treated as if this function freed it - (because semantically it does). - -*/ -static void cwal_weak_ref_free2( cwal_engine * e, cwal_weak_ref * r ){ - cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_WEAK_REF); - assert(e && r && !r->next); - assert(!CWAL_MEM_IS_BUILTIN(r)); - if(re->maxLength>0 - && re->count < re->maxLength){ - *r = cwal_weak_ref_empty; - r->next = (cwal_weak_ref*)re->list; - re->list = r; - ++re->count; - } - else { - *r = cwal_weak_ref_empty; - cwal_free2( e, r, sizeof(cwal_weak_ref) ); - /* cwal_memchunk_add(e, r, sizeof(cwal_weak_ref)); */ - } -} - -void cwal_weak_ref_free( cwal_engine * e, cwal_weak_ref * r ){ - assert(e && r); - if(!e || !r || CWAL_MEM_IS_BUILTIN(r)) return; - else if(0==r->refcount || 0==--r->refcount){ - cwal_weak_ref_remove( e, r ); - cwal_weak_ref_free2(e, r); - } -} - -cwal_value * cwal_weak_ref_value( cwal_weak_ref * r ){ - if(!r||!r->value) return NULL; - else switch(r->typeID){ - case CWAL_TYPE_BOOL: - case CWAL_TYPE_NULL: - case CWAL_TYPE_UNDEF: - assert(r->value) - /* Because of how we allocate/share these, this will - always be true if r was validly allocated. */; - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_HASH: - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_STRING: - case CWAL_TYPE_UNIQUE: - case CWAL_TYPE_TUPLE: - return (cwal_value*)r->value; - case CWAL_TYPE_KVP: - case CWAL_TYPE_SCOPE: - case CWAL_TYPE_WEAK_REF: - return NULL; - case CWAL_TYPE_XSTRING: - case CWAL_TYPE_ZSTRING: - assert(!"Not possible"); - default: - assert(!"Unhandled type!"); - return NULL; - } -} - - -/** - Allocates a new cwal_weak_ref. If possible, it takes the head of - the recycler list, else is cwal_malloc()s it. The returned value must - eventually be passed to cwal_weak_ref_free() or - cwal_weak_ref_free2(), depending on its state at cleanup time. - - If cwal_weak_ref_search(e,ptr,tid) returns a value then this - function returns the same one. - - It is up to the caller to increment the return'd object's refcount. -*/ -static cwal_weak_ref * cwal_weak_ref_alloc( cwal_engine * e, void * ptr, - cwal_type_id tid ){ - cwal_weak_ref * r; - ++e->metrics.requested[CWAL_TYPE_WEAK_REF]; - r = cwal_weak_ref_search(e, ptr, tid); - if(!r){ - cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_WEAK_REF); - r = (cwal_weak_ref *)re->list; - if(r){ - assert(re->count); - ++re->hits; - re->list = r->next; - --re->count; - }else{ - ++re->misses; - r = cwal_malloc2(e, sizeof(cwal_weak_ref)); - if(r){ - ++e->metrics.allocated[CWAL_TYPE_WEAK_REF]; - e->metrics.bytes[CWAL_TYPE_WEAK_REF] += sizeof(cwal_weak_ref); - } - } - if(r){ - *r = cwal_weak_ref_empty; - r->value = ptr; - r->typeID = tid; - } - if(cwal_weak_ref_insert(e, r)){ - cwal_weak_ref_free2(e, r); - r = NULL; - } - } - return r; -} - -cwal_weak_ref * cwal_weak_ref_new( cwal_value * v ){ - cwal_engine * e = v ? ((v && v->scope) ? v->scope->e : NULL) : NULL; - if(!e && !CWAL_MEM_IS_BUILTIN(v)) return NULL; - else{ - cwal_type_id const tid = v->vtab->typeID; - cwal_weak_ref * r = NULL; - switch(tid){ - case CWAL_TYPE_UNDEF: - r = &CWAL_BUILTIN_VALS.wref.wUndef; - break; - case CWAL_TYPE_NULL: - r = &CWAL_BUILTIN_VALS.wref.wNull; - break; - case CWAL_TYPE_BOOL: - r = (&CWAL_BUILTIN_VALS.vTrue == v) - ? &CWAL_BUILTIN_VALS.wref.wTrue - : &CWAL_BUILTIN_VALS.wref.wFalse; - break; - case CWAL_TYPE_STRING: - if(CWAL_BUILTIN_VALS.vEmptyString==v) r = &CWAL_BUILTIN_VALS.wref.wStrEmpty; - else{/* It's one of the length-1 strings */} - break; - case CWAL_TYPE_INTEGER:{ - /* - Shared weak-ref-to-int removed 20171202 because (A) - the built-in int range is now variable, (B) adding - static weak refs to all of them would be a huge waste - (weak refs to ints are NEVER used), and (C) ... i - forgot (C) while reformatting this comment :/. - */ - break; - } - case CWAL_TYPE_DOUBLE: - if(CWAL_MEM_IS_BUILTIN(v)){ - if(CWAL_BUILTIN_VALS.vDblM1==v) r = &CWAL_BUILTIN_VALS.wref.wDblM1; - else if(CWAL_BUILTIN_VALS.vDbl0==v) r = &CWAL_BUILTIN_VALS.wref.wDbl0; - else if(CWAL_BUILTIN_VALS.vDbl1==v) r = &CWAL_BUILTIN_VALS.wref.wDbl1; - else{assert(!"Impossible!");} - } - break; - default: - break; - } - if(!r){ - r = cwal_weak_ref_alloc(e, v, v->vtab->typeID); - if(r){ - assert(r->value == v); - assert(r->typeID == v->vtab->typeID); - ++r->refcount; - } - } - return r; - } -} - -cwal_weak_ref * cwal_weak_ref_custom_new( cwal_engine * e, void * p ){ - if(!e || CWAL_MEM_IS_BUILTIN(p)) return NULL; - else{ - cwal_weak_ref * r = cwal_weak_ref_alloc(e, p, CWAL_TYPE_WEAK_REF); - if(r){ - assert(r->value == p); - assert(r->typeID == CWAL_TYPE_WEAK_REF); - ++r->refcount; - } - return r; - } -} - -void * cwal_weak_ref_custom_ptr( cwal_weak_ref * r ){ - return (r && (CWAL_TYPE_WEAK_REF==r->typeID)) - ? r->value - : NULL; -} - -void * cwal_weak_ref_custom_check( cwal_engine * e, void * p ){ - if(!e || !p) return NULL; - else{ - cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF); - if(r){ - assert(r->value==p); - } - return r ? r->value : NULL; - } -} - -bool cwal_weak_ref_custom_invalidate( cwal_engine * e, void * p ){ - if(!e || !p) return 0; - else{ - cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF); - if(r) r->value = NULL; - return r ? 1 : 0; - } -} - - -static int cwal_engine_init_interning(cwal_engine * e){ - cwal_ptr_table * t = &e->interned; - /* notes to self regarding table size... - - The following gives me a really good fill rate for - the low page (over 90%) for 500 random strings: - - entries= 3*1024/(2*sizeof(void*)) - - hashSize= trim_hash(entriesPerPage*33/50) - - But lots of tables (9 on average, it seems) - - */ - uint16_t const entriesPerPage = -#if 0 - /* testing a leak of interned strings. */ - 3 -#elif 0 - 1024 * 4 / sizeof(void*) -#elif 0 - 281 /* mediocre */ -#elif 1 - 379 /* reasonable */ -#elif 0 - 503 -#else - /*cwal_trim_hash_size(200)*/ - /* not bad 240 */ - /* 281 seems to give a good size for 500-string tests. */ - 512 * 5 / (sizeof(void*)) /* also not too bad, about 2.5kb/page (64-bit), low page count. */ - /* One test: with a table size of 3163 we got - over 90% in the first hit and 99% in the - second hit with 478 strings. - - With 433 entries/page we got about 58% - in the 1st hit, 85 in the 2nd hit. - - After much experimentation, a starting page size of about - 550 seems to provide a good basis here. Increasing the size - by 8 (later: 8 what?) doesn't give us many fewer tables and - does cost a lot more memory. - */ -#endif - ; - uint16_t const hashSize = - cwal_trim_hash_size(entriesPerPage); - uint16_t const stepSize = 0 /* not actually used by this particular table */; - /*MARKER("INTERNED STRING TABLE hashSize=%u stepSize=%u\n", hashSize, stepSize);*/ - return cwal_ptr_table_create(e, &t, hashSize, stepSize ); -} - - - -cwal_hash_t cwal_hash_bytes( void const * _zKey, cwal_size_t nKey ){ - unsigned char const * zKey = (unsigned char const *)_zKey; -#if 0 - /* - FNV-xx. These both well for our typical inputs. Marginally more - collisions when using the 32-bit seeds on 64-bit arch. - */ -# if CWAL_SIZE_T_BITS < 64 - cwal_hash_t const prime = 16777619U; - cwal_hash_t const offset = 2166136261U; -# else - cwal_hash_t const prime = 1099511628211U; - cwal_hash_t const offset = 14695981039346656037U; -# endif - cwal_size_t i; - cwal_hash_t hash = offset; - for( i = 0; i < nKey; ++i ){ -# if 0 - /* FNV-1 */ - hash = hash * prime; - hash = hash ^ zKey[i]; -# else - /* FNV-1a */ - /* Works a tick better than FNV-1 in my tests */ - hash = hash ^ zKey[i]; - hash = hash * prime; -# endif - } - return hash; - /* end FNV-1/1a */ -#elif 0 - /* - CRC32a: http://www.hackersdelight.org/hdcodetxt/crc.c.txt - */ -# define reverse(x) \ - x = ((x & 0x55555555) << 1) | ((x >> 1) & 0x55555555); \ - x = ((x & 0x33333333) << 2) | ((x >> 2) & 0x33333333); \ - x = ((x & 0x0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F); \ - x = (x << 24) | ((x & 0xFF00) << 8) | \ - ((x >> 8) & 0xFF00) | (x >> 24) - int i, j; - cwal_hash_t byte, crc; - cwal_size_t n; - i = 0; - crc = 0xFFFFFFFF; - for( n = 0; n < nKey; ++n ){ - byte = (unsigned char)zKey[i]; - reverse(byte); - for (j = 0; j <= 7; j++) { - if ((int)(crc ^ byte) < 0) - crc = (crc << 1) ^ 0x04C11DB7; - else crc = crc << 1; - byte = byte << 1; - } - i = i + 1; - } - crc = ~crc; - reverse(crc); - return crc; -# undef reverse -#elif 0 - /* - CRC32b: http://www.hackersdelight.org/hdcodetxt/crc.c.txt - */ - int j; - unsigned char byte; - cwal_hash_t crc, mask; - cwal_size_t n = 0; - crc = 0xFFFFFFFF; - for ( ; n < nKey; ++n) { - byte = (unsigned char)zKey[n]; - crc = crc ^ byte; - for (j = 7; j >= 0; j--) { - mask = -(crc & 1); - crc = (crc >> 1) ^ (0xEDB88320 & mask); - } - } - return ~crc; -#elif 0 - /* - CRC32e: http://www.hackersdelight.org/hdcodetxt/crc.c.txt - - HOLY COW: this collides about every 2nd string! - - Worst. Hash. Ever. - - Seriously, in the s2 unit tests: interning tables with space for - 379 entries each, holdling a total of 264 strings: 32 tables are - needed with this hash! - */ - int j; - cwal_hash_t byte, c; - cwal_int_t crc; - cwal_size_t i; - const unsigned int g0 = 0xEDB88320, g1 = g0 >> 1, - g2 = g0 >> 2, g3 = g0 >> 3; -# if CWAL_SIZE_T_BITS < 64 - crc = 0xFFFFFFFF; -# else - crc = 0xFFFFFFFFFFFFFFFF; -# endif - for(i = 0; i < nKey; ++i ){ - byte = (unsigned char)zKey[i]; - crc = crc ^ byte; - for (j = 1; j >= 0; j--) { - c = ((crc<<31>>31) & g3) ^ ((crc<<30>>31) & g2) ^ - ((crc<<29>>31) & g1) ^ ((crc<<28>>31) & g0); - crc = ((unsigned)crc >> 4) ^ c; - } - i = i + 1; - } - return (cwal_hash_t)~crc; -#elif 1 - /* another experiment... */ - /* 20181122: this one seems to perform the best for the s2 unit - test suite, measured in terms of how many string interning - tables get allocated and how tightly packed they get. */ - cwal_hash_t h = 0; - unsigned char const * p = (unsigned char const *)zKey; - cwal_size_t i = 0; - /* This one performs a tick better than FNV-1a in my brief tests */ - for( ; i> shift)) & mask; - } -# elif 0 - /* This one seems to perform (hash-wise) pretty well, by just a tick, - based on simple tests with the string interning table. - It's just a tiny tick slower than option #1. - - This hash in combination with a table size of 547 performs - quite well for my pseudo-scientific random-strings tests. - - Longer-term tests show it not to perform as well as FNV - for s2's unit tests. - */ - /* "djb2" algo code taken from: http://www.cse.yorku.ca/~oz/hash.html */ - static const cwal_hash_t seed = 5381; - char const * vstr = (char const *)zKey; - cwal_hash_t hash = seed; - cwal_size_t i = 0; - for( ; iinterned; - if( !t->pg.head ) return CWAL_RC_NOT_FOUND; - iKey = (uint16_t)(cwal_hash_bytes( zKey, nKey ) % t->hashSize); - if(itemIndex) *itemIndex = iKey; - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = (cwal_value*)pPage->list[iKey]; - if(!pRet) break /* end of the table list */; - else if(pRet){ - cwal_string const * sRet = CWAL_STR(pRet); - assert(sRet); - if(sRet && - (CWAL_STRLEN(sRet) == nKey) - && (0 == memcmp( cwal_string_cstr(sRet), - zKey, nKey ))){ - if(out) *out = pRet; - if(pageIndex) *pageIndex = pPage; - return CWAL_RC_OK; - } - } - } - return CWAL_RC_NOT_FOUND; -} - -int cwal_interned_search_val( cwal_engine * e, - cwal_value const * v, - cwal_value ** out, - cwal_ptr_page ** pageIndex, - uint16_t * itemIndex ){ - cwal_ptr_page * pPage = NULL; - cwal_value * pRet = NULL; - uint16_t iKey = 0; - cwal_ptr_table *t; - if(!e || !v) return CWAL_RC_MISUSE; - t = &e->interned; - if( !t->pg.head ) return CWAL_RC_NOT_FOUND; - iKey = (uint16_t)(v->vtab->hash(v) % t->hashSize); - if(itemIndex) *itemIndex = iKey; - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = (cwal_value*)pPage->list[iKey]; - if(!pRet) break /* end of the table list for this hash */; - else if(pRet){ - if((v==pRet) - || ((v->vtab == pRet->vtab /* enforce strict type comparisons */) - && (0 == v->vtab->compare(v, pRet)))){ - if(out) *out = pRet; - if(pageIndex) *pageIndex = pPage; - return CWAL_RC_OK; - } - } - } - return CWAL_RC_NOT_FOUND; -} - - -int cwal_interned_insert( cwal_engine * e, cwal_value * v ){ - cwal_ptr_page * pPage = NULL; - cwal_ptr_page * pageIndex = NULL; - cwal_value * pRet = NULL; - uint16_t iKey = 0; - int rc; - cwal_ptr_table * t; - if(!(CWAL_FEATURE_INTERN_STRINGS & e->flags)) return CWAL_RC_UNSUPPORTED; - else if(!e || !v) return CWAL_RC_MISUSE; - t = &e->interned; - if(!t->pg.head){ - rc = cwal_engine_init_interning( e ); - if(rc) return rc; - assert(t->pg.head); - } - - rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey ); - if( pRet ) { - assert(pageIndex); - assert( (pRet == v) && "i'm fairly sure this holds with the current code."); - return CWAL_RC_ALREADY_EXISTS; - } - assert(!pageIndex); - - /** - Search just failed, so we need to add an entry. Check if one - of our existing pages has a slot... - */ - for(pPage = t->pg.head; pPage; pPage = pPage->next ){ - pRet = pPage->list[iKey]; - if(!pRet) goto insert; - } - if(!pPage){ - /* We reached the end of the table and found no empty slot - (maybe had a collision). Add a page and insert the value - there. */ - rc = cwal_ptr_table_add_page( e, t ); - if(rc) return rc; - pPage = t->pg.tail; - } - insert: - rc = CWAL_RC_OK; - assert(NULL != pPage); - assert(!pPage->list[iKey]); - pPage->list[iKey] = v; - CWAL_TR_VCS(e,v); - CWAL_TR2(e,CWAL_TRACE_VALUE_INTERNED); - ++pPage->entryCount; - return rc; -} - -int cwal_interned_remove( cwal_engine * e, - cwal_value const * v, - cwal_value ** out ){ - cwal_ptr_page * pPage = 0; - cwal_value * pRet = NULL; - cwal_ptr_table * t; - uint16_t iKey = 0; - cwal_ptr_page * pageIndex = NULL; - int rc; - if(!e || !v) return CWAL_RC_MISUSE; - t = &e->interned; - if(!t->pg.head) return CWAL_RC_NOT_FOUND; - - rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey ); - if( !pRet ) { - assert( CWAL_RC_NOT_FOUND == rc ); - return rc; - } - else if(rc){ - assert(!"Cannot happen"); - return rc; - } - if( out ) *out = pRet; - CWAL_TR_VCS(e,pRet); - CWAL_TR2(e,CWAL_TRACE_VALUE_UNINTERNED); - assert( pageIndex ); - for( pPage = pageIndex; pPage; pPage = pPage->next ){ - /* Remove the entry. If any higher-level pages contain that - key, move it into this page (up through to the top-most - page which has a matching key). - */ - pPage->list[iKey] = pPage->next - ? pPage->next->list[iKey] - : 0; - if( !pPage->list[iKey] ) { - --pPage->entryCount; - break; - } - } - assert( pPage && (0 == pPage->list[iKey]) ); - return CWAL_RC_OK; -} - -cwal_obase * cwal_value_obase( cwal_value * const v ){ - return CWAL_VOBASE(v); -} - - -void cwal_value_cleanup_noop( cwal_engine * e, void * v ){ - if(e || v){/*avoid unused param warning*/} -} - -void cwal_value_cleanup_integer( cwal_engine * e, void * self ){ -#if 1 - /* Only disable this block when chasing bugs. */ - cwal_value * v = (cwal_value *)self; - *CWAL_INT(v) = 0; -#endif - if(e){/*avoid unused param warning*/} -} - -void cwal_value_cleanup_double( cwal_engine * e, void * self ){ - static const cwal_double_t zero = 0.0; - cwal_value * v = (cwal_value *)self; - memcpy(CWAL_DBL_NONULL(v), &zero, sizeof(cwal_double_t)); - if(e){/*avoid unused param warning*/} -} - - -void cwal_value_cleanup_unique( cwal_engine * e, void * self ){ - cwal_value * v = (cwal_value *)self; - cwal_value ** wrapped = CWAL_UNIQUE_VALPP(v); - if(e){/*avoid unused param warning*/} - assert(wrapped); - if(*wrapped){ - cwal_value * w = *wrapped; - *wrapped = 0; - if(!CWAL_V_IS_IN_CLEANUP(w)){ - /* - Without this is-in-cleanup check, code like the - following (s2) crashes here during scope cleanup: - - var e = enum {a:{x:1},b:{x:2}}; - e.a.value.b = e.b; - e.b.value.a = e.a; - - 20180105: a string key and its Unique-type wrapper were - being cleaned up during scope cleanup and the string got - recycled (as one does) during that process before the - Unique wrapper did. That ended up triggering the - assert() below. As of 20180105, the cleanup process - gc-queues all values, not just containers, which - resolves this case. It's just amazing that it didn't - trigger sooner. - - Addenda: the root cause, it turns out, is that Uniques - and Tuples live in the cwal_scope::mine::headPod list, - not headObj, and can therefore be destroyed after their - values are. - */ - assert( CWAL_REFCOUNT(w) || CWAL_MEM_IS_BUILTIN(w) ); - cwal_value_unref(w); - } - } -} - -void cwal_value_cleanup_tuple( cwal_engine * e, void * self ){ - cwal_value * v = (cwal_value *)self; - cwal_tuple * p = CWAL_TUPLE(v); - cwal_size_t i; - assert(p && p->n); - for( i = 0; i < p->n; ++i ){ - /* hashtag CWAL_V_GOES_IN_HEADOBJ */ - if(!CWAL_V_IS_IN_CLEANUP(p->list[i])){ - /* ^^^ cleanup check is hypothetically needed for certain - constellations because Tuples are managed via the - cwal_scope::headPod list, not headObj. That said, it's - never been seen to trigger a problem before (probably - because tuples are seldom used?).*/ - cwal_value_unref(p->list[i]); - } - p->list[i] = 0; - } - cwal_free2(e, p->list, sizeof(cwal_value*) * p->n); - *p = cwal_tuple_empty; -} - -cwal_type_id cwal_value_type_id( cwal_value const * v ){ - return (v && v->vtab) ? v->vtab->typeID : CWAL_TYPE_UNDEF; -} - -cwal_value_type_name_proxy_f cwal_engine_type_name_proxy( cwal_engine * e, - cwal_value_type_name_proxy_f f ){ - cwal_value_type_name_proxy_f rc = e ? e->type_name_proxy : 0; - if(e) e->type_name_proxy = f; - return rc; -} - -char const * cwal_value_type_name2( cwal_value const * v, - cwal_size_t * len){ - if(!v || !v->vtab) return NULL; - else{ - cwal_engine const * e = v->scope ? v->scope->e : NULL; - char const * rc = NULL; - if(e && e->type_name_proxy){ - rc = e->type_name_proxy(v, len); - } - if(!rc){ - rc = v->vtab->typeName; - if(rc && len) *len = cwal_strlen(rc); - } - return rc; - } -} - -char const * cwal_value_type_name( cwal_value const * v ){ - return v ? cwal_value_type_name2(v, NULL) : NULL; -} - -char const * cwal_type_id_name( cwal_type_id id ){ - cwal_value_vtab const * t = 0; - switch(id){ - case CWAL_TYPE_BOOL: t = &cwal_value_vtab_bool; break; - case CWAL_TYPE_UNDEF: t = &cwal_value_vtab_undef; break; - case CWAL_TYPE_NULL: t = &cwal_value_vtab_null; break; - case CWAL_TYPE_STRING: t = &cwal_value_vtab_string; break; - case CWAL_TYPE_INTEGER: t = &cwal_value_vtab_integer; break; - case CWAL_TYPE_DOUBLE: t = &cwal_value_vtab_double; break; - case CWAL_TYPE_ARRAY: t = &cwal_value_vtab_array; break; - case CWAL_TYPE_OBJECT: t = &cwal_value_vtab_object; break; - case CWAL_TYPE_NATIVE: t = &cwal_value_vtab_native; break; - case CWAL_TYPE_BUFFER: t = &cwal_value_vtab_buffer; break; - case CWAL_TYPE_FUNCTION: t = &cwal_value_vtab_function; break; - case CWAL_TYPE_EXCEPTION: t = &cwal_value_vtab_exception; break; - case CWAL_TYPE_HASH: t = &cwal_value_vtab_hash; break; - case CWAL_TYPE_UNIQUE: t = &cwal_value_vtab_unique; break; - case CWAL_TYPE_TUPLE: t = &cwal_value_vtab_tuple; break; - case CWAL_TYPE_SCOPE: return "cwal_scope"; - case CWAL_TYPE_KVP: return "cwal_kvp"; - case CWAL_TYPE_WEAK_REF: return "cwal_weak_ref"; - case CWAL_TYPE_XSTRING: return "x-string"; - case CWAL_TYPE_ZSTRING: return "z-string"; - case CWAL_TYPE_LISTMEM: return "cwal_list"; - default: break; - } - return t ? t->typeName : NULL; -} - -bool cwal_value_is_undef( cwal_value const * v ){ - return ( !v || !v->vtab || (v->vtab==&cwal_value_vtab_undef)) - ? 1 : 0; -} - -#define ISA(T,TID) bool cwal_value_is_##T( cwal_value const * v ) { \ - /*return (v && v->vtab) ? cwal_value_is_a(v,CWAL_TYPE_##TID) : 0;*/ \ - return (v && (v->vtab == &cwal_value_vtab_##T)) ? 1 : 0; \ - } - -ISA(null,NULL) - ISA(bool,BOOL) - ISA(integer,INTEGER) - ISA(double,DOUBLE) - ISA(string,STRING) - ISA(array,ARRAY) - ISA(object,OBJECT) - ISA(native,NATIVE) -/* ISA(buffer,BUFFER) */ - ISA(function,FUNCTION) - ISA(exception,EXCEPTION) - ISA(hash,HASH) - ISA(unique,UNIQUE) - ISA(tuple,TUPLE) -#undef ISA - - - bool cwal_value_is_buffer( cwal_value const * v ){ - cwal_buffer_obj const * bo = CWAL_BUFOBJ(v); - assert(bo ? (bo==bo->buf.self) : 1); - return (bo && bo == bo->buf.self) ? 1 : 0; -} - -bool cwal_value_is_number( cwal_value const * v ){ - if(!v) return 0; - else switch(v->vtab->typeID){ - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_BOOL: - return 1; - default: - return 0; - } -} - - -void cwal_finalizer_f_fclose( cwal_engine * e, void * m ){ - if(e){/*avoid unused param warning*/} - if(m && (m != stdout) && (m != stderr) && (m != stdin)){ - fclose( (FILE*)m ); - } -} - -void * cwal_realloc_f_std( void * state, void * m, cwal_size_t n ){ - if( 0 == n ){ - free( m ); - return NULL; - }else if( !m ){ - return malloc( n ); - }else{ - if(state){/*avoid unused param warning*/} - return realloc( m, n ); - } -} - -int cwal_output_f_FILE( void * state, void const * src, cwal_size_t n ){ - if( !state || !src || !n ) return 0; - return (1 == fwrite( src, n, 1, state ? (FILE*)state : stdout )) - ? CWAL_RC_OK - : CWAL_RC_IO; -} - -int cwal_output_flush_f_FILE( void * f ){ - return fflush(f ? (FILE*)f : stdout) - ? CWAL_RC_IO - : 0 - ; -} - -int cwal_output_f_buffer( void * state, void const * src, cwal_size_t n ){ - cwal_output_buffer_state * ob = (cwal_output_buffer_state*) state; -#if 1 - return cwal_buffer_append(ob->e, ob->b, src, n); -#else - int rc = 0; - cwal_output_buffer_state * ob = (cwal_output_buffer_state *)state_; - cwal_size_t sz = ob->b->used+n+1; - if(sz > ob->b->capacity){ - /* TODO? expand by some factor */ - /* sz = sz * 13 / 10; */ - rc = cwal_buffer_reserve(ob->e, ob->b, sz); - } - if(!rc){ - rc = cwal_buffer_append( ob->e, ob->b, n ); - } - return rc; -#endif -} - -void cwal_output_buffer_finalizer( cwal_engine * e, void * m ){ - cwal_output_buffer_state * ob = (cwal_output_buffer_state *)m; - assert(ob->e == e); - cwal_buffer_reserve(e, ob->b, 0); - ob->e = NULL; - *ob = cwal_output_buffer_state_empty; -} - -void * cwal_malloc( cwal_engine * e, cwal_size_t n ){ - unsigned char * rc = 0; - cwal_size_t const origN = n; - if(!e || !n || !e->vtab) return 0; - n = CWAL_MEMSZ_PAD(n); - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - n += sizeof(void*) - /* To store the size in. Must be generically aligned, thus - we don't use sizeof(uint32_t). */; -#if 64==CWAL_SIZE_T_BITS - if(n > 0xFFFFFFFF){ - /* b/c our size marker is explicitly uint32_t. */ - return NULL; - } -#endif - } - CWAL_TR_MEM(e,rc,n); - if(n < origN) return NULL /* overflow after adjustment */; - else if(/* Check cap constraints... */ - /* too-large single alloc */ - (e->vtab->memcap.maxSingleAllocSize - && (n > e->vtab->memcap.maxSingleAllocSize)) - || /* too many concurrent allocs */ - (e->vtab->memcap.maxConcurrentAllocCount - && (e->memcap.currentAllocCount - >= e->vtab->memcap.maxConcurrentAllocCount)) - || /* too much concurrent memory */ - (e->vtab->memcap.maxConcurrentMem - && (e->memcap.currentMem + n - > e->vtab->memcap.maxConcurrentMem)) - || /* too many total allocs */ - (e->vtab->memcap.maxTotalAllocCount - && (e->memcap.totalAllocCount - >= e->vtab->memcap.maxTotalAllocCount)) - || /* Too much total memory... */ - (e->vtab->memcap.maxTotalMem - && (e->memcap.currentMem + n - > e->vtab->memcap.maxTotalMem)) - ){ - return 0; - } - rc = (unsigned char *) - e->vtab->allocator.realloc( e->vtab->allocator.state.data, NULL, n ); - if(rc){ - if( ++e->memcap.currentAllocCount > e->memcap.peakAllocCount ){ - e->memcap.peakAllocCount = - e->memcap.currentAllocCount; - } - e->memcap.totalMem += n; - ++e->memcap.totalAllocCount; - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - /* Stamp the size and adjust rc. */ - cwal_memsize_t * sz = (cwal_memsize_t*)rc; - *sz = (cwal_memsize_t)n; - rc += sizeof(void*); - e->memcap.currentMem += n - /* We can only decrement this if overallocating, so - only increment if we overallocate. */; - if(e->memcap.peakMem < e->memcap.currentMem){ - e->memcap.peakMem = e->memcap.currentMem; - } - } - } - CWAL_TR2(e,CWAL_TRACE_MEM_MALLOC); - return rc; -} - -void * cwal_malloc2( cwal_engine * e, cwal_size_t n ){ - cwal_size_t newN = n; - void * rc = cwal_memchunk_request(e, &newN, - (CWAL_F_TRACK_MEM_SIZE & e->flags) - ? 150 : 125 - /* If over-allocation/tracking - is on, we can recover all - slack bytes when the mem is - passed to cwal_free2(), so - allow more leeway in the size - of the recycled chunk. */, - "cwal_malloc2()"); - if(!rc){ - rc = cwal_malloc(e, n); - } - return rc; -} - - -void cwal_free( cwal_engine * e, void * m ){ - if(e && m){ - assert(e != m /* check for this corner case: e is allocated - before cwal_malloc() and friends are - configured, so e was allocated on the stack or - before CWAL_F_TRACK_MEM_SIZE could take - effect. */); - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(m); - assert(e->memcap.currentMem >= *sz); - e->memcap.currentMem -= *sz; - m = sz; - } - CWAL_TR_MEM(e,m,0); - CWAL_TR2(e,CWAL_TRACE_MEM_FREE); - e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, 0 ); - --e->memcap.currentAllocCount; - } -} - -void cwal_free2(cwal_engine * e, void * mem, cwal_size_t size ){ - assert(e); - if(mem){ - assert(e != mem /* check for this corner case: e is allocated - before cwal_malloc() and friends are - configured, so e was allocated on the stack or - before CWAL_F_TRACK_MEM_SIZE could take - effect. */); - if(size) cwal_memchunk_add(e, mem, size); - else cwal_free(e, mem); - } -} - -void * cwal_realloc( cwal_engine * e, void * m, cwal_size_t n ){ - if(!e || !e->vtab) return 0; - else if( 0 == n ){ - cwal_free( e, m ); - return NULL; - }else if( !m ){ - return cwal_malloc( e, n ); - }else{ - unsigned char * rc; - uint32_t oldSize = 0; - cwal_size_t const origN = n; - n = CWAL_MEMSZ_PAD(n); - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(m); - oldSize = *sz; - assert(oldSize>0); - assert(e->memcap.currentMem >= oldSize); - m = sz; - n += sizeof(void*); -#if 64==CWAL_SIZE_T_BITS - if(n > 0xFFFFFFFF){ - /* b/c our size marker is explicitly uint32_t. */ - return NULL; - } -#endif - } - CWAL_TR_MEM(e,m,n); - CWAL_TR2(e,CWAL_TRACE_MEM_REALLOC); - if(n < origN) return NULL /* overflow after adjustment */; - else if(/* Allocation is too big... */ - (e->vtab->memcap.maxSingleAllocSize - && (n > e->vtab->memcap.maxSingleAllocSize)) - || /* Too much concurrent memory... */ - (e->vtab->memcap.maxConcurrentMem - && (e->memcap.currentMem - oldSize + n - > e->vtab->memcap.maxConcurrentMem)) - || /* Too much total memory... */ - (e->vtab->memcap.maxTotalMem - && (e->memcap.currentMem + n - > e->vtab->memcap.maxTotalMem)) - ){ - rc = NULL; - }else{ - rc = (unsigned char*) - e->vtab->allocator.realloc( e->vtab->allocator.state.data, - m, n ); - if(rc && (CWAL_F_TRACK_MEM_SIZE & e->flags)){ - /* update e->memcap, re-stamp memory size */ - cwal_memsize_t * sz = (cwal_memsize_t*)rc; - *sz = n; - rc += sizeof(void*); - e->memcap.currentMem -= oldSize; - e->memcap.currentMem += n; - } - } - return rc; - } -} - -static cwal_size_t cwal_scope_sweep_r0( cwal_scope * s ){ - cwal_engine * e = s->e; - cwal_value * v; - cwal_value * n; - cwal_size_t rc = 0; - cwal_value * r0 = s->mine.r0; - assert(e); - CWAL_TR_MSG(e,"s->mine.r0:"); - CWAL_TR_SV(e,s,s->mine.r0); - CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK); - s->mine.r0 = NULL; - for(v = r0; v; v = n ){ - n = v->right; - if(v->scope!=s) { - dump_val(v,"Check for scope mismatch"); - assert(!v->left); - if(v->right) dump_val(v->right,"v->right"); - } - assert(v->scope==s); - v->left = v->right = 0; - if(n){ - assert(n->scope==s); - n->left = 0; - } - /* The "special" propagating values always get a reference, - and therefore cannot be in the r0 list. */ - assert(e->values.exception != v); - assert(e->values.propagating != v); - cwal_value_unref2(e,v); - ++rc; - } - return rc; -} - -cwal_size_t cwal_engine_sweep2( cwal_engine * e, char allScopes ){ - if(!e) return 0; - else if(!allScopes){ - return cwal_scope_sweep_r0(e->current); - } - else { - cwal_scope * s = e->current; - cwal_size_t rc = 0; - for( ; s; s = s->parent ){ - rc += cwal_scope_sweep_r0(s); - } - return rc; - } -} - -cwal_size_t cwal_engine_sweep( cwal_engine * e ){ - return (e && e->current) - ? cwal_scope_sweep_r0(e->current) - : 0; -} - -cwal_size_t cwal_scope_sweep( cwal_scope * s ){ - return (s && s->e) - ? cwal_scope_sweep_r0(s) - : 0; -} - -/** - calls cwal_value_snip() to snip v from any chain and moves v to the - head of one of s->mine's members (depending on a couple of - factors). Does not modify refcount. - - Reminder to self: this is also used to move v around within - s->mine's various lists, so v->scope may be s. - - It only returns non-0 if it sets s->e->fatalCode. -*/ -static int cwal_scope_insert( cwal_scope * s, cwal_value * v ); - -/** - Removes v from its owning scope and places it in s->mine.r0, - putting it back in the probationary state. v MUST have a refcount - of 0 and CWAL_MEM_IS_BUILTIN(v) MUST be false. s MUST be the scope to - reprobate v to. -*/ -static void cwal_value_reprobate( cwal_scope * s, cwal_value * v){ - /* int rc; */ - assert(v); - assert(!CWAL_REFCOUNT(v)); - assert(!CWAL_MEM_IS_BUILTIN(v)); - assert(s); - assert(s->e); - /* dump_val(v,"Re-probating value"); */ - cwal_scope_insert(s, v); - /* dump_val(v,"Re-probated value"); */ -} - -/** - "Snips" v from its left/right neighbors. If v->scope and v is the - head of one of v->scope->mine's ownership lists then the list is - adjusted to point to v->left (if set) or v->right. Also sets - v->scope to 0. - - Returns the right-hand neighbor of v, or 0 if it has no neighbor. -*/ -static cwal_value * cwal_value_snip( cwal_value * v ); - -/** - Internal helper to move a refcount==0 value from the r0 list to one - of the "longer-lived" lists. Returns 0 on success. - - CWAL_REFCOUNT(v) must be 1 (not 0) when this is called, and must have - just gone from 0 to 1, as opposed to going from 2 to 1. -*/ -static int cwal_scope_from_r0( cwal_value * v ){ - cwal_scope * s = v->scope; - assert(1==CWAL_REFCOUNT(v)); - if(!s->mine.r0) return CWAL_RC_NOT_FOUND; - else if(1!=CWAL_REFCOUNT(v)) return CWAL_RC_RANGE - /* Only to ensure that caller ++'s it before calling this, so - that the cwal_scope_insert() call below can DTRT. */ - ; - cwal_scope_insert( s, v ) /* does the list shuffling */; - if(E_IS_DEAD(s->e)) return s->e->fatalCode; - assert(v->scope == s); - return 0; -} - -/** - Returns the number of values in s->mine's various lists. An O(N) - operation, N being the number returned (not incidentally). -*/ -static cwal_size_t cwal_scope_value_count( cwal_scope const * s ){ - cwal_size_t n = 0; - cwal_value const * v; -# define VCOUNT(WHO) v = WHO; while(v){++n; v=v->right;} (void)0 - VCOUNT(s->mine.headPod); - VCOUNT(s->mine.headObj); - VCOUNT(s->mine.headSafe); - VCOUNT(s->mine.r0); -#undef VCOUNT - return n; -} - -/** - This function frees the internal state of s but does not free s. - If any of the specially-propagating values live in s, they are - re-scoped/moved to s's parent unless s is the top-most scope. in - which case they get cleaned up. -*/ -int cwal_scope_clean( cwal_engine * e, cwal_scope * s ){ - int rc = 0; - cwal_value * v; - char iInitedGc = 0; - if(!e->gcInitiator) { - iInitedGc = 1; - e->gcInitiator = s; - CWAL_TR_S(e,s); - CWAL_TR3(e,CWAL_TRACE_MESSAGE,"Initializing gc capture."); - } - - CWAL_TR_S(e,s); - CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_START,"Scope cleanup starting."); - /* prohibits vacuum: assert(e->current != s); */ - /* Special treatment of e->values.exception and e->values.propagating. */ - { - cwal_value * vPropagate; - int phase = 1; - propagate_next: - vPropagate = (1==phase ? e->values.exception : e->values.propagating); - if(vPropagate && vPropagate->scope == s){ - cwal_scope * parent = (s==e->current /* vacuum op */) ? s->parent : e->current; - CWAL_TR_SV(e,s,vPropagate); - CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Relocating vPropagate..."); - if(!parent){ - if(1==phase) cwal_exception_set(e, 0); - else cwal_propagating_set(e, 0); - assert((1==phase) ? 0==e->values.exception : 0==e->values.propagating); - }else{ - rc = cwal_value_xscope( e, parent, vPropagate, NULL ); -#if 0 - /* we really should soldier on and clean up, but - we'd be doing so on possibly corrupt memory! - - 20200111: that said, this operation has never - failed in practice. */ - if(rc){ - assert(rc == e->fatalCode); - return e->fatalCode; - } -#endif - assert(!rc && "Cannot fail anymore?"); - assert(vPropagate && (vPropagate->scope!=s)); - CWAL_TR_SV(e,vPropagate->scope,vPropagate); - CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Moved EXCEPTION/PROPAGATING to next scope up."); - } - } - if(2==++phase) goto propagate_next; - } - - if(s->props){ - cwal_value * const pv = s->props; - cwal_obase * const obase = CWAL_VOBASE(pv); - assert(pv); - assert(obase); - assert(pv->scope && "It's not possible to have no scope at this point."); - assert((pv->scope==s || (pv->scope->levellevel)) - && "Scoping/lifetime precondition violation."); - assert((CWAL_REFCOUNT(pv)>0) && "Who stole my properties ref?"); - /* - If CWAL_REFCOUNT(pv)>1 then we know that "someone" still - holds a reference. During/after scope cleanup, using such a - reference is illegal, so we don't really care about - that. What we do care about is if... - - If we set the vacuum-safe flag on pv, we also need to turn - off the flag, but... If a client gets a handle to pv and - explicitely sets it to vacuum-safe then we can no longer - know (without a new flag) that we are the ones who set the - vacuum-proofing flag, so unsetting it here "could be bad." - One solution would be to use another flag bit in cwal_obase - to mark property storage objects as being so, and unmark - them if they are ever removed from their initial scope. It - is possible/legal that an older scope holds a reference to - pv, and in that case pv->scope!=s. So we can use that info - to determine whether this scope really owns pv or not, which - may help us do... something or other useful... - - So CWAL_F_IS_PROP_STORAGE is born... - */ - assert(obase->flags & CWAL_F_IS_PROP_STORAGE); - obase->flags &= ~CWAL_F_IS_PROP_STORAGE; - s->props = 0 /* if pv->scope==s, pv is in s->mine, - otherwise pv is in pv->scope->mine. Either way, - it's where it needs to be right now. */; - cwal_value_unref(pv); - } - - if(e->values.prototypes && (s == CWAL_VALPART(e->values.prototypes)->scope)){ - /* cwal_value_unhand(CWAL_VALPART(e->values.prototypes)); */ - e->values.prototypes = 0 /* it's in s->mine somewhere, - and will be cleaned up - momentarily. */; - } - - cwal_scope_sweep_r0( s ); - - /** - Reminder: we HAVE to clean up the containers first to ensure - that cleanup of PODs during container cleanup does not step - on dead POD refs we already cleaned. We could get around this - ordering if we included PODs in the gc queue, but we do not - need to, so we don't. (20180105: we gc-queue PODs now.) - - Algorith: keep reducing each value's refcount by 1 until it's - dead. This weeds out cycles one step at a time. - - Notes: - - For PODs we REALLY want to unref them only once here, BUT - internalized strings screw that up for us (but are too cool to - outright give up, yet i also don't want to special-case them). - - The destruction order is not QUITE what i want (i would prefer - reverse-allocation order, but we cannot guaranty that ordering - once objects move between scopes, anyway). What we're doing - here is unref'ing the head (last-added) item. If that item - still has references (it was not destroyed) then we proceed to - unref subsequent items in the list until one is destroyed. - Value destruction modifies the list we are traversing, forcing - a re-start of the traversal if any item is actually finalized - by the unref. As values are cleaned up they remove themselves - from s->mine, so we can simply walk the list until it's - empty. For "normal use" the destruction order will be the - referse of allocation, but once references are held that - doesn't... well, hold. - - Remember that this ONLY works because of our scoping rules: - - - All values in a scope when it is cleaned up must not (cannot) - reference values in higher (newer) scopes because performing - such a reference transfers the being-referenced value - (recursively for containers) into the referencing value's - owning scope. - */ - while((v = s->mine.headObj - ? s->mine.headObj - : (s->mine.headSafe - ? s->mine.headSafe - : s->mine.headPod) - )){ - CWAL_TR_SV(e,s,v); - CWAL_TR_MSG(e,"Scope is about to unref value"); - CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK); - assert(!CWAL_MEM_IS_BUILTIN(v)); - assert(v->scope); - assert(v->scope==s); - assert(v->right ? v->right->scope==s : 1); - while(v){ - cwal_value * n = v->right; - assert(n != v); - assert(!n || CWAL_REFCOUNT(n)>0); - assert(!n || n->scope == s); - if( CWAL_RC_HAS_REFERENCES == cwal_value_unref2(e, v) ) { - /* - It still has references. Let's try again. - - This is part of the reason the gc queue is so - important/helpful. Consider what happens when we - clean up a Prototype value (which may have hundreds - or thousands of references to it). We may clean it - up before some of the objects which reference it - because of this "repeat if it survives" behaviour. - */ - assert(CWAL_REFCOUNT(v)>0); - v = n; - assert((!n || s == n->scope) && "unexpected. Quite."); - continue; - /* break; */ - } - else if(n && n->scope){ - assert((s == n->scope) && "This is still true in a vacuum, right?"); - /* n is still in THIS list, else n is in the gc - queue and we need to re-start traversal. - - The destruction of v can only affect n if v is a - container. a POD cannot form a reference to anyone - else, so if we're here then we know that either: - - - v was a POD before unref'ing it. - - - OR v was a container which did not (even - indirectly) reference n. Had it cleaned up n, - n->scope would be 0. - */ - v = n; - continue; - } - else{ - /* - Need to restart traversal due to either: - - - v being finalized (which modifies this list). - - - n being in the gc queue or recycle bin (having - been put there when we unref'd v, which held the - only reference(s) (possibly indirectly) to n). - We detect this case by checking whether n has a scope. - If it has no scope, it's in the gc queue. Because - PODs don't gc-queue (they don't have to because they - cannot reference anything) all this funkiness only - applies to containers. - */ - break; - } - } - } - - assert(0 == s->mine.headPod); - assert(0 == s->mine.headObj); - assert(0 == s->mine.headSafe); - assert(0 == s->mine.r0); - CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_END,"Scope cleanup finished."); - /*MARKER("ALLOCS LIST SIZES: compound=%u simple=%u\n", s->mine.compound.count, s->mine.simple.count );*/ - - if(iInitedGc){ - assert(s == e->gcInitiator); - if(s == e->gcInitiator) { - e->gcInitiator = 0; - cwal_gc_flush( e ); - } - } - return rc; -} - -static void cwal_scope_free( cwal_engine * e, cwal_scope * s, char allowRecycle ){ - void const * stamp; - assert( e && s ); - stamp = s->allocStamp; - s->flags |= CWAL_F_IS_DESTRUCTING; - cwal_scope_clean(e, s); - *s = cwal_scope_empty; - if(CwalConsts.AllocStamp == stamp){ - /* This API allocated the scope - recycle or free it. */ - cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE); - assert(re); - if( allowRecycle && (re->count < re->maxLength) ){ - s->parent = (cwal_scope*)re->list; - re->list = s; - ++re->count; - } - else cwal_free(e, s); - }else{ - /* it was allocated elsewhere */ - s->allocStamp = stamp; - } -} - -int cwal_exception_info_clear( cwal_engine * e, cwal_exception_info * err ){ - if(!e || !err) return CWAL_RC_MISUSE; - else{ - int rc = CWAL_RC_OK; - if( err->zMsg ) cwal_free( e, err->zMsg ); - if( err->value ) rc = cwal_value_unref2( e, err->value ); - if(CWAL_RC_DESTRUCTION_RUNNING == rc) { - assert( 0 && "i don't _think_ this can happen." ); - rc = 0; - } - assert(0 == rc); - *err = cwal_exception_info_empty; - return CWAL_RC_OK; - } -} - -/** - Passes all values in the given linked value list to cwal_free(). - The values MUST have been properly cleaned up via the - cwal_unref() mechanism. -*/ -static void cwal_value_list_free( cwal_engine * e, cwal_value * list){ - cwal_value * v, * next; - for( v = list; v; v = next ){ - assert(0==v->scope); - assert(0==CWAL_REFCOUNT(v)); - next = v->right; - cwal_free(e, v); - } -} - - -/** - If s->finalize is not NULL then s->finalize(e, s->state) is called - (if finalize is not 0) and s's state is cleared, else this is a - no-op. -*/ -static void cwal_state_cleanup( cwal_engine * e, cwal_state * s ); - -static int cwal_engine_destroy_impl( cwal_engine * e, cwal_engine_vtab * vtab ){ - if(!e || !vtab) return CWAL_RC_MISUSE; - else{ - void const * stamp = e->allocStamp; - cwal_state_cleanup( e, &e->client ); - if(!e->vtab ) e->vtab = vtab /* only happens during on-init errors. */ ; - e->gcInitiator = 0; - e->flags |= CWAL_F_IS_DESTRUCTING; - CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_START); - - /* - Maintenance reminder: if we ever have an Values to clean up, - they need to be cleaned up first (right after e->client). - */ - - cwal_recycler_get(e, CWAL_TYPE_WEAK_REF)->maxLength = 0; - - e->values.exception = 0 /* its scope will clean it up */; - e->values.propagating = 0 /* its scope will clean it up */; - e->values.prototypes = 0 /* its scope will clean it up */; - while( e->current ){ - cwal_scope_pop(e); - } - - {/* Cleanup recyclers (AFTER scopes have been popped)... */ - cwal_size_t i; - cwal_kvp * kvp, * next; - cwal_recycler * re; - cwal_scope * s, * snext; - /* This "should" be a loop, but our use of mixed types - screws that up. - */ -#define RE(T) re = cwal_recycler_get(e, T); assert(re && #T) -#define UNCYCLE(T) RE(T); cwal_value_list_free(e, (cwal_value*)re->list); \ - re->list = 0; re->count = 0 - UNCYCLE(CWAL_TYPE_INTEGER); - UNCYCLE(CWAL_TYPE_DOUBLE); - UNCYCLE(CWAL_TYPE_OBJECT); - UNCYCLE(CWAL_TYPE_HASH); - UNCYCLE(CWAL_TYPE_ARRAY); - UNCYCLE(CWAL_TYPE_NATIVE); - UNCYCLE(CWAL_TYPE_BUFFER); - UNCYCLE(CWAL_TYPE_FUNCTION); - UNCYCLE(CWAL_TYPE_EXCEPTION); - UNCYCLE(CWAL_TYPE_XSTRING /* actually x/z-strings! */); - UNCYCLE(CWAL_TYPE_UNIQUE); - UNCYCLE(CWAL_TYPE_TUPLE); - /* WEAK_REF, KVP and SCOPE are handled below... */ -#undef UNCYCLE - cwal_value_list_free(e, e->reString.list); - e->reString.list = 0; - e->reString.count = 0; - - RE(CWAL_TYPE_KVP); - kvp = (cwal_kvp*) re->list; - re->list = 0; - for( ; kvp; kvp = next ){ - next = kvp->right; - assert(!kvp->key); - assert(!kvp->value); - kvp->right = NULL; - cwal_kvp_free( e/* , NULL */, kvp, 0 ); - --re->count; - } - assert(0==re->count); - - RE(CWAL_TYPE_SCOPE); - s = (cwal_scope*) re->list; - re->list = 0; - for( ; s; s = snext ){ - snext = s->parent; - s->parent = 0; - cwal_free( e, s ); - --re->count; - } - assert(0==re->count); - - { /* Clean up weak ref recycler... */ - cwal_weak_ref * wr; - cwal_weak_ref * wnext; - RE(CWAL_TYPE_WEAK_REF); - wr = (cwal_weak_ref*) re->list; - re->list = 0; - for( ; wr; wr = wnext ){ - wnext = wr->next; - assert(!wr->value); - wr->next = NULL; - cwal_free(e, wr); - --re->count; - } - assert(0==re->count); - } -#undef RE - - /* sanity-check to make sure we didn't leave any - new additions out of the e-recycler cleanup... - - This has saved me grief twice now. - */ - for( i = 0; i < (sizeof(e->recycler)/sizeof(e->recycler[0])); ++i ){ - re = &e->recycler[i]; - if(re->list){ - MARKER(("Recycler index #%d has a list? count=%d\n", (int)i, (int)re->count)); - } -#if 0 - assert(0==re->list && "Steve seems to have forgotten " - "to account for cwal_engine::recycler-related changes."); - assert(0==re->count); -#endif - } - } - - { /* Clean up any dangling cwal_weak_refs if the client - failed to do so... */ - cwal_size_t i = 0; - for( i = 0; i < sizeof(e->weakr)/sizeof(e->weakr[0]); ++i ){ - cwal_weak_ref * list = e->weakr[i]; - e->weakr[i] = NULL; - while( list ){ - cwal_weak_ref * next = list->next; - list->next = NULL; - cwal_weak_ref_free2(e, list); - list = next; - } - } - } - cwal_ptr_table_destroy(e, &e->weakp); - cwal_ptr_table_destroy( e, &e->interned ); - cwal_buffer_reserve(e, &e->buffer, 0); - cwal_gc_flush( e ); - - if(vtab->tracer.close){ - vtab->tracer.close( vtab->tracer.state ); - vtab->tracer.state = 0; - } - if(vtab->outputer.state.finalize){ - vtab->outputer.state.finalize( e, vtab->outputer.state.data ); - vtab->outputer.state.data = 0; - } - - cwal_memchunks_free(e); - cwal_error_clear(e, &e->err); - - CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_END); - *e = cwal_engine_empty; - - if( stamp == CwalConsts.AllocStamp ){ - vtab->allocator.realloc( vtab->state.data, e, 0 ); - }else{ - /* client allocated it or it was part of another object. */ - e->allocStamp = stamp; - } - - if(vtab->state.finalize){ - vtab->state.finalize( NULL, vtab->state.data ); - } - - /** - TODO: call vtab->shutdown() once that's added to the - vtab interface. - */ - - return CWAL_RC_OK; - } -} - -/** - Lazily allocates and initializes e->values.prototypes, if it is not - 0, then returns it. Returns 0 only on OOM errors during initial - allocation/intialization. -*/ -static cwal_array * cwal_engine_prototypes(cwal_engine * e){ - if(!e->values.prototypes && (e->values.prototypes = cwal_new_array(e)) ){ - if(cwal_array_reserve(e->values.prototypes, (cwal_size_t)CWAL_TYPE_end-1)){ - cwal_value_unref(CWAL_VALPART(e->values.prototypes)); - e->values.prototypes = 0; - }else{ - cwal_value_ref2(e, CWAL_VALPART(e->values.prototypes)) - /* So that it cannot get sweep()'d up. */ - ; - cwal_value_make_vacuum_proof(CWAL_VALPART(e->values.prototypes),1); - } - } - return e->values.prototypes; -} - -int cwal_engine_destroy( cwal_engine * e ){ - if( NULL == e->vtab ) return 0 /* special case: assume not yet inited */; - return e ? cwal_engine_destroy_impl(e, e->vtab) : CWAL_RC_MISUSE; -} - -int cwal_engine_init( cwal_engine ** E, cwal_engine_vtab * vtab ){ - unsigned const sz = sizeof(cwal_engine); - cwal_engine * e; - int rc; - static int once = 0; - if(!once){ - /* Just making sure some assumptions are right... */ -#if CWAL_VOID_PTR_IS_BIG - assert(sizeof(void*)>4); -#else - assert(sizeof(void*)<8); -#endif - assert(sizeof(cwal_memsize_t) <= sizeof(void*)) - /* or we'll overwrite important stuff */; - once = 1; - } - - if(!E || !vtab){ - return CWAL_RC_MISUSE; - } - if(!CWAL_BUILTIN_VALS.inited) cwal_init_builtin_values(); - assert(CWAL_BUILTIN_VALS.inited); - e = *E; - if(!e){ - e = (cwal_engine*)vtab->allocator - .realloc( vtab->allocator.state.data, NULL, sz ) - /* reminder: this does not take into account memory - over-allocation. */; - if(!e){ - return CWAL_RC_OOM; - } - } - *e = cwal_engine_empty; - if(!*E){ - e->allocStamp = CwalConsts.AllocStamp - /* we use this later to recognize that we allocated (and - need to free()) e. */; - *E = e; - } - - e->vtab = vtab; - if(CwalConsts.AutoInternStrings){ - e->flags |= CWAL_FEATURE_INTERN_STRINGS; - } - if(e->vtab->memcap.forceAllocSizeTracking - || e->vtab->memcap.maxTotalMem - || e->vtab->memcap.maxConcurrentMem){ - e->flags |= CWAL_F_TRACK_MEM_SIZE; - } - - /* Tag e->recycler[*].id, just for sanity checking and to simplify - metrics reporting. We store the virtual size of the Value type(s!) - stored in that bin. - */ - { - int i = CWAL_TYPE_UNDEF; - for( ; i < CWAL_TYPE_end; ++i ){ - cwal_recycler * re = cwal_recycler_get(e, (cwal_type_id)i); - if(re){ - re->id = (int)cwal_type_id_sizeof((cwal_type_id)i); - } - } - e->reString.id = (int)cwal_type_id_sizeof(CWAL_TYPE_STRING); - } - -#if CWAL_ENABLE_TRACE - e->trace.e = e; -#endif - { - cwal_scope * sc = &e->topScope; - rc = cwal_scope_push( e, &sc ); - if(rc) goto end; - assert(sc->e == e); - } -#if 0 - /* defer interned strings init until needed. */ - if(CWAL_FEATURE_INTERN_STRINGS & e->flags) { - rc = cwal_engine_init_interning(e); - if(rc) goto end; - } -#endif - - if(! cwal_engine_prototypes(e) ){ - rc = CWAL_RC_OOM; - }else if(vtab->hook.on_init){ - rc = vtab->hook.on_init( e, vtab ); - } - end: - return rc; -} - - -/*static int cwal_list_visit_FreeFunction( void * S, void * E ){ - cwal_engine * e = (cwal_engine *)E; - cwal_function_info * f = (cwal_function_info *)S; - cwal_state_cleanup( e, &f->state ); - cwal_string_unref( e, f->name ); - cwal_free( e, S ); - return CWAL_RC_OK; - } -*/ - -void cwal_state_cleanup( cwal_engine * e, cwal_state * s ){ - if( s ){ - if( s->finalize ) s->finalize( e, s->data ); - *s = cwal_state_empty; - } -} - -int cwal_engine_client_state_set( cwal_engine * e, - void * state, void const * typeId, - cwal_finalizer_f dtor){ - if(!e || !state) return CWAL_RC_MISUSE; - else if(e->client.data) return CWAL_RC_ACCESS; - else{ - e->client.data = state; - e->client.typeID = typeId; - e->client.finalize = dtor; - return 0; - } -} - -void * cwal_engine_client_state_get( cwal_engine * e, void const * typeId ){ - return (e && (typeId == e->client.typeID)) - ? e->client.data - : 0; -} - - -int cwal_output( cwal_engine * e, void const * src, cwal_size_t n ){ - return (e && src && n) - ? (e->vtab->outputer.output - ? e->vtab->outputer.output( e->vtab->outputer.state.data, src, n ) - : CWAL_RC_OK) - : CWAL_RC_MISUSE; -} - -int cwal_output_flush( cwal_engine * e ){ - return (e && e->vtab) - ? (e->vtab->outputer.flush - ? e->vtab->outputer.flush( e->vtab->outputer.state.data ) - : CWAL_RC_OK) - : CWAL_RC_MISUSE; -} - -static int cwal_printfv_appender_cwal_output( void * S, char const * s, - unsigned n ){ - return cwal_output( (cwal_engine *)S, s, (cwal_size_t)n ); -} - -int cwal_outputfv( cwal_engine * e, char const * fmt, va_list args ){ - if(!e || !fmt) return CWAL_RC_MISUSE; - else{ - return cwal_printfv( cwal_printfv_appender_cwal_output, e, fmt, args ); - } -} - - -int cwal_outputf( cwal_engine * e, char const * fmt, ... ){ - if(!e || !fmt) return CWAL_RC_MISUSE; - else{ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_outputfv( e, fmt, args ); - va_end(args); - return rc; - } -} - -typedef struct BufferAppender { - cwal_engine * e; - cwal_buffer * b; - int rc; -} BufferAppender; - -static int cwal_printfv_appender_buffer( void * arg, char const * data, - unsigned n ){ - BufferAppender * const ba = (BufferAppender*)arg; - cwal_buffer * const sb = ba->b; - if( !sb ) return CWAL_RC_MISUSE; - else if( ! n ) return 0; - else{ - int rc; - unsigned N; - size_t npos = sb->used + n; - if( npos >= sb->capacity ){ - const size_t asz = npos ? ((3 * npos / 2) + 1) : 32; - if( asz < npos /* overflow */ ) { - return ba->rc = CWAL_RC_RANGE; - } else { - rc = cwal_buffer_reserve( ba->e, sb, asz ); - if(rc) { - return ba->rc = rc; - } - } - } - N = 0; - for( ; N < n; ++N, ++sb->used ) sb->mem[sb->used] = data[N]; - sb->mem[sb->used] = 0; - return 0; - } -} - -int cwal_buffer_append( cwal_engine * e, - cwal_buffer * b, - void const * data, - cwal_size_t len ){ - cwal_size_t sz; - int rc; - if(!b || !data) return CWAL_RC_MISUSE; - sz = b->used + len + 1/*NUL*/; - rc = cwal_buffer_reserve( e, b, sz ); - if(rc) return rc; - memcpy( b->mem+b->used, data, len ); - b->used += len; - b->mem[b->used] = 0; - return 0; -} - -int cwal_buffer_printfv( cwal_engine * e, cwal_buffer * b, char const * fmt, va_list args){ - if(!e || !b || !fmt) return CWAL_RC_MISUSE; - else{ - BufferAppender ba; - cwal_size_t const oldUsed = b->used; - ba.b = b; - ba.e = e; - ba.rc = 0; - cwal_printfv( cwal_printfv_appender_buffer, &ba, fmt, args ); - if(ba.rc){ - b->used = oldUsed; - if(b->capacity>oldUsed){ - b->mem[oldUsed] = 0; - } - } - return ba.rc; - } -} - -int cwal_buffer_printf( cwal_engine * e, cwal_buffer * b, char const * fmt, ... ){ - if(!e || !b || !fmt) return CWAL_RC_MISUSE; - else{ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_buffer_printfv( e, b, fmt, args ); - va_end(args); - return rc; - } -} - -static int cwal_scope_alloc( cwal_engine * e, cwal_scope ** S ){ - cwal_scope * s; - assert( S && "Invalid NULL ptr."); - s = *S; - ++e->metrics.requested[CWAL_TYPE_SCOPE]; - if(!s){ - cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE); - assert(re); - if(re->count){ - s = (cwal_scope*)re->list; - assert(s); - if(s->parent){ - re->list = s->parent; - s->parent = 0; - } - else re->list = 0; - --re->count; - } - else{ - s = (cwal_scope *)cwal_malloc( e, sizeof(cwal_scope) ); - if(s){ - ++e->metrics.allocated[CWAL_TYPE_SCOPE]; - e->metrics.bytes[CWAL_TYPE_SCOPE] += sizeof(cwal_scope); - } - } - } - if(s){ - *s = cwal_scope_empty; - s->e = e; - if(*S != s) { - s->allocStamp = CwalConsts.AllocStamp - /* we use this later to know whether or not we need to - free() s. */; - /* Potential TODO: use e as the allocStamp for any - resources e allocates. */ - *S = s; - } - } - return s ? CWAL_RC_OK : CWAL_RC_OOM; -} - -/** - Pops the e->current scope from the scope stack, cleaning it up and - possibly cwal_free()ing it. If callHook is true then the - cwal_engine_vtab::hook::scope_pop hook, if not NULL, will be called - before the scope state is changed. The only error conditions are - invalid arguments: - - !e = CWAL_RC_MISUSE - - !e->current = CWAL_RC_RANGE -*/ -static int cwal_scope_pop_impl( cwal_engine * e, char callHook ){ - if(!e) return CWAL_RC_MISUSE; - else if( 0 == e->current ) return CWAL_RC_RANGE; - else{ - cwal_scope * p; - cwal_scope * s = e->current; - assert(s->e == e); - if(callHook && e->vtab->hook.scope_pop){ - e->vtab->hook.scope_pop( s, e->vtab->hook.scope_state ); - } - p = s->parent; - assert(p || (e->top==s)); - e->current = p; - if(e->top==s) e->top = 0; - cwal_scope_free( e, s, 1 ); - return 0; - } -} - -int cwal_scope_pop( cwal_engine * e ){ - return cwal_scope_pop_impl(e, 1); -} - -int cwal_scope_push( cwal_engine * e, cwal_scope ** S ){ - cwal_scope * s = S ? *S : NULL; - int rc; - if(!e) return CWAL_RC_MISUSE; - rc = cwal_scope_alloc(e, &s); - if(rc){ - assert(NULL == s); - return rc; - } - assert(NULL != s); - s->level = e->current ? (1 + e->current->level) : 1; - s->parent = e->current; - if(!e->top){ - assert(NULL == e->current); - e->top = s; - } - e->current = s; - if(e->vtab->hook.scope_push){ - rc = e->vtab->hook.scope_push( s, e->vtab->hook.scope_state ); - if(rc) cwal_scope_pop_impl(e, 0); - } - if(!rc && S) *S = s; - return rc; -} - -int cwal_scope_push2( cwal_engine * e, cwal_scope * s ){ - return (!e || !s || memcmp(s, &cwal_scope_empty, sizeof(cwal_scope))) - ? CWAL_RC_MISUSE - : cwal_scope_push( e, &s ); -} - -int cwal_scope_pop2( cwal_engine * e, cwal_value * resultVal ){ - if(!e) return CWAL_RC_MISUSE; - else if(!e->current - || (resultVal && !e->current->parent)) return CWAL_RC_RANGE; - else{ - int rc = 0; - if(resultVal){ - cwal_value_ref(resultVal); - cwal_value_rescope(e->current->parent, resultVal); - } - rc = cwal_scope_pop_impl(e, 1); - if(resultVal){ - cwal_value_unhand(resultVal); - } - return rc; - } -} - -cwal_value * cwal_value_snip( cwal_value * v ){ - cwal_scope * p = v->scope; - cwal_value * l = v->left; - cwal_value * r = v->right; - v->scope = 0; - v->right = v->left = 0; - assert(r != v); - assert(l != v); - if(l) l->right = r; - if(r) r->left = l; - if(p){ - /* Adjust value lifetime lists if v is the head of one of them. - If it is not the head, then removal from its linked list - is sufficient. - */ - l = l ? l : r; - if(l){ - while(l->left){ - if(l == l->left){ - cwal_dump_value( "cwal.c", __LINE__, l, "Internal cwal_value list misuse."); - assert(l->left != l && "Internal cwal_value list misuse."); - E_IS_DEAD(p->e) = CWAL_RC_ASSERT; - return NULL; - /* abort(); */ - } - l=l->left; - } - } - if(p->mine.headObj==v){ - p->mine.headObj = l; - CWAL_TR_SV(p->e,p,l); - CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headObj."); - if(p->mine.headObj) assert(0==p->mine.headObj->left); - }else if(p->mine.headPod==v){ - p->mine.headPod = l; - CWAL_TR_SV(p->e,p,l); - CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headPod."); - if(p->mine.headPod) assert(0==p->mine.headPod->left); - }else if(p->mine.headSafe==v){ - p->mine.headSafe = l; - CWAL_TR_SV(p->e,p,l); - CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headSafe."); - if(p->mine.headSafe) assert(0==p->mine.headSafe->left); - }else if(p->mine.r0==v){ - p->mine.r0 = l; - CWAL_TR_SV(p->e,p,l); - CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->r0."); - if(p->mine.r0) assert(0==p->mine.r0->left); - } - } - return r; -} - -/** - Inserts v so that v->right is now l, adjusting v and l as necessary. - v->right and v->left must be 0 before calling this (it assert()s so). -*/ -static void cwal_value_insert_before( cwal_value * l, cwal_value * v ){ - assert(0 == v->right); - assert(0 == v->left); - assert((l != v) && "Unexpected duplicate items for value list. " - "Possibly caused by an unwarranted unref."); - /* if(l != v){ */ - if( l->left ){ - l->left->right = v; - } - l->left = v; - v->right = l; - /* } */ -} - -cwal_value * cwal_string_from_recycler( cwal_engine * e, cwal_size_t len ){ - cwal_value * li = (cwal_value *)e->reString.list; - cwal_string * s; - cwal_value * prev = 0; - cwal_size_t slen; - cwal_size_t paddedLen; - for( ; li; prev = li, li = li->right ){ - s = CWAL_STR(li) /*cwal_value_get_string(li)*/; - assert(s); - assert(0 < CWAL_STRLEN(s)); - assert(!CWAL_STR_ISXZ(s)); - slen = CWAL_STRLEN(s); - if(!CwalConsts.StringPadSize){ - if(len!=slen) continue; - /* Else fall through */ - }else if(len!=slen){ - /** - If s's "padded length" is large enough for the request, - but not "too large" (within 1 increment of - CwalConsts.StringPadSize) then we will re-use it. - */ - cwal_size_t const mod = (slen % CwalConsts.StringPadSize); - paddedLen = mod - ? (slen + (CwalConsts.StringPadSize - mod)) - : slen; - if(paddedLen < len) continue; - else if(paddedLen > len){ - if((paddedLen - CwalConsts.StringPadSize) > len) continue; - } - } - if(prev){ - prev->right = li->right; - } - else { - assert(e->reString.list == li); - e->reString.list = li->right; - } - li->right = 0; - --e->reString.count; - if(CwalConsts.StringPadSize){ - s->length = CWAL_STRLEN_MASK & len; - } - /* MARKER("Pulling string of len %u from recycle bin.\n", (unsigned)len); */ - CWAL_TR_V(e,li); - CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER, - "Pulled string from recycle bin."); - ++e->metrics.valuesRecycled; - ++e->reString.hits; - return li; - } - ++e->reString.misses; - ++e->metrics.valuesRecycleMisses; -#if 0 - /* this is causing a valgrind warning via memset() via cwal_value_new() */ - if(e->reChunk.head - && !e->reString.list /* see comments below */){ - /* Look in the chunk recycler as a last resort. Testing with - the s2 amalgamation shows that this very rarely hits if - recycling is on (only once in the whole test suite from - 20141201). If recycling is off, it hits surprisingly often - (1224 times in the test suite). So we only do this O(N) - lookup when string recycling is off or its recycling bin is - empty. - - If both value recyling and string interning are off, this - block hits 3798 times in the above-mentioned test suite. - */ - cwal_size_t reqSize = sizeof(cwal_value) - + sizeof(cwal_string) - + len + 1 /*NUL*/; - cwal_value * v = (cwal_value *) - cwal_memchunk_request(e, &reqSize, - len up to 1.5x */ - : 125 /* ==> up to 1.25x. Wasting - memory? They should be glad - we're recycling it at - all! */, - "cwal_string_from_recycler()"); - if(v){ - /* MARKER(("Got string fallback!\n")); */ - *v = cwal_value_string_empty; - s = CWAL_STR(v); - assert(!(~CWAL_STRLEN_MASK & len)); - s->length = (cwal_size_t)(CWAL_STRLEN_MASK & len); - return v; - } - } -#endif - return NULL; -} - - -static bool cwal_string_recycle( cwal_engine * e, cwal_value * v ){ - cwal_string * s = cwal_value_get_string(v); - cwal_size_t const slen = CWAL_STRLEN(s); - char const * freeMsg = 0; - cwal_value * li; - assert(s); - if( slen > CwalConsts.MaxRecycledStringLen ){ - freeMsg = "String too long to recycle - freeing."; - goto freeit; - } - else if(CWAL_STR_ISXZ(s)){ - assert(!"Cannot happe anymore - x/z-string recycled elsewhere/elsehow."); - freeMsg = "Cannot recycle x/z-strings."; - goto freeit; - } - else if( 0 == e->reString.maxLength ){ - freeMsg = "String recycling disabled - freeing."; - goto freeit; - }else if(freeMsg){ - /* avoiding an unused var in non-debug build */ - assert(!"impossible"); - } - li = (cwal_value *)e->reString.list; - if(e->reString.count>=e->reString.maxLength){ - /* - Remove the oldest entries from the list. - They have not been recycled in a while, - and are not likely needed any more. - - To avoid unduly high N on the O(N) linked list traversal, - when trimming the list we trim some percentage of it, as - opposed to only the last (oldest) element. Otherwise this - algo is remarkably slow on large recycle lists or when - rapidly freeing many strings. - - TODO?: sort the strings by size(?) in order to optimize the - from-recycle-bin size lookup? - */ - cwal_size_t keep = e->reString.maxLength * 3 / 4; - cwal_size_t i; - cwal_value * x = li; - cwal_value * prev = 0; - cwal_value * cut = 0; - for( i = 0; x; - prev = x, x = x->right, ++i ){ - if(i==keep){ - assert(!x->left); - cut = x; - break; - } - } - if(prev) prev->right = 0; - else e->reString.list = 0; - -#if 0 - MARKER("Trimming list to %u of %u entries cut=%c.\n", - (unsigned)i, (unsigned) e->reString.maxLength, cut?'y':'n'); -#endif - - if(cut){ -#if 0 - e->reString.count = i; - CWAL_TR_V(e,x); - CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, - "Popping stale string(s) from recycler."); - cwal_value_list_free(e, cut); -#else - /* We "could" just use cwal_value_list_free(), - but i want more tracing info. */ - for( x = cut; x ; x = cut ){ - cut = x->right; - x->right = 0; - --e->reString.count; - CWAL_TR_V(e,x); - CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, - "Popping stale string from recycler."); - cwal_free(e,x); - } -#endif - } - assert(e->reString.count < e->reString.maxLength); - } - - /* MARKER("String to recyler...\n"); */ - assert(!v->right); - assert(!v->scope); - assert(!CWAL_REFCOUNT(v)); - assert(CWAL_TYPE_STRING==v->vtab->typeID); - li = (cwal_value*)e->reString.list; - assert(!li || (CWAL_TYPE_STRING==li->vtab->typeID)); - v->right = li; - e->reString.list = v; - ++e->reString.count; - CWAL_TR_V(e,v); - CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER, - "Placed string in recycling bin."); - return 1; - freeit: -#if !CWAL_ENABLE_TRACE - if(0){ assert(freeMsg && "Avoid unused var warning in non-trace builds."); } -#endif - CWAL_TR_SV(e,e->current,v); - CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, freeMsg); -#if 1 - { - /** - Pushing this memory to the chunk recycler can lower both allocs - and peak marginally, but can also increase peak while lowering - allocs, depending on the usage. Compare s2's UNIT.s2 vs - UNIT-import.s2. They both run the same tests, but the former - loads them as a single script and the latter imports one script - at a time. - */ - cwal_size_t sz = sizeof(cwal_value)+sizeof(cwal_string) + slen + 1 /*NUL byte*/; - /* Account for string size padding */ - if(CwalConsts.StringPadSize){ - if(slenCwalConsts.StringPadSize){ - cwal_size_t const mod = (slen % CwalConsts.StringPadSize); - if(mod) sz = sz + (CwalConsts.StringPadSize - mod); - } - } - cwal_memchunk_add(e, v, sz); - } -#else - cwal_free( e, v ); -#endif - return 0; - -} - -int cwal_value_recycle( cwal_engine * e, cwal_value * v ){ - int ndx; - cwal_recycler * re; - cwal_size_t max; -#if CWAL_ENABLE_TRACE - char const * freeMsg = "==> CANNOT RECYCLE. FREEING."; -# define MSG(X) freeMsg = X -#else -# define MSG(X) -#endif - assert(e && v && v->vtab); - assert(0==CWAL_REFCOUNT(v)); - if(e->gcInitiator - /*&& (CWAL_V_IS_OBASE(v) - || (CWAL_TYPE_UNIQUE==v->vtab->typeID) - || (CWAL_TYPE_TUPLE==v->vtab->typeID) - //^^^^ We also need to put "not-quite-containers" here, - // namely CWAL_TYPE_UNIQUE and any potentially similar - // ones (CWAL_TYPE_TUPLE). - )*/){ - /** - 20180105: historically we only put certain types in the GC - queue, but we came across a situation where a Unique value - wrapping a string choked at destruction because its wrapped - string had already been sent to the string recycler - (strings, at the time, did not get gc-queued) - */ - /** - This means a scope cleanup is running and deallocation must - be delayed until the cleanup is finished. Move the value - into the GC queue. We hypothetically only need this for - types which can participate in cycles, as it's easy to step - on a destroyed value via a cycle during value - destruction. Practice has (finally, 20180105) shown that - certain non-cyclic relationships require that their values - (in particular code constellations) must also be gc-queued. - */ - CWAL_TR_V(e,v); - CWAL_TR3(e,CWAL_TRACE_MEM_TO_GC_QUEUE, - "Redirecting value to gc queue"); - ndx = cwal_gc_push( e, v ); - assert(0==ndx && "cwal_gc_push() can (now) only fail if !e->gcInitiator."); - return (0==ndx) - ? -1 - : 0; - } - else if(CWAL_TYPE_STRING == v->vtab->typeID - && !CWAL_STR_ISXZ(CWAL_STR(v))){ - return cwal_string_recycle( e, v ); - } - - assert( 0 == CWAL_REFCOUNT(v) ); - assert( 0 == v->right ); - assert( 0 == v->left ); - assert( 0 == v->scope ); - ndx = cwal_recycler_index( v->vtab->typeID ); - if( ndx < 0 ) { - /* Non-recylable type */ - MSG("==> Unrecyclable type. FREEING."); - goto freeit; - } - re = &e->recycler[ndx]; - max = re->maxLength; - if(!max) { - /* recycling disabled */ - MSG("==> Recyling disabled for this type. FREEING."); - goto freeit; - } - else if( re->count >= max ){ - /* bin is full */ - MSG("==> Recyling bin for this type is full. FREEING."); - goto freeit; - } - - assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)); - CWAL_RCFLAG_ON(v, CWAL_RCF_IS_RECYCLED); - assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)); - if(re->list){ - cwal_value_insert_before( re->list, v ); - } - ++re->count; - re->list = v; - if(re->count > 1){ - assert(((cwal_value*)re->list)->right); - } - CWAL_TR_V(e,v); - CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER, - "Placed in recycling bin."); - return 1; - freeit: - CWAL_TR_V(e,v); - CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,freeMsg); - *v = cwal_value_undef; - cwal_free(e, v); - return 0; -#undef MSG -} - - -int cwal_gc_push( cwal_engine * e, cwal_value * v ){ - assert( e->gcInitiator ); - assert(0 == v->scope); - assert(0 == v->right); - if(!e->gcInitiator) return CWAL_RC_MISUSE; - if(e->values.gcList){ - cwal_value_insert_before( e->values.gcList, v ); - } - assert(!CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED)); - CWAL_RCFLAG_ON(v,CWAL_RCF_IS_GC_QUEUED); - assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED)); - e->values.gcList = v; - assert(0==e->values.gcList->left); - return CWAL_RC_OK; -} - -int cwal_gc_flush( cwal_engine * e ){ - int rc = 0; - cwal_value * v; - cwal_value * n; - assert( 0 == e->gcInitiator - && "Otherwise we might have a loop b/t this and cwal_value_recycle()"); - for( v = e->values.gcList; v; v = n ){ - n = v->right; - cwal_value_snip(v); - assert(!CWAL_REFCOUNT(v)); - assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED)); - CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_GC_QUEUED); - assert(!CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED)); - /* if( (base = CWAL_VOBASE(v)) ) base->flags &= ~CWAL_F_IS_GC_QUEUED; */ - /* dump_val(v,"refcount?"); */ - assert(!CWAL_REFCOUNT(v)); - rc = cwal_value_recycle(e, v); - assert(-1!=rc && "Impossible loop!"); - } - e->values.gcList = 0; - return rc; -} - -int cwal_scope_insert( cwal_scope * s, cwal_value * v ){ - /* cwal_scope * p = v->scope; */ - cwal_value * list; - cwal_value ** listpp = 0; - cwal_obase * const b = CWAL_VOBASE(v); - assert(s && v); - assert(!CWAL_MEM_IS_BUILTIN(v)); - /* - Reminder to self: v->scope == s when we're moving items - around from s->mine.{r0,headSafe}. - */ -#if 0 - /* 20191211: The intent: if we are moving a scope's properties - object into an older new scope, remove the is-prop-storage - flag, as it's now hypothetically moved beyond that role. It's - not yet certain that that's always correct, though, nor exactly - what the ramifications of doing this would be. - */ - if(b && (CWAL_F_IS_PROP_STORAGE & b->flags) && v->scope != s){ - b->flags &= ~CWAL_F_IS_PROP_STORAGE; - } -#endif - cwal_value_snip( v ); - assert(0==v->right); - assert(0==v->left); - assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)); - if(0==CWAL_REFCOUNT(v)){ - listpp = &s->mine.r0; - }else if(CWAL_V_IS_VACUUM_SAFE(v) - || (b && (b->flags & CWAL_F_IS_PROP_STORAGE))){ - listpp = &s->mine.headSafe; - }else if(CWAL_V_GOES_IN_HEADOBJ(v)){ - /* 20180105 reminder: this doesn't include Uniques and Tuples. - TODO?: add those types to headObj once we're certain that - doing so won't cause other Grief. We could undo the - special-case destruction checks in - cwal_value_cleanup_unique() and cwal_value_cleanup_tuple() - if we do that, and revert the GC queue to only holding - containers (plus Unique and Tuple). Initial tests in that - regard didn't alleviate them (or Unique, at least) needing - their extra cleanup checks :/. - */ - listpp = &s->mine.headObj; - }else { - listpp = &s->mine.headPod; - } - list = *listpp; - if(list == v){ - cwal_dump_value( __FILE__, __LINE__, v, - "FATAL: inserting item we already have! " - "String interning backfire?"); - assert(list != v && "Insertion failed: this item is " - "the head of one of the lists! String interning backfire?"); - E_IS_DEAD(s->e) = CWAL_RC_ASSERT; -#if 1 - return s->e->fatalCode; -#else - abort(/* this is the only solution, as this error is indicative - or memory corruption within cwal and we cannot report - it to the user from this level. */); -#endif - } - else if(list) { - v->right = list; - assert(0==list->left); - list->left = v; - } - *listpp = v; - v->scope = s; - assert(0==v->left); - CWAL_TR_SV(s->e,s,v); - CWAL_TR3(s->e,CWAL_TRACE_VALUE_SCOPED,"Value moved to scope."); - return 0; -} - - -int cwal_value_take( cwal_engine * e, cwal_value * v ){ - cwal_scope * s = v ? v->scope : 0; - if(!e || !v) return CWAL_RC_MISUSE; - else if( CWAL_MEM_IS_BUILTIN( v ) ) return CWAL_RC_OK; - else if(!s) return CWAL_RC_RANGE; - else{ - cwal_value_snip( v ); - CWAL_TR_SV(e,s,v); - CWAL_TR3(e,CWAL_TRACE_VALUE_UNSCOPED,"Removed from parent scope."); - assert(!v->scope); - assert(0==v->right); - assert(0==v->left); - assert(s->mine.headObj != v); - assert(s->mine.headPod != v); - assert(s->mine.headSafe != v); - assert(s->mine.r0 != v); - return CWAL_RC_OK; - } -} - -int cwal_value_unref(cwal_value *v ){ - if(!v) return CWAL_RC_MISUSE; - else if( CWAL_MEM_IS_BUILTIN( v ) ) return CWAL_RC_OK; - else if(!v->scope){ - if(CWAL_V_IS_IN_CLEANUP(v)) return CWAL_RC_DESTRUCTION_RUNNING; - assert(!"Cannot unref a Value with no scope: serious misuse or Value corruption."); - return CWAL_RC_MISUSE; - } - else return cwal_value_unref2(v->scope->e, v); -} - -int cwal_value_unref2(cwal_engine * e, cwal_value *v ){ - assert( e && v ); - if(NULL == e || NULL == v) return CWAL_RC_MISUSE; - CWAL_TR_SV(e,v->scope,v); - if(CWAL_MEM_IS_BUILTIN(v)) return 0; - else { - cwal_obase * b; - b = CWAL_VOBASE(v); - CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"Unref'ing"); - if(!CWAL_REFCOUNT(v) || !CWAL_RCDECR(v)){ - cwal_scope * const vScope = v->scope; - CWAL_TR_V(e,v); - if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_GC_QUEUED)){ - assert(!v->scope); - CWAL_TR_S(e,vScope); - CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, - "DESTRUCTION OF A GC-QUEUED OBJECT: SKIPPING"); - /* Possible again since starting refcount at 0: - assert(!"This is no longer possible."); */ - return CWAL_RC_DESTRUCTION_RUNNING; - } - else if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)){ - assert(!v->scope); - CWAL_TR_S(e,vScope); - CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, - "DESTRUCTION OF A RECYCLED OBJECT: SKIPPING"); - /* Possible again since starting refcount at 0: - assert(!"This is no longer possible."); */ - return CWAL_RC_DESTRUCTION_RUNNING; - } - else if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)) { - CWAL_TR_S(e,vScope); - CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, - "ENTERING DESTRUCTION A 2ND TIME (or more): SKIPPING"); - /* Possible again since starting refcount at 0: - assert(!"This is no longer possible."); */ - return CWAL_RC_DESTRUCTION_RUNNING; - } - CWAL_TR_S(e,vScope); - CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_START, - "Starting finalization..."); - - if(e->values.exception == v){ - e->values.exception = 0; - } - if(e->values.propagating == v){ - e->values.propagating = 0; - } - - if(!v->scope){ - dump_val(v,"Value with NULL scope???"); - assert(v->scope && "this is always true now. " - "That was not always the case."); - return e->fatalCode = CWAL_RC_ASSERT; - } - cwal_value_take(e, v) - /*ignoring rc! take() cannot fail any more under these - conditions.*/; - if(b && (CWAL_F_IS_PROP_STORAGE & b->flags)){ - /* reminder to self: it's potentially possible for - clients to move this value into a place where - it's used for purposes other than property - storage, via cwal_scope_properties(). - */ - cwal_scope * sc = e->current; - assert(CWAL_TYPE_OBJECT==v->vtab->typeID - || CWAL_TYPE_HASH==v->vtab->typeID); - /* - Special case: if v is the cwal_scope::props handle - of a scope, we need to clear that to make sure it - doesn't get stale. This can hypothetically happen if - client code exposes cwal_scope_properties() to - script-space, causes it to get upscoped (for - ownership purposes) but still being pointed to by - cwal_scope::props, and then unrefs it. If that - happens, though... hmmm... the scope storage is - always treated as vacuum-safe, which means that they - could become impossible to vacuum up if clients - introduced cycles and then abandoned all - script-visible references. Oh, well. They shouldn't - be exposing these to script code, probably. - */ - for( ; sc ; sc = sc->parent ){ - if(sc->props == v){ - sc->props = 0; - break; - } - } - } - /* The left/right assertions help ensure we're not now - traversing through the recycle list, which is an easy thing - to do when memory has been mismanaged. - */ - if(v->left || v->right){ - /* - This is case checked above in a different manner. - If this check fails, then memory corruption is - what's going on and we need to stop that... the only - way we reasonably can. Alternately, we may at some - point add a flag to the engine telling it it's in an - unrecoverable/unusable state. That would require - adding that check to a great many routines, though. - */ - dump_val(v,"Item from recycler???"); - if(v->left) dump_val(v->left,"v->left"); - if(v->right) dump_val(v->right,"v->right"); - assert(!"Trying to clean up item from recycler(?)!"); - return e->fatalCode = CWAL_RC_ASSERT; - /* abort(); */ - } - - assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)); - CWAL_RCFLAG_ON(v, CWAL_RCF_IS_DESTRUCTING); - assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)); - assert(0 == v->right); - assert(0 == v->left); - - cwal_weak_unregister( e, v, v->vtab->typeID ); - v->scope = vScope - /* Workaround to help support the ancient behaviour - which may or may not still be relevant. - */; - v->vtab->cleanup(e, v); - v->scope = 0 /* END KLUDGE! */; - assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)); - CWAL_RCFLAG_OFF(v, CWAL_RCF_IS_DESTRUCTING); - assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)); - CWAL_TR_V(e,v); - CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_END, - "Cleanup complete. Sending to recycler..."); - assert(0 == CWAL_REFCOUNT(v)); - cwal_value_recycle(e, v); - return CWAL_RC_FINALIZED; - } - else{ - CWAL_TR_V(e,v); - CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT, - "It continues to live"); - return CWAL_RC_HAS_REFERENCES; - } - } -} - -cwal_value * cwal_value_unhand( cwal_value * v ){ - if(!v || CWAL_MEM_IS_BUILTIN(v)) return v; - else if(!v->scope){ - /* should we do this check, like we do in cwal_value_unref()? - if(CWAL_V_IS_IN_CLEANUP(v)) return NULL; - */ - assert(!"Cannot unhand a Value with no scope: serious misuse or Value corruption."); - return NULL; - } - else if(CWAL_REFCOUNT(v) && 0==CWAL_RCDECR(v)){ - cwal_value_reprobate(v->scope, v); - } - return v; -} - -/** - Increments cv's reference count by 1. Asserts that !CWAL_MEM_IS_BUILTIN(v) - and (NULL!=cv). Code which might be dealing with those value should - call the public-API-equivalent of this, cwal_value_ref(). -*/ -static int cwal_refcount_incr( cwal_engine *e, cwal_value * cv ) -{ - assert( NULL != cv ); - /* assert(!CWAL_MEM_IS_BUILTIN(cv)); */ - assert(cv->scope) /* also catches builtins */; - if( CWAL_RCFLAG_MAXRC <= CWAL_REFCOUNT(cv) ){ - /* Overflow! */ - cwal_dump_value( __FILE__, __LINE__, cv, - "FATAL: refcount overflow! How?!?"); - assert(!"Refcount overflow! Undefined behaviour!"); - return e->fatalCode = CWAL_RC_RANGE; - } - else if(1 == CWAL_RCINCR(cv)){ - if(cwal_scope_from_r0(cv)){ - assert(!"If this is failing then internal " - "preconditions/assumptions have not been met."); -#ifdef DEBUG - abort(); -#endif - return e->fatalCode = CWAL_RC_ASSERT; - } - } - if(CWAL_REFCOUNT(cv) > e->metrics.highestRefcount){ - e->metrics.highestRefcount = CWAL_REFCOUNT(cv); - e->metrics.highestRefcountType = cv->vtab->typeID; - } - CWAL_TR_V(e,cv); - CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"++Refcount"); - return 0; -} - -int cwal_value_ref2( cwal_engine *e, cwal_value * cv ){ - assert(e && cv); - if( !e || !cv ) return CWAL_RC_MISUSE; - else if( CWAL_MEM_IS_BUILTIN(cv) ) return CWAL_RC_OK; - else if( !cv->scope ){ - assert(!"Apparent attempt to ref() an " - "invalid (cleaned up?) value."); - return CWAL_RC_MISUSE; - } - else return CWAL_RCFLAG_MAXRC <= CWAL_REFCOUNT(cv) - ? CWAL_RC_RANGE - : cwal_refcount_incr( e, cv ); -} - -int cwal_value_ref( cwal_value * cv ){ - if( NULL == cv ) return CWAL_RC_MISUSE; - else if( CWAL_MEM_IS_BUILTIN(cv) ) return CWAL_RC_OK; - else if( !cv->scope ){ - assert(!"Apparent attempt to ref() an " - "invalid (cleaned up?) value."); - return CWAL_RC_MISUSE; - } - else { - assert( cv->scope->e ); - return cwal_refcount_incr( cv->scope->e, cv ); - } -} - -#if 0 -int cwal_ref(cwal_value *v){ return cwal_value_ref(v); } -int cwal_unref(cwal_value *v){ return cwal_value_unref(v); } -cwal_value * cwal_unhand( cwal_value * v ){ - return cwal_value_unhand(v); -} -#endif - -bool cwal_refunref( cwal_value * v ){ - char rc = 0; - if(v && v->scope && !CWAL_REFCOUNT(v)){ - /* This is a temp. Or, it turns out, it's an interned string - which is used in 2+ places and our nuking it has disastrous - side-effects. So... there is no obvious internal workaround - because string interning does not count how many instances - are interned (which we could use to "fudge" the refcount - check). So we're going to call that a usage error and leave - it at that. i'm suddenly very glad i resisted, at the time, - the urge to intern integers/doubles as well. - - One potential workaround, but not a good one: - - if v is-a string AND (v->scope->level < - v->scope->e->current->level) then don't nuke it. - - That would only hide the problem some (most?) of the time, - though. - */ - cwal_value_unref(v); - rc = 1; - } - return rc; -} - -cwal_refcount_t cwal_value_refcount( cwal_value const * v ){ - return v ? CWAL_REFCOUNT(v) : 0; -} - - -int cwal_scope_current( cwal_engine * e, cwal_scope ** s ){ - if(!e || !s) return CWAL_RC_MISUSE; - else if(!e->current) return CWAL_RC_RANGE; - else{ - *s = e->current; - return CWAL_RC_OK; - } -} - -cwal_scope * cwal_scope_current_get( cwal_engine * e ){ - return e ? e->current : 0; -} - - - -cwal_size_t cwal_type_id_sizeof( cwal_type_id id ){ - switch(id){ - case CWAL_TYPE_BOOL: - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - return 0; - case CWAL_TYPE_STRING: return sizeof(cwal_value)+sizeof(cwal_string); - case CWAL_TYPE_UNIQUE: return sizeof(cwal_value)+sizeof(cwal_value*); - case CWAL_TYPE_TUPLE: return sizeof(cwal_value)+sizeof(cwal_tuple); - case CWAL_TYPE_INTEGER: return sizeof(cwal_value)+sizeof(cwal_int_t); - case CWAL_TYPE_DOUBLE: return sizeof(cwal_value)+sizeof(cwal_double_t); - case CWAL_TYPE_ARRAY: return sizeof(cwal_value)+sizeof(cwal_array); - case CWAL_TYPE_OBJECT: return sizeof(cwal_value)+sizeof(cwal_object); - case CWAL_TYPE_NATIVE: return sizeof(cwal_value)+sizeof(cwal_native); - case CWAL_TYPE_BUFFER: return sizeof(cwal_value)+sizeof(cwal_buffer_obj); - case CWAL_TYPE_FUNCTION: return sizeof(cwal_value)+sizeof(cwal_function); - case CWAL_TYPE_EXCEPTION: return sizeof(cwal_value)+sizeof(cwal_exception); - case CWAL_TYPE_HASH: return sizeof(cwal_value)+sizeof(cwal_hash); - case CWAL_TYPE_SCOPE: return sizeof(cwal_scope); - case CWAL_TYPE_KVP: return sizeof(cwal_kvp); - case CWAL_TYPE_WEAK_REF: return sizeof(cwal_weak_ref); - case CWAL_TYPE_XSTRING: - case CWAL_TYPE_ZSTRING: - return sizeof(cwal_value) - +sizeof(cwal_string) - + sizeof(char **); - case CWAL_TYPE_LISTMEM: return 0; - default: - return 0; - } -} - -/** - Allocates a new value of the specified type. The value is pushed - onto e->current, effectively transfering ownership to that scope. - - extra is a number of extra bytes to allocate after the "concrete - type part" of the allocation. It is only valid for type - CWAL_TYPE_STRING, and must be the length of the string to allocate - (NOT included the terminating NUL byte - this function adds that - byte itself). - - The returned value->vtab member will be set appropriately and. Use - the internal CWAL_VVPCAST() family of macros to convert the - cwal_values to their corresponding native representation. - - Returns NULL on allocation error or if adding the new value - to s fails. - - @see cwal_new_array() - @see cwal_new_object() - @see cwal_new_string() - @see cwal_new_integer() - @see cwal_new_double() - @see cwal_new_function() - @see cwal_new_native() - @see cwal_new_buffer() - @see cwal_new_hash() - @see cwal_new_unique() - @see cwal_new_tuple() - @see cwal_value_unref() -*/ -static cwal_value * cwal_value_new(cwal_engine * e, - cwal_scope * s, - cwal_type_id t, cwal_size_t extra){ - enum { vsz = sizeof(cwal_value) }; - const cwal_size_t sz = vsz + extra /* base amount of memory to allocate */; - cwal_size_t tx = 0 /* number of extra bytes to allocate for the - concrete value type */; - cwal_value def = cwal_value_undef /* prototype to stamp over the - newly-allocated value */; - cwal_value * v = NULL; - switch(t) - { - case CWAL_TYPE_DOUBLE: - assert( 0 == extra ); - def = cwal_value_double_empty; - tx = sizeof(cwal_double_t); - break; - case CWAL_TYPE_INTEGER: - assert( 0 == extra ); - def = cwal_value_integer_empty; - /* We might want to consider using double for integers on - 32-bit platforms, which would allow us to support - larger-precision integers in the public interface by - hiding the doubles behind them. With that we could - effectively declare cwal_int_t as guarantying, say 48 - bits of integer precision. But would break if we add - disable-doubles support (which i would like to eventually - add as an option). - */ - tx = sizeof(cwal_int_t); - break; - case CWAL_TYPE_STRING:{ - assert( (extra > 0) && "empty strings are handled elsewhere" ); - def = cwal_value_string_empty; - tx = sizeof(cwal_string) + 1 /*NUL byte*/; - if(CwalConsts.StringPadSize){ - int const pad = extra % CwalConsts.StringPadSize; - if(pad) tx += CwalConsts.StringPadSize - pad; - } - break; - } - case CWAL_TYPE_XSTRING: - case CWAL_TYPE_ZSTRING: - assert( !extra && "x/z-string length is handled elsewhere" ); - def = cwal_value_string_empty; - tx = sizeof(cwal_string) + sizeof(unsigned char **) - /* x/z-strings are stored like - (cwa_value+cwal_string+(cstring-ptr)), and we stuff - the external string pointer in the cstring-ptr part. - */; - break; - case CWAL_TYPE_ARRAY: - assert( 0 == extra ); - def = cwal_value_array_empty; - tx = sizeof(cwal_array); - break; - case CWAL_TYPE_OBJECT: - assert( 0 == extra ); - def = cwal_value_object_empty; - tx = sizeof(cwal_object); - break; - case CWAL_TYPE_FUNCTION: - assert( 0 == extra ); - def = cwal_value_function_empty; - tx = sizeof(cwal_function); - break; - case CWAL_TYPE_NATIVE: - assert( 0 == extra ); - def = cwal_value_native_empty; - tx = sizeof(cwal_native); - break; - case CWAL_TYPE_EXCEPTION: - assert( 0 == extra ); - def = cwal_value_exception_empty; - tx = sizeof(cwal_exception); - break; - case CWAL_TYPE_BUFFER: - assert( 0 == extra ); - def = cwal_value_buffer_empty; - tx = sizeof(cwal_buffer_obj); - break; - case CWAL_TYPE_HASH: - assert( 0 == extra ); - def = cwal_value_hash_empty; - tx = sizeof(cwal_hash); - break; - case CWAL_TYPE_UNIQUE: - assert( 0 == extra ); - def = cwal_value_unique_empty; - tx = sizeof(cwal_value*); - break; - case CWAL_TYPE_TUPLE: - assert( 0 == extra ); - def = cwal_tuple_value_empty; - tx = sizeof(cwal_tuple); - break; - default: - assert(0 && "Unhandled type in cwal_value_new()!"); - /* FIXME: set e error state here. */ - return NULL; - } - assert( def.vtab->typeID != CWAL_TYPE_UNDEF ); - /* See if one of the recycle bins can serve the request... */ - if(CWAL_TYPE_STRING == t){ - v = cwal_string_from_recycler( e, extra ); - } - else{ - cwal_recycler * re = - cwal_recycler_get( e, t /*def.vtab->typeID (wrong for x/z-strings) */ ); - /* MARKER(("BIN #%d(%s)\n", recycleListIndex, def.vtab->typeName)); */ - if(re){ - if(!re->count){ - ++e->metrics.valuesRecycleMisses; - ++re->misses; - }else{ - /* Recycle (take) the first entry from the list. */ - ++re->hits; - ++e->metrics.valuesRecycled; - /* MARKER(("BIN #%d(%s) LENGTH=%u\n", recycleListIndex, cwal_type_id_name(t), (unsigned)re->count)); */ - v = (cwal_value*)re->list; - assert(NULL != v); - re->list = cwal_value_snip( v ); - --re->count; - CWAL_TR_V(e,v); - CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER, - "RECYCLED FROM BIN."); - assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)); - CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_RECYCLED); - assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)); - *v = def /* needed for types which share a recycling pool */; - } - } - } - ++e->metrics.requested[t]; - if(!v){ - /* Check the memchunk for an exact-fit match. This saves a - small handful of allocs and total memory, but increases the - search/miss ratio notably, going from ~5% misses to ~20% misses - in the s2 amalgamated unit tests. - */ - cwal_size_t reqSize = sz+tx; - v = (e->reChunk.config.useForValues - && e->reChunk.head - && e->reChunk.head->size<=reqSize) - ? (cwal_value*)cwal_memchunk_request(e, &reqSize, 0, - "cwal_value_new()") - : 0; - if(v){ - assert(reqSize==sz+tx); - }else{ - v = (cwal_value *)cwal_malloc(e, sz+tx); - if(v){ - ++e->metrics.allocated[t]; - e->metrics.bytes[t] += sz+tx; - } - } - } - if( v ) { - int rc = 0; - *v = def; - if(tx || extra){ - memset(v+1, 0, tx + extra); - } - assert(0 == v->scope); - assert(0 == CWAL_REFCOUNT(v)); - - CWAL_TR_V(e, v); - CWAL_TR_S(e, s); - CWAL_TR2(e, CWAL_TRACE_VALUE_CREATED); - if(s) { - int check = 0; - rc = cwal_value_xscope( e, s, v, &check ) - /* reminder: xscope "cannot fail" in this case except - on inputs which are corrupt in "just the right way" - such that they appear valid by this point. */ - ; - assert(0 == CWAL_REFCOUNT(v)); - assert(s == v->scope); - assert(-1==check); - assert(0==rc); - } - if(rc){ - /* Reminder to self: since the port to linked lists for - values, this scenario essentially cannot happen for a - new value. - */ - v->vtab->cleanup( e, v ); - cwal_free(e, v); - v = NULL; - } - else if(e->values.prototypes - /* Will only be false once, while creating - e->values.prototypes! This also means e->values.prototypes will - not get the built-in prototype for arrays unless we - special-case that, but clients do not have access - to e->values.prototypes, anyway, so that shouldn't be a - problem/limitation. - */){ - cwal_obase const * base = CWAL_VOBASE(v); - if(base){ - cwal_value * proto = cwal_prototype_base_get(e, t); - if(proto){ - /*MARKER(("Setting %s prototype from base: @%p\n", - cwal_type_id_name(t), (void const*)proto));*/ - cwal_value_prototype_set(v, proto); - assert(proto == cwal_value_prototype_get(e, v)); - } - } - } -#ifdef DEBUG - if(v){ /* Sanity-check that the CWAL_VOBASE() and CWAL_VALPART() - can perform round-trip conversions. If not, much of cwal - is broken! - */ - cwal_obase const * baseCheck = CWAL_VOBASE(v); - if(baseCheck){ - /*MARKER("v=@%p, baseCheck=@%p\n", (void const *)v, (void const *)baseCheck);*/ - assert( CWAL_VALPART(baseCheck) == v ); - } - } -#endif - } - return v; -} - -cwal_value * cwal_new_bool( int v ){ - return v ? &CWAL_BUILTIN_VALS.vTrue : &CWAL_BUILTIN_VALS.vFalse; -} - -cwal_value * cwal_value_true(){ - return &CWAL_BUILTIN_VALS.vTrue; -} - -cwal_value * cwal_value_false(){ - return &CWAL_BUILTIN_VALS.vFalse; -} - -cwal_value * cwal_value_null(){ - return &CWAL_BUILTIN_VALS.vNull; -} - -cwal_value * cwal_value_undefined(){ - return &CWAL_BUILTIN_VALS.vUndef; -} - -cwal_value * cwal_new_integer( cwal_engine * e, cwal_int_t v ){ - if(!e) return NULL; - if(v>=CWAL_BUILTIN_INT_FIRST && - v<= CWAL_BUILTIN_INT_LAST){ - METRICS_REQ_INCR(e,CWAL_TYPE_INTEGER); - return (cwal_value*) &CWAL_BUILTIN_VALS.memInt[v + -CWAL_BUILTIN_INT_FIRST]; - } - else{ - cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_INTEGER,0); - if( c ){ - *CWAL_INT(c) = v; - } - return c; - } -} - -cwal_value * cwal_new_double( cwal_engine * e, cwal_double_t v ) -{ - if( CWAL_BUILTIN_VALS.dbls.zero == v ){ - METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE); - return CWAL_BUILTIN_VALS.vDbl0; - } - else if( CWAL_BUILTIN_VALS.dbls.one == v ){ - METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE); - return CWAL_BUILTIN_VALS.vDbl1; - } - else if( CWAL_BUILTIN_VALS.dbls.mOne == v ){ - METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE); - return CWAL_BUILTIN_VALS.vDblM1; - }else{ - cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_DOUBLE, 0); - if( c ){ - memcpy(CWAL_DBL_NONULL(c), &v, sizeof(cwal_double_t)); - } - return c; - } -} - -cwal_value * cwal_new_unique( cwal_engine * e, cwal_value * wrapped ){ - cwal_value * v = e ? cwal_value_new(e, e->current, CWAL_TYPE_UNIQUE, 0) : 0; - if(v){ - *CWAL_UNIQUE_VALPP(v) = wrapped; - if(wrapped){ - cwal_value_ref(wrapped); - cwal_value_rescope(v->scope, wrapped) - /* that's just me being overly pedantic. It's not - possible that wrapped->scope is newer if v->scope - resp. e->current is running. */; - } - } - return v; -} - -cwal_value * cwal_unique_wrapped_get( cwal_value const * v ){ - cwal_value ** rc = CWAL_UNIQUE_VALPP(v); - return rc ? *rc : 0; -} - -int cwal_unique_wrapped_set( cwal_value * v, cwal_value * w ){ - cwal_value ** ch = CWAL_UNIQUE_VALPP(v); - if(!ch) return CWAL_RC_TYPE; - else if(v == w) return CWAL_RC_CYCLES_DETECTED; - else{ - cwal_value * prev = *ch; - if(prev == w) return 0; - else{ - *ch = w; - if(w){ - cwal_value_ref(w); - cwal_value_rescope(v->scope, w); - } - if(prev){ - assert( CWAL_REFCOUNT(prev) || CWAL_MEM_IS_BUILTIN(prev) ); - cwal_value_unref(prev); - } - return 0; - } - } -} - -uint16_t cwal_tuple_length(cwal_tuple const * p){ - return p ? p->n : 0; -} - -int cwal_tuple_set(cwal_tuple * p, uint16_t n, cwal_value * v){ - cwal_value * self = CWAL_VALPART(p); - if(!self) return CWAL_RC_MISUSE; - else if(n>=p->n) return CWAL_RC_RANGE; - /* else if(self == v) return CWAL_RC_CYCLES_DETECTED; - Cycles are not a problem, are they? */ - else{ - cwal_value * ch = p->list[n]; - if(v) cwal_value_ref(v); - if(ch) cwal_value_unref(ch); - p->list[n] = v; - if(v) cwal_value_rescope(self->scope, v); - return 0; - } -} - -cwal_value * cwal_tuple_get(cwal_tuple const * p, uint16_t n){ - return (p && nn) ? p->list[n] : 0; -} - -cwal_tuple * cwal_new_tuple( cwal_engine * e, uint16_t n ){ - if(!n){ - assert(CWAL_MEM_IS_BUILTIN(CWAL_BUILTIN_VALS.vTuple0)); - return CWAL_TUPLE(CWAL_BUILTIN_VALS.vTuple0); - }else{ - cwal_tuple * p = 0; - cwal_value * v = e - ? cwal_value_new(e, e->current, CWAL_TYPE_TUPLE, 0) - : 0; - if(v){ - cwal_size_t const reqSize = sizeof(cwal_value*) * n; - p = CWAL_TUPLE(v); - assert(p); - p->list = (cwal_value**)cwal_malloc2(e, reqSize); - if(!p->list){ - cwal_value_unref(v); - p = 0; - }else{ - memset(p->list, 0, reqSize); - p->n = n; - } - } - return p; - } -} - -cwal_value * cwal_new_tuple_value( cwal_engine * e, uint16_t n ){ - cwal_tuple * p = cwal_new_tuple(e, n); - return CWAL_VALPART(p); -} - -cwal_value * cwal_tuple_value( cwal_tuple const * p ){ - return CWAL_VALPART(p); -} - -cwal_tuple * cwal_value_get_tuple( cwal_value * v ){ - return CWAL_TUPLE(v); -} - -#if 0 -/* Not needed because tuples cannot be prototypes. Maybe - someday. */ -cwal_tuple * cwal_tuple_part( cwal_engine * e, - cwal_value * v ){ - cwal_tuple * p; - do{ - if( (p = CWAL_TUPLE(v)) ) return p; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} -#endif - -int cwal_tuple_visit( cwal_tuple * o, cwal_value_visitor_f f, void * state ){ - cwal_value * const tv = CWAL_VALPART(o); - if(!tv || !f) return CWAL_RC_MISUSE; - /*else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED;*/ - else if(!o->n) return 0; - else { - uint16_t i; - cwal_value * v; - int rc = CWAL_RC_OK; - int opaque; - cwal_visit_list_begin(tv, &opaque); - cwal_value_ref(tv); - for( i = 0; i < o->n && !rc; ++i ){ - v = o->list[i]; - if(v) cwal_value_ref(v); - rc = f( v, state ); - if(v) cwal_value_unref(v); - } - cwal_value_unhand(tv); - cwal_visit_list_end(tv, opaque); - return rc; - } -} - -cwal_value * cwal_new_array_value(cwal_engine *e){ - cwal_value * v = (e && e->current) - ? cwal_value_new(e, e->current, CWAL_TYPE_ARRAY,0) - : 0; - if( NULL != v ){ - cwal_array * ar = CWAL_ARRAY(v); - cwal_value * proto = ar->base.prototype; - *ar = cwal_array_empty; - ar->base.prototype = proto; - } - return v; -} - -cwal_array * cwal_new_array(cwal_engine *e){ - return cwal_value_get_array(cwal_new_array_value(e)); -} - -cwal_value * cwal_new_object_value(cwal_engine *e){ - cwal_value * v = (e && e->current) - ? cwal_value_new(e, e->current, CWAL_TYPE_OBJECT,0) - : 0; - if( NULL != v ) - { - cwal_object * ar = CWAL_OBJ(v); - cwal_value * proto = ar->base.prototype; - *ar = cwal_object_empty; - ar->base.prototype = proto; - } - return v; -} - -cwal_object * cwal_new_object(cwal_engine *e){ - return cwal_value_get_object(cwal_new_object_value(e)); -} - -/** - If li->list is not 0 and e has space in its list memory recycler, - the contents of li are moved into the recycler and *li is reset to a - clean state. If li->list but the recycler has no slots, the memory - is freed instead. -*/ -static void cwal_list_to_recycler( cwal_engine * e, cwal_list * li ); - -/** - Frees all cwal_kvp entries in the given htable. If freeList - is true then it also frees up htable->list.list. -*/ -static void cwal_cleanup_htable( cwal_engine * const e, - cwal_htable * const htable, - bool freeList){ - cwal_kvp * kvp = 0; - cwal_kvp * next = 0; - for( cwal_midsize_t i = 0; - htable->list.count && (i < htable->hashSize); - ++i ){ - kvp = (cwal_kvp*)htable->list.list[i]; - htable->list.list[i] = next = NULL; - for(; kvp; kvp = next){ - assert(htable->list.count>0); - next = kvp->right; - kvp->right = 0; - cwal_kvp_free( e, kvp, 1 ); - --htable->list.count; - } - } - assert(0==htable->list.count); - if(freeList){ - cwal_list_to_recycler(e, &htable->list); - assert(0==htable->list.count); - assert(0==htable->list.alloced); - assert(0==htable->list.list); - } -} - -/** - Cleanup routine for the cwal_obase part of classes which use that. - Cleans up the contents of b->kvp and sets b->kvp to NULL. Also, if - unrefProto is true, clears the - CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flag from b->flags and - unrefs/NULLs b->prototype. -*/ -static void cwal_cleanup_obase( cwal_engine * e, cwal_obase * b, bool unrefProto ){ -#if CWAL_OBASE_ISA_HASH - cwal_cleanup_htable(e, &b->hprops, true); -#else - while(b->kvp){ - cwal_kvp * kvp = b->kvp; - cwal_kvp * next = 0; - b->kvp = 0; - /* In theory the is-visiting flag is not needed here because - b->kvp=0 means we cannot possibly traverse the property - list as a side-effect of this cleanup. Well, we cannot - travse THIS property list. We can only hope that cleanup - does not then add properties to b->kvp, but that's why we - while(b->kvp). - */ - /* b->flags |= CWAL_F_IS_VISITING; */ - for( ; kvp; kvp = next ){ - next = kvp->right; - kvp->right = 0; - /* if(kvp->key==bv) kvp->key = 0; */ - /* if(kvp->value==bv) kvp->value = 0; */ - cwal_kvp_free( e/* , bv->scope */, kvp, 1 ); - } - /* b->flags &= ~CWAL_F_IS_VISITING; */ - } -#endif - if( unrefProto ){ - b->flags &= ~CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET; - if( b->prototype ){ - cwal_value_prototype_set( CWAL_VALPART(b), NULL ); - } - } -} - -/** - If table->hashSize is 0, initialize the table for hashSize - elements, or CwalConsts.ScopePropsHashSize if hashSize==0. Use this - only to initialize an empty table. Returns 0 on success, - CWAL_RC_OOM on error. -*/ -static int cwal_htable_alloc( cwal_engine * const e, cwal_htable * const table, - cwal_midsize_t hashSize ){ - if(table->hashSize && table->list.alloced){ - assert(table->list.alloced >= table->hashSize); - assert(table->hashSize >= hashSize); - return 0; - }else{ - assert(!table->list.alloced); - if(!hashSize) hashSize = CwalConsts.DefaultHashtableSize; - int rc = 0; - if(hashSize > cwal_list_reserve(e, &table->list, hashSize)){ - rc = CWAL_RC_OOM; - }else{ - table->hashSize = hashSize; - } - return rc; - } -} - -/** @internal - - Internal impl for cwal_list-of-(cwal_kvp*) hashtable search. If - tableIndex is not NULL then *tableIndex is assigned to the hash - table index for the given key, even on search failure. - - If a match is found then its kvp entry is returned and if left is - not NULL then *left will point to the kvp immediately to the left - of the returned kvp in the hash table (required for re-linking - collisions). - - This function is intollerant of NULLs for (table,key). -*/ -static cwal_kvp * cwal_htable_search_impl_v( cwal_htable const * htable, - cwal_value const * key, - cwal_midsize_t * tableIndex, - cwal_kvp ** left ){ - if(!htable->hashSize) return NULL; - cwal_midsize_t const ndx = cwal_value_hash( key ) % htable->hashSize; - if(!htable->list.count){ - if(left) *left = 0; - if(tableIndex) *tableIndex = ndx; - return NULL; - } - cwal_type_id const kType = key->vtab->typeID /*cwal_value_type_id(key)*/; - cwal_kvp * kvp = (cwal_kvp*)htable->list.list[ndx]; - //dump_val(key, "cwal_htable_search_impl_v() key"); - //MARKER(("ndx=%d, list->count=%d, kvp=%p\n", (int)ndx, (int)htable->list.count, (void const*)kvp)); - if(tableIndex) *tableIndex = ndx; - for( ; kvp; (left?(*left=kvp):NULL), kvp = kvp->right ){ - assert(kvp->key); -#if 0 - if(kvp){ - dump_val(kvp->key,"cwal_htable_search_impl_v() found key"); - dump_val(kvp->value,"cwal_htable_search_impl_v() found value"); - } -#endif - if(kvp->key==key) return kvp; - else if(kType != kvp->key->vtab->typeID - /*cwal_value_type_id(kvp->key)*/){ - //dump_val(kvp->key,"cwal_htable_search_impl_v() type mismatch. Skipping"); - continue; - } - else if(0==key->vtab->compare(key, kvp->key)){ - //MARKER(("returning ndx=%d, kvp=%p\n", (int)ndx, (void const*)kvp)); - return kvp; - } - } - return NULL; -} - -/** - C-string conterpart of cwal_htable_search_impl_v(). -*/ -static cwal_kvp * cwal_htable_search_impl_cstr( cwal_htable const * htable, - char const * key, - cwal_midsize_t keyLen, - cwal_midsize_t * tableIndex, - cwal_kvp ** left ){ - if(!htable->hashSize) return NULL; - cwal_hash_t const ndx = - cwal_hash_bytes( key, keyLen ) % htable->hashSize; - if(!htable->list.count){ - if(left) *left = 0; - if(tableIndex) *tableIndex = ndx; - return NULL; - } - cwal_kvp * kvp = (cwal_kvp*)htable->list.list[ndx]; - cwal_string const * sk; -#if 0 - MARKER(("%s() hash entries=%d, ndx=%d, key=%.*s, hash=%08x, kvp=%p\n", __func__, - (int)htable->list.count, (int)ndx, (int)keyLen, key, - (unsigned)cwal_hash_bytes( key, keyLen ), - (void *)kvp)); -#endif - if(tableIndex) *tableIndex = ndx; - for( ; kvp; (left?(*left=kvp):NULL), kvp = kvp->right ){ - assert(kvp->key); - //dump_val(kvp->key, "Comparing to this key."); - if(CWAL_TYPE_STRING != cwal_value_type_id(kvp->key)) continue; - sk = cwal_value_get_string(kvp->key); - if(keyLen != CWAL_STRLEN(sk)) continue; - else if(0==cwal_compare_str_cstr(sk, key, keyLen)){ - return kvp; - } - } - return NULL; -} - -/** - The cwal_htable counterpart of the public API's - cwal_hash_insert_with_flags_v(). The isResizing flag must only be - true when this insert is called during the course of - cwal_htable_resize(). That parameter is not strictly needed: it's - used as a sanity-checking measure for a case which could, if - internally used incorrectly, lead to infinite recursion (stack - overflow). -*/ -static int cwal_htable_insert_impl_v( cwal_value * const container, - cwal_htable * const table, - cwal_value * const key, cwal_value * const v, - bool allowOverwrite, uint16_t kvpFlags, - bool isResizing ); -/** - The cwal_htable counterpart of the public API's - cwal_hash_grow_if_loaded(). If load<=0 then - CwalConsts.PreferredHashLoad is used. htable is assumed to be - a component of the given container. -*/ -static int cwal_htable_grow_if_loaded( cwal_value * const container, - cwal_htable * htable, double load ); - -/** - Assumes table is used as a hashtable for the value hv. The table - is, if needed, resized to toSize. Returns 0 on success, CWAL_RC_OOM - on OOM. -*/ -static int cwal_htable_resize( cwal_value * const hv, - cwal_htable * const table, - cwal_midsize_t toSize ){ - if(!hv) return CWAL_RC_MISUSE; - else if(!toSize) return CWAL_RC_RANGE; - //else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST; - else if(toSize==table->hashSize) return 0; - /* highly arguable: toSize = cwal_trim_hash_size( toSize ); */ - cwal_list li = cwal_list_empty; - int rc = 0; - cwal_engine * e = hv->scope->e; - cwal_midsize_t i; - //cwal_midsize_t const oldSize = table->hashSize; -#ifdef DEBUG - cwal_midsize_t const oldCount = table->list.count; -#endif - //MARKER(("Resizing htable @%p from %d to %d\n", (void *)table, (int)table->hashSize, (int)toSize)); - //dump_val(hv, "hv htable holder"); - if(toSize > cwal_list_reserve(e, &li, toSize)){ - assert(!li.list); - return CWAL_RC_OOM; - } - { - /* Swap the table memory */ - cwal_list const tmp = table->list; - table->list = li; - li = tmp; - } - /* Iterate over li.list and move all entries into table->list, - assigning them to a new index, as appropriate. */ - table->hashSize = toSize; - assert(toSize <= table->list.alloced); - assert(!table->list.count); - assert(li.alloced ? !!li.list : !li.list); - for( i = 0; li.count; ++i ){ - cwal_kvp * kvp = (cwal_kvp*)li.list[i]; - cwal_kvp * next = 0; - li.list[i] = 0; - for( ; !rc && kvp; kvp = next ){ - cwal_value * const k = kvp->key; - cwal_value * const v = kvp->value; - assert(V_SEEMS_OK(k)); - assert(V_SEEMS_OK(v)); - next = kvp->right; - /* kvp holds refs to k/v, but we're going to steal them... */ - kvp->right = 0; - kvp->key = 0; - kvp->value = 0; - /* *kvp = cwal_kvp_empty; must retain flags */ - e->values.hashXfer = kvp; -#if defined(DEBUG) - cwal_midsize_t const cCheck = table->list.count; -#endif - rc = cwal_htable_insert_impl_v( hv, table, k, v, false, kvp->flags, true ); - assert(!rc) /* cannot fail under these conditions - no allocation */; - assert(0==e->values.hashXfer); - assert(kvp->key == k); - assert(kvp->value == v); - assert(table->list.count==cCheck+1); - /* We got an extra ref on k/v up there, so... */ - assert(CWAL_REFCOUNT(k)>1 || CWAL_MEM_IS_BUILTIN(k)); - assert(CWAL_REFCOUNT(v)>1 || CWAL_MEM_IS_BUILTIN(v)); - cwal_value_unref(k); - cwal_value_unref(v); - assert(CWAL_MEM_IS_BUILTIN(k) || k->scope); - assert(CWAL_MEM_IS_BUILTIN(v) || v->scope); - assert(V_SEEMS_OK(k)); - assert(V_SEEMS_OK(v)); - --li.count; - } - } - assert(0==li.count); -#if defined(DEBUG) - assert(table->list.count == oldCount); -#endif - cwal_list_reserve(e, &li, 0); - return rc; -} - -static int cwal_htable_grow_if_loaded( cwal_value * const container, - cwal_htable * htable, double load ){ - if(!htable->list.count) return 0; - else if(load<=0) load = CwalConsts.PreferredHashLoad; - else if(load<0.5) load = 0.5/*kinda arbitrary*/; - double const hashSize = (double)htable->hashSize; - double const entryCount = (double)(htable->list.count - ? htable->list.count - : CwalConsts.DefaultHashtableSize); - int rc = 0; - assert(hashSize); -#if 0 - /* arguable. If someone wants loads of 5.0, let him. OTOH, values - approaching or surpassing 1.0 break an upcoming calculation... */ - if(load >= 0.95) load = 0.95; -#endif - if((entryCount / hashSize) > load){ - cwal_midsize_t const newSize = - (cwal_midsize_t)cwal_next_prime((cwal_midsize_t)(entryCount * 3 / 2)); - assert(newSize > entryCount); -#if 0 - MARKER(("Resizing hash: container@%p old size=%f, count=%f, Going for load %f, size=%d\n", - (void const *)container, - hashSize, entryCount, load, (int)newSize)); -#endif - rc = cwal_htable_resize(container, htable, newSize); - } - return rc; -} - -int cwal_htable_insert_impl_v( cwal_value * const container, - cwal_htable * const table, - cwal_value * const key, cwal_value * const v, - bool allowOverwrite, uint16_t kvpFlags, - bool isResizing ){ - cwal_midsize_t ndx = 0; - cwal_kvp * left = 0; - cwal_kvp * kvp; - cwal_obase * const base = CWAL_VOBASE(container); - cwal_scope * const sc = container->scope; - if(!key || !v) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_IN_CLEANUP(container)) return CWAL_RC_DESTRUCTION_RUNNING; -#if 0 - // These need to be checked in the higher-level containers which - // call this - else if((h = CWAL_HASH(container)) && - table==&h->htable - && CWAL_V_IS_VISITING_LIST(container)){ - /* Genuine hashtable entries */ - return CWAL_RC_IS_VISITING_LIST; - } - else if(CWAL_V_IS_VISITING(container)){ - /* base->hprops properties */ - return CWAL_RC_IS_VISITING; - } -#endif - else if(!cwal_prop_key_can(key)) return CWAL_RC_TYPE; - assert(!(CWAL_CONTAINER_DISALLOW_PROP_SET & base->flags) - && "Expecting this to be checked before this is called."); - int rc = 0; - /* Maintenance reminder: we need to init/resize the table before - searching so that the calculated hash index is valid. There's a - potential corner case here where we'll grow even if the entry is - already in the hashtable (so no new space would have been - needed), but that's not too tragic. Working around that would - require(?) searching, then resizing if needed, then searching - _again_ so that we pick up the proper (new) kvp->right and ndx - values. Not worth it. */ - if(!table->list.alloced){ - assert(!isResizing && "Table must have already been allocated in this case."); - rc = cwal_htable_alloc(CWAL_VENGINE(container), table, 0); - }else if(!isResizing){ - rc = cwal_htable_grow_if_loaded(container, table, -1.0); - } - if(rc) return rc; - assert(table->list.alloced>=table->hashSize); - kvp = cwal_htable_search_impl_v( table, key, &ndx, &left ); - if(kvp){ - assert(!isResizing); - if(!allowOverwrite) return CWAL_RC_ALREADY_EXISTS; - else if(CWAL_VAR_F_CONST & kvp->flags){ - return CWAL_RC_CONST_VIOLATION; - }else if(kvp->key != key){ - cwal_value * const old = kvp->key; - cwal_value_xscope(sc->e, sc, key, 0); - if(E_IS_DEAD(sc->e)) return sc->e->fatalCode; - kvp->key = key; - cwal_value_ref(key); - cwal_value_unref(old); - } - if(kvp->value != v){ - cwal_value * const old = kvp->value; - cwal_value_xscope(sc->e, sc, v, 0); - if(E_IS_DEAD(sc->e)) return sc->e->fatalCode; - kvp->value = v; - cwal_value_ref(v); - cwal_value_unref(old); - } - if(CWAL_VAR_F_PRESERVE!=kvpFlags) kvp->flags = kvpFlags; - }else{/* Not found: add it. */ - if(CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & base->flags){ - return CWAL_RC_DISALLOW_NEW_PROPERTIES; - } - if(sc->e->values.hashXfer){ - kvp = sc->e->values.hashXfer; - sc->e->values.hashXfer = 0; - }else{ - kvp = cwal_kvp_alloc(sc->e); - if(!kvp) return CWAL_RC_OOM; - } - assert(!kvp->key); - assert(!kvp->value); - assert(!kvp->right); - if(left){ - kvp->right = left->right; - left->right = kvp; - }else{ - kvp->right = (cwal_kvp*)table->list.list[ndx]; - table->list.list[ndx] = kvp; - } - cwal_value_xscope(sc->e, sc, key, 0); - cwal_value_xscope(sc->e, sc, v, 0); - cwal_value_ref(key); - cwal_value_ref(v); - kvp->key = key; - kvp->value = v; - if(CWAL_VAR_F_PRESERVE!=kvpFlags) kvp->flags = kvpFlags; - assert(CWAL_MEM_IS_BUILTIN(key) || key->scope); - assert(CWAL_MEM_IS_BUILTIN(v) || v->scope); -#if 0 && CWAL_OBASE_ISA_HASH - dump_val(kvp->key,"inserted hash key"); - dump_val(kvp->value,"inserted hash value"); - dump_val(container,"inserted into container"); - MARKER(("hash ndx=%d, hashSize=%d\n", (int)ndx, (int)table->hashSize)); -#endif - ++table->list.count; - } - return 0; -} - -static int cwal_htable_remove_impl_v( cwal_value * const vSelf, - cwal_htable * const htable, - cwal_value * const key ){ - if(!htable || !key) return CWAL_RC_MISUSE; - else if(!htable->hashSize || !htable->list.count) return CWAL_RC_NOT_FOUND; - assert(!(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) - && "Expecting this to be checked upstream."); -#if 0 - // To be checked by higher-level containers... - if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & CWAL_VOBASE(vSelf)->flags) return CWAL_RC_DISALLOW_PROP_SET; - else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST; -#endif - cwal_midsize_t ndx = 0; - cwal_kvp * left = 0; - cwal_kvp * const kvp = - cwal_htable_search_impl_v( htable, key, &ndx, &left ); - cwal_engine * const e = vSelf->scope->e; - if(!kvp) return CWAL_RC_NOT_FOUND; - else{ - assert(htable->list.count>0); - if(left){ - left->right = kvp->right; - }else{ - htable->list.list[ndx] = kvp->right; - } - kvp->right = NULL; - --htable->list.count; - cwal_kvp_free(e, kvp, 1); - return 0; - } -} - -static int cwal_htable_remove_impl_cstr( cwal_value * const vSelf, - cwal_htable * const htable, - char const * const key, - cwal_midsize_t keyLen ){ - if(!vSelf || !key) return CWAL_RC_MISUSE; - else if(!htable->hashSize || !htable->list.count) return CWAL_RC_NOT_FOUND; - assert(!(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) - && "Expecting this to be checked upstream."); -#if 0 - // To be checked by higher-level containers... - else if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & CWAL_VOBASE(vSelf)->flags) return CWAL_RC_DISALLOW_PROP_SET; - else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST; -#endif - cwal_midsize_t ndx = 0; - cwal_kvp * left = 0; - cwal_kvp * kvp; - cwal_engine * const e = vSelf->scope->e; - kvp = cwal_htable_search_impl_cstr( htable, key, keyLen, &ndx, &left ); - if(!kvp) return CWAL_RC_NOT_FOUND; - else{ - assert(htable->list.count>0); - if(left){ - left->right = kvp->right; - }else{ - htable->list.list[ndx] = kvp->right; - } - kvp->right = NULL; - --htable->list.count; - cwal_kvp_free(e, kvp, 1); - return 0; - } -} - -void cwal_list_to_recycler( cwal_engine * e, cwal_list * li ){ - if(li->list){ - cwal_memchunk_add(e, li->list, li->alloced * sizeof(void*)); - *li = cwal_list_empty; - }else{ - assert(!li->count); - assert(!li->alloced); - li->isVisiting = false; - } -} - -/** - Cleans up various parts of an array: - - self must be a (ceal_value*). - - Cleans up all list entries Then... - - freeList: if true, frees all list memory - - freeProps: if true, clears all properties. - - unrefProto: if true, clears the - CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flag from the cwal_obase part - and unsets/unrefs the prototype. -*/ -static void cwal_value_cleanup_array_impl( cwal_engine * e, void * self, - char freeList, char freeProps, - char unrefProto ){ - cwal_value * const vSelf = (cwal_value *)self; - cwal_array * const ar = cwal_value_get_array(vSelf); - int opaque; - assert(NULL!=ar); - cwal_visit_list_begin(vSelf, &opaque); - if( ar->list.count ){ - cwal_value * v; - cwal_size_t i = 0, x = ar->list.count -1; - for( ; i < ar->list.count; ++i, --x ){ - v = (cwal_value*)ar->list.list[x]; - if(v){ - ar->list.list[x] = NULL; - if(!CWAL_V_IS_IN_CLEANUP(v)){ - cwal_value_unref(v); - } - } - } - ar->list.count = 0; - } - cwal_visit_list_end(vSelf, opaque); - if(freeList && ar->list.list){ - cwal_list_to_recycler(e, &ar->list); - } - if(freeProps) { - cwal_cleanup_obase( e, &ar->base, 0 ); - } - if(unrefProto){ - ar->base.flags &= ~CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET; - if(ar->base.prototype){ - assert(vSelf->scope && "We can't have a prototype and yet have no scope."); - /* dump_val(vSelf,"Unref'ing this one's prototype"); */ - /* dump_val(ar->base.prototype,"Unref'ing prototype"); */ - cwal_value_prototype_set( vSelf, NULL ); - } - } -} - - -/** - cwal_value_vtab::cleanup() impl for Array values. Cleans up - self-owned memory, but does not free self. -*/ -void cwal_value_cleanup_array( cwal_engine * e, void * self ){ - cwal_value_cleanup_array_impl( e, self, 1, 1, 1 ); -} - -void cwal_array_clear( cwal_array * ar, char freeList, char freeProps ){ - cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0; - cwal_engine * e = s ? s->e : 0; - if(e){ - cwal_value_cleanup_array_impl( e, CWAL_VALPART(ar), - freeList, freeProps, 0 ); - } -} - -void cwal_value_cleanup_object( cwal_engine * e, void * self ){ - cwal_value * vSelf = (cwal_value *)self; - cwal_object * ar = cwal_value_get_object(vSelf); - assert(vSelf && ar); - cwal_cleanup_obase( e, &ar->base, 1 ); - *ar = cwal_object_empty; -} - - -void cwal_value_cleanup_function( cwal_engine * e, void * self ){ - cwal_value * v = (cwal_value*)self; - cwal_function * f = CWAL_VVPCAST(cwal_function,v); - assert(v && f); - if(f->state.finalize){ - f->state.finalize( e, f->state.data ); - } - cwal_cleanup_obase( e, &f->base, 1 ); - *f = cwal_function_empty; -} - -int cwal_value_fetch_function( cwal_value const * val, cwal_function ** x){ - if( ! val ) return CWAL_RC_MISUSE; - else if( CWAL_TYPE_FUNCTION != val->vtab->typeID ) return CWAL_RC_TYPE; - else{ - if(x) *x = CWAL_VVPCAST(cwal_function,val); - return 0; - } -} - - -cwal_value * cwal_new_function_value(cwal_engine * e, - cwal_callback_f callback, - void * state, - cwal_finalizer_f stateDtor, - void const * stateTypeID ){ - if(!e || !callback) return NULL; - else{ - cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_FUNCTION, 0); - if( NULL != v ) { - cwal_function * f = CWAL_VVPCAST(cwal_function,v); - cwal_value * proto = f->base.prototype; - *f = cwal_function_empty; - f->base.prototype = proto; - f->state.data = state; - f->state.finalize = stateDtor; - f->state.typeID = stateTypeID; - f->callback = callback; - } - return v; - } -} - -cwal_function * cwal_new_function(cwal_engine * e, - cwal_callback_f callback, - void * state, - cwal_finalizer_f stateDtor, - void const * stateTypeID ){ - cwal_value * v = cwal_new_function_value(e, callback, state, - stateDtor, stateTypeID); - return v ? cwal_value_get_function(v) : NULL; -} - -int cwal_function_unref(cwal_function *fv){ - cwal_value * v = CWAL_VALPART(fv); - if(!v){ - assert(!fv); - return CWAL_RC_MISUSE; - } - assert(v->scope); - assert(v->scope->e); - return cwal_value_unref2( v->scope->e, v ); -} - -cwal_engine * cwal_scope_engine(cwal_scope const * s){ - return s ? s->e : NULL; -} - - -int cwal_function_call_array( cwal_scope * s, - cwal_function * f, - cwal_value * self, - cwal_value ** rv, - cwal_array * args){ - cwal_value ** argv = args ? - (args->list.count ? (cwal_value **)args->list.list : 0) - : 0; - int const argc = argv ? (int)args->list.count : 0; - int rc; - cwal_value * vargs = argc ? CWAL_VALPART(args) : 0; - char const aWasVacSafe = vargs - ? CWAL_V_IS_VACUUM_SAFE(vargs) - : 1; - if(argc && !aWasVacSafe){ - cwal_value_make_vacuum_proof(vargs,1); - } - cwal_value_ref(vargs); - rc = s - ? cwal_function_call_in_scope( s, f, self, rv, argc, argv ) - : cwal_function_call( f, self, rv, argc, argv ); - cwal_value_unhand(vargs); - if(!aWasVacSafe){ - cwal_value_make_vacuum_proof(vargs,0); - } - return rc; -} - -int cwal_function_call_in_scope2( cwal_scope * s, - cwal_function * f, - cwal_value * propertyHolder, - cwal_value * self, - cwal_value ** _rv, - uint16_t argc, - cwal_value * const * argv){ - uint16_t cflags = 0; - if(!s ||!s->e || !f) return CWAL_RC_MISUSE; - else if((cflags = cwal_container_flags_get(CWAL_VALPART(f))) - && (CWAL_CONTAINER_INTERCEPTOR_RUNNING & cflags)){ - return CWAL_RC_CYCLES_DETECTED; - } - else { - cwal_engine * const e = s->e /* typing saver */; - cwal_scope * old = e->current /* previous scope */; - cwal_scope * check = 0 /* sanity check */; - int rc = 0; - cwal_callback_args args = cwal_callback_args_empty /* callback state */; - cwal_value * rv = 0 /* result value for f() */; - cwal_value * fv = CWAL_VALPART(f); - cwal_callback_hook const hook = e->cbHook /* why (again) do we take/need a copy? */; - char const fWasVacuumProof = CWAL_V_IS_VACUUM_SAFE(fv); - cwal_obase * selfBase = CWAL_VOBASE(self); - char const selfWasVacSafe = selfBase - ? CWAL_V_IS_VACUUM_SAFE(self) - : 0; - /* uint32_t const oldScopeFlags = s->flags; */ - assert(fv->scope); - args.engine = e; - args.scope = s; - args.self = self; - args.callee = f; - args.state = f->state.data; - args.stateTypeID = f->state.typeID; - args.argc = argc; - args.argv = argv; - args.propertyHolder = propertyHolder; - /* s->flags |= CWAL_F_IS_CALL_SCOPE; */ - - /* We can't just fiddle with the refcount here: - we have to make sure fv is removed from - fv->scope->mine.r0 so it's sweep-safe. */ - rc = cwal_refcount_incr(e, fv); - if(rc) return rc /* game over, man */; - - /* - We set the vacuum-proofing flag and an artificial - reference on f to proactively cover this hypothetical - case: - - function(){...}() - - where the anonymous function triggers a recursive sweep or - vacuum. - - Reminder to self: - - (proc(){...})() - - in th1ish, that can theoretically kill that proc before the - call op can be applied if auto-sweeping is running at an - interval of 1! In s2, the eval engine disables - sweeping/vacuuming during any given single expression, so - it's not a problem there unless/until we add recursive - sweeping/vacuuming. - */ - if(!fWasVacuumProof){ - cwal_value_make_vacuum_proof(fv, 1); - /* why does this trigger in th1ish? */ - assert(fv == (CWAL_REFCOUNT(fv) ? fv->scope->mine.headSafe : fv->scope->mine.r0)); - /* - Reminder: in th1ish we have refcount-0 funcs being - called. That's potentially unsafe (we've learned in the - meantime). - */ - } - if(self){ - cwal_value_ref(self); - if(selfBase && !selfWasVacSafe){ - cwal_value_make_vacuum_proof(self,1); - } - } - cwal_value_ref(propertyHolder); - if(hook.pre){ - rc = hook.pre(&args, hook.state); - } - if(!rc){ - uint16_t i; - for(i = 0; i < argc; ++i ) cwal_value_ref(argv[i]); - if(CWAL_CONTAINER_INTERCEPTOR & cflags){ - cwal_container_flags_set(fv,cflags | CWAL_CONTAINER_INTERCEPTOR_RUNNING); - assert(CWAL_CONTAINER_INTERCEPTOR & cwal_container_flags_get(fv)); - } - rc = f->callback( &args, &rv ); - if(CWAL_CONTAINER_INTERCEPTOR & cflags){ - cwal_container_flags_set(fv, cflags); - } - if(hook.post){ - /* ALWAYS call post() if pre() succeeds. */ - int const rc2 = hook.post(&args, hook.state, - rc, rc ? NULL : rv); - if(rc2 && !rc) rc = rc2; - } - for(i = 0; i < argc; ++i ) cwal_value_unhand(argv[i]); - } - /* assert(CWAL_REFCOUNT(fv)>0 && "Someone took my ref!"); */ - cwal_value_unhand(propertyHolder); - cwal_value_unhand(fv); - if(!fWasVacuumProof){ - cwal_value_make_vacuum_proof(fv, 0); - /*20181122: removed because this is triggering and i'm not 100% sure - whether this assertion is truly valid (i've been away from the - intricaces of this level too long :/) - - assert(fv == (CWAL_REFCOUNT(fv) - ? fv->scope->mine.headSafe : fv->scope->mine.r0)); - - For the time being we'll replace it with something less - intrusive... - */ - assert(fv->scope); - assert(!CWAL_RCFLAG_HAS(fv,CWAL_RCF_IS_GC_QUEUED)); - } - if(self){ - cwal_value_unhand(self); - if(selfBase && !selfWasVacSafe){ - cwal_value_make_vacuum_proof(self,0); - } - } - /* s->flags = oldScopeFlags; */ - check = e->current; - if(old != check){ - /* i've never seen this happen - it's intended as a - sanity check for higher-level implementors. */ - assert(!"The callback implementor or the engine " - "around it violated scope creation rules."); - MARKER(("WARNING: callback created scopes without popping them " - "(or popped too many!)." - " Cleaning them up now!\n")); - while(e->current && (e->current!=old)){ - cwal_scope_pop(e); - } - if(e->current!=old){ - assert(!"Serious scope mis-management during callback."); - rc = CWAL_RC_FATAL; - old = 0 /* assume it was lost along the way */; - } - } - e->current = old; - assert(e->current); - if(rc){ - if(rv) cwal_refunref(rv); - }else{ -#if 0 - /* Historical: disabled 20141124. The docs do not imply - that we do this, and no C code (aside from a stray - assertion) assumes it, either. And we need to leave - rv==0 in order to distinguish between "no return" and - "return undefined" (if we ever really want/need to). - FWIW, my callbacks almost always explicitly set it to - undefined, but that's more of a style thing than a - requirement. */ - if(!rv) rv = cwal_value_undefined(); -#endif - if(_rv) *_rv = rv; - else if(rv) cwal_refunref(rv); - } - return rc; - } -} - -int cwal_function_call_in_scope( cwal_scope * s, - cwal_function * f, - cwal_value * self, - cwal_value ** rv, - uint16_t argc, - cwal_value * const * argv){ - return cwal_function_call_in_scope2( s, f, 0, self, rv, argc, argv ); -} - -void * cwal_args_state( cwal_callback_args const * args, - void const * stateTypeID ){ - return (args && (args->stateTypeID==stateTypeID || !args->stateTypeID)) - ? args->state - : NULL; -} - -void * cwal_function_state_get( cwal_function * f, - void const * stateTypeID ){ - return (f && (f->state.typeID==stateTypeID || !stateTypeID)) - ? f->state.data - : NULL; - -} - -int cwal_function_set_rescoper( cwal_function * f, - cwal_value_rescoper_f rescoper){ - if(!f) return CWAL_RC_MISUSE; - else { - f->rescoper = rescoper; - return 0; - } -} - -int cwal_function_call_in_scopef( cwal_scope * s, - cwal_function * f, - cwal_value * self, - cwal_value ** rv, ... ){ - if(!s || !s->e || !f) return CWAL_RC_MISUSE; - else { - int rc = CWAL_RC_OK; - cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS+1] = {0,}; - cwal_value * v; - uint16_t argc = 0; - va_list args; - memset( argv, 0, sizeof(argv) ); - va_start(args, rv); - while( (v=va_arg(args,cwal_value*)) ){ - if(argc>CWAL_OPT_MAX_FUNC_CALL_ARGS){ - rc = CWAL_RC_RANGE; - break; - } - else argv[argc++] = v; - } - va_end(args); - if(CWAL_RC_OK==rc){ - argv[argc] = 0; - rc = cwal_function_call_in_scope( s, f, self, rv, argc, argv ); - } - return rc; - } -} - -int cwal_function_call2( cwal_function * f, - cwal_value * propertyHolder, - cwal_value * self, - cwal_value ** rv, - uint16_t argc, - cwal_value * const * argv ){ - cwal_value * fv = f ? cwal_function_value(f) : NULL; - cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL; - if(!e) return CWAL_RC_MISUSE; - else{ - int rc, rc2 = 0; - cwal_scope _sub = cwal_scope_empty; - cwal_scope * s = &_sub; - rc = cwal_scope_push(e, &s); - if(!rc){ - rc = cwal_function_call_in_scope2( s, f, propertyHolder, - self, rv, argc, argv ); - rc2 = cwal_scope_pop2(e, rc ? 0 : (rv ? *rv : 0)); - } - return rc2 ? rc2 : rc; - } -} - -int cwal_function_call( cwal_function * f, - cwal_value * self, - cwal_value ** rv, - uint16_t argc, - cwal_value * const * argv ){ - return cwal_function_call2( f, 0, self, rv, argc, argv ); -} - -int cwal_function_callv( cwal_function * f, cwal_value * self, - cwal_value ** rv, va_list args ){ - cwal_value * fv = f ? cwal_function_value(f) : NULL; - cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL; - if(!e) return CWAL_RC_MISUSE; - else { - cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS+1] = {0,}; - cwal_value * v; - uint16_t argc = 0; - int rc = 0; - memset( argv, 0, sizeof(argv) ); - while( (v=va_arg(args,cwal_value*)) ){ - if(argc>CWAL_OPT_MAX_FUNC_CALL_ARGS){ - rc = CWAL_RC_RANGE; - break; - } - else argv[argc++] = v; - } - if(rc) return rc; - else{ - argv[argc] = 0; - return cwal_function_call( f, self, rv, argc, argv ); - } - } -} - - -int cwal_function_callf( cwal_function * f, - cwal_value * self, - cwal_value ** rv, - ... ){ - int rc = 0; - va_list args; - va_start(args, rv); - rc = cwal_function_callv( f, self, rv, args ); - va_end(args); - return rc; -} - -cwal_function * cwal_value_get_function( cwal_value const * v ) { - cwal_function * ar = NULL; - cwal_value_fetch_function( v, &ar ); - return ar; -} - -cwal_value * cwal_function_value(cwal_function const * s){ - return CWAL_VALPART(s); -} - -cwal_value * cwal_new_buffer_value(cwal_engine *e, cwal_size_t startingSize){ - cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_BUFFER,0); - if( NULL != v ) - { - cwal_buffer_obj * bo = CWAL_BUFOBJ(v); - cwal_buffer * b; - assert(NULL != bo); - b = &bo->buf; - b->self = bo; - cwal_buffer_wipe_keep_self(b); - assert(bo == b->self); - if(startingSize && - cwal_buffer_reserve(e, b, startingSize)){ - cwal_value_unref2(e, v); - v = NULL; - } - } - return v; -} - -int cwal_buffer_unref(cwal_engine *e, cwal_buffer *v){ - return (e&&v) - ? cwal_value_unref2( e, cwal_buffer_value(v) ) - : CWAL_RC_MISUSE; -} - -int cwal_value_fetch_buffer( cwal_value const * val, cwal_buffer ** x){ - cwal_buffer_obj * bo; - if( ! val ) return CWAL_RC_MISUSE; - else if( !(bo = CWAL_BUFOBJ(val)) ) return CWAL_RC_TYPE; - else{ - if(x) *x = &bo->buf; - return 0; - } -} - -cwal_buffer * cwal_value_get_buffer( cwal_value const * v ) { - cwal_buffer * b = NULL; - cwal_value_fetch_buffer( v, &b ); - return b; -} - -cwal_buffer * cwal_new_buffer(cwal_engine *e, cwal_size_t startingSize){ - return cwal_value_get_buffer(cwal_new_buffer_value(e, startingSize)); -} - -cwal_value * cwal_buffer_value(cwal_buffer const * s){ - if(!s || !s->self) return 0; - else{ - cwal_buffer_obj const * bo = (cwal_buffer_obj const *)s->self; - cwal_value * v = CWAL_VALPART(bo); - if(s->self != CWAL_BUFOBJ(v)){ - assert(!"It seems that that the 'self' member of a buffer got screwed up."); - return 0; - } - if(v && v->vtab && (CWAL_TYPE_BUFFER==v->vtab->typeID)){ - return v; - }else{ - assert(!"It seems that we were passed a non-Value cwal_buffer."); - return NULL; - } - } -} - -cwal_string * cwal_buffer_to_zstring(cwal_engine * e, cwal_buffer * b){ - if(!e || !e->current || !b) return 0; - else if((b->used+1) & ~((cwal_size_t)CWAL_STRLEN_MASK)) return 0 /* too big */; - else{ - cwal_string * s = cwal_new_zstring(e, (char *)b->mem, b->used) - /* reminder: that might cwal_free(e, b->mem) */; - if(!s) return NULL; - else if(s && !CWAL_MEM_IS_BUILTIN(s)){ - /* Re-tweak the metrics which the z-string ctor just - counted. Those bytes were already counted by wherever - buf->mem came from (it might have initially been from, - e.g. cwal_list::list). - - Because b->mem's source is unclear, we cannot subtract - the metric from its origin entry in e->metrics. The - best we can do here is _subtract_ the b->used+1 which - the z-string ctor just added to its metrics, to avoid - double-counting. - - Not perfect, but there it is. - */ - assert(e->metrics.bytes[CWAL_TYPE_ZSTRING] >= b->used+1); - e->metrics.bytes[CWAL_TYPE_ZSTRING] -= b->used+1; - } - cwal_buffer_wipe_keep_self(b); - return s; - } -} - -cwal_value * cwal_buffer_to_zstring_value(cwal_engine * e, cwal_buffer * b){ - return cwal_string_value(cwal_buffer_to_zstring(e,b)); -} - -/** - cwal_value_vtab::destroy_value() impl for Buffer - values. Cleans up self-owned memory, but does not - free self. -*/ -void cwal_value_cleanup_buffer( cwal_engine * e, void * self ){ - cwal_value * v = (cwal_value*)self; - cwal_buffer_obj * bo = CWAL_BUFOBJ(v); - cwal_buffer_reserve(e, &bo->buf, 0); - cwal_cleanup_obase(e, &bo->base, 1); - *bo = cwal_buffer_obj_empty; -} - -void cwal_value_cleanup_exception( cwal_engine * e, void * self ){ - cwal_value * v = (cwal_value*)self; - cwal_exception * f = CWAL_VVPCAST(cwal_exception,v); - cwal_cleanup_obase(e, &f->base, 1); - *f = cwal_exception_empty; -} - -cwal_value * cwal_new_exception_value( cwal_engine * e, int code, cwal_value * msg ){ - cwal_value * v = e - ? cwal_value_new(e, e->current, CWAL_TYPE_EXCEPTION, 0 ) - : NULL; - if(v){ - cwal_value * proto; - cwal_exception * r; - static cwal_size_t codeKeyLen = 0; - static cwal_size_t msgKeyLen = 0; - int rc; - if(!codeKeyLen){ - msgKeyLen = cwal_strlen(CwalConsts.ExceptionMessageKey); - codeKeyLen = cwal_strlen(CwalConsts.ExceptionCodeKey); - } - /* - Reminder: - - i would prefer to have a cwal_exception::message member, but - lifetime of it gets problematic. One solution would be to - move the xscope() operation into cwal_value_vtab, so that we - can generically xscope Exception values without having to - know that they have a free-floating member (not in a - cwal_obase::kvp list). - - (That feature has since been added, by the way.) - */ - r = cwal_value_get_exception(v); - assert(r); - proto = r->base.prototype; - *r = cwal_exception_empty; - r->base.prototype = proto; - r->code = code; - rc = cwal_prop_set(v, CwalConsts.ExceptionCodeKey, - codeKeyLen, - cwal_new_integer(e, (cwal_int_t)code)); - if(!rc && msg) rc = cwal_prop_set(v, - CwalConsts.ExceptionMessageKey, - msgKeyLen, - msg); - if(0!=rc){ - cwal_value_unref2(e, v); - v = 0; - } - } - return v; -} - -cwal_exception * cwal_new_exceptionfv(cwal_engine * e, int code, char const * fmt, va_list args ){ - if(!e) return 0; - else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL); - else{ - cwal_string * s = cwal_new_stringfv( e, fmt, args); - cwal_exception * x; - if(!s) return NULL; - x = cwal_new_exception(e, code, cwal_string_value(s)); - if(!x) cwal_string_unref(s); - return x; - } -} - -cwal_exception * cwal_new_exceptionf(cwal_engine * e, int code, char const * fmt, ...){ - if(!e) return 0; - else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL); - else{ - cwal_exception * x; - va_list args; - va_start(args,fmt); - x = cwal_new_exceptionfv(e, code, fmt, args); - va_end(args); - return x; - } -} - - - -int cwal_exception_unref(cwal_engine *e, cwal_exception *v){ - return (e&&v) - ? cwal_value_unref2( e, cwal_exception_value(v) ) - : CWAL_RC_MISUSE; -} - - -int cwal_value_fetch_exception( cwal_value const * val, cwal_exception ** x){ - if( ! val ) return CWAL_RC_MISUSE; - else if( !cwal_value_is_exception(val) ) return CWAL_RC_TYPE; - else{ - if(x) *x = CWAL_VVPCAST(cwal_exception,val); - return 0; - } -} - -cwal_exception * cwal_value_get_exception( cwal_value const * v ){ - cwal_exception * r = 0; - cwal_value_fetch_exception( v, &r ); - return r; -} - -cwal_exception * cwal_new_exception( cwal_engine * e, int code, cwal_value * msg ){ - cwal_value * v = cwal_new_exception_value(e, code, msg); - return v ? cwal_value_get_exception(v) : NULL; -} - -cwal_value * cwal_exception_value(cwal_exception const * s){ - return s - ? CWAL_VALPART(s) - : NULL; -} - -int cwal_exception_code_get( cwal_exception const * r ){ - return r ? r->code : cwal_exception_empty.code; -} -int cwal_exception_code_set( cwal_exception * r, int code ){ - return r - ? (r->code=code, 0) - : CWAL_RC_MISUSE; -} - -cwal_value * cwal_exception_message_get( cwal_exception const * r ){ - cwal_kvp * const kvp = - cwal_prop_get_kvp( CWAL_VALPART(r), CwalConsts.ExceptionMessageKey, - cwal_strlen(CwalConsts.ExceptionMessageKey), - false, NULL); - return kvp ? kvp->value : NULL; -} - -int cwal_exception_message_set( cwal_engine * e, cwal_exception * r, cwal_value * msg ){ - if(!e || !r) return CWAL_RC_MISUSE; - else return cwal_prop_set( cwal_exception_value(r), - CwalConsts.ExceptionMessageKey, - cwal_strlen(CwalConsts.ExceptionMessageKey), - msg ); -} - - -char * cwal_string_str_rw(cwal_string *v){ - /* - See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a - */ - assert(v && - !CWAL_STR_ISX(v)/* not allowed for x-strings */); - return CWAL_STR_ISZ(v) - ? (char *)*((unsigned char **)(v+1)) - : (CWAL_STRLEN(v) - ? (char *)((unsigned char *)( v+1 )) - : NULL - ); -} - -/** - Intended to be called immediately after initialization of s and - assignment of its string content, and it assert()'s that the - is-ascii flag is no set on s. If the byte length of s equals its - UTF8 length, the is-ascii flag is encoded in s->length, else this - has no side effects. -*/ -static void cwal_string_check_for_ascii( cwal_string * s ){ - unsigned char const * c = (unsigned char const *) cwal_string_cstr(s); - unsigned char const * end = c + CWAL_STRLEN(s); - assert(!CWAL_STR_ISASCII(s)); - assert(c); - assert(c < end); - for( ; c < end; ++c ){ - if(*c & 0x80) return; - } - s->length |= CWAL_STR_ASCII_MASK; -} - -char const * cwal_string_cstr(cwal_string const *v){ -#if 1 - return (NULL == v) - ? NULL - : (CWAL_STR_ISXZ(v) - ? (char const *) *((unsigned char const **)(v+1)) - : (char const *) ((unsigned char const *)(v+1))) - ; -#else - /* - See https://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a - */ - return (NULL == v) - ? NULL - : (CWAL_STRLEN(v) - ? (CWAL_STR_ISXZ(v) - ? (char const *) *((unsigned char const **)(v+1)) - : (char const *) ((unsigned char const *)(v+1))) - : ""); -#endif -} - - -char const * cwal_string_cstr2(cwal_string const *v, cwal_midsize_t * len){ - if(v && len) *len = CWAL_STRLEN(v); - return cwal_string_cstr(v); -} - -void cwal_value_cleanup_string( cwal_engine * e, void * V ){ - cwal_value * v = (cwal_value*)V; - cwal_string * s = cwal_value_get_string(v); - assert(s); - assert(CWAL_STRLEN(s) && "Empty string cannot be cleaned up - it doesn't refcount."); - if(CWAL_MEM_IS_BUILTIN(v)) return; - else if(CWAL_STR_ISZ(s)){ - unsigned char ** pos = (unsigned char **)(s+1); - char * cs = cwal_string_str_rw(s); - cwal_size_t const slen = CWAL_STRLEN(s); - assert(cs == (char *)*pos); - *pos = NULL; - if(e->flags & CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP){ - memset(cs, 0, slen+1/*NUL*/); - } - cwal_memchunk_add(e, cs, slen+1/*NUL*/); - /* cwal_free(e, cs); */ - }else if(CWAL_STR_ISX(s)){ - unsigned char const ** pos = (unsigned char const **)(s+1); -#ifdef DEBUG - char const * cs = cwal_string_cstr(s); - assert(cs == (char *)*pos); -#endif - *pos = NULL; - /* Nothing to free - the bytes are external */ - }else{/* Is a normal string, not an X/Y-string */ - cwal_interned_remove( e, v, 0 ); - if(e->flags & CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP){ - char * cs = cwal_string_str_rw(s); - memset(cs, 0, CWAL_STRLEN(s)); - } - } -} - -int cwal_string_unref(cwal_string * s){ - cwal_value * v = cwal_string_value(s); - return v - ? cwal_value_unref2( cwal_value_engine(v), v ) - : CWAL_RC_MISUSE; -} - -cwal_midsize_t cwal_string_length_bytes( cwal_string const * str ){ - return str - ? CWAL_STRLEN(str) - : 0U; -} - - -cwal_midsize_t cwal_string_length_utf8( cwal_string const * str ){ - return str - ? (CWAL_STR_ISASCII(str) - ? CWAL_STRLEN(str) - : cwal_strlen_utf8( cwal_string_cstr(str), - CWAL_STRLEN(str) ) - ) - : 0U; -} - -bool cwal_string_is_ascii( cwal_string const * str ){ - return str ? CWAL_STR_ISASCII(str) : 0; -} - -cwal_value * cwal_new_string_value(cwal_engine * e, char const * str, cwal_midsize_t len){ - return cwal_string_value( cwal_new_string(e, str, len) ); -} - -bool cwal_cstr_internable_predicate_f_default( void * state, char const * str, cwal_size_t len ){ - if(state || str){/*avoid unused param warning*/} - return !CwalConsts.MaxInternedStringSize - || (len <= CwalConsts.MaxInternedStringSize); -} - -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS -/** - Expects (asserts) char to be in the range [0,127]. Gets the shared - length-1 string for that character and returns it. If it fails, ndx - is of an unexpected value. - - Intended ONLY to be called from cwal_new_string/xstring/zstring() - and ONLY after they have verified that ndx is in range. -*/ -static cwal_string * cwal_len1_ascii_string(int ndx){ - cwal_value * v; - assert(ndx>=0 && ndx<=127); - v = (cwal_value *)CWAL_BUILTIN_VALS.memAsciiPrintable[ndx]; - assert(CWAL_STR(v)); - return CWAL_STR(v); -} -#endif - -cwal_string * cwal_new_string(cwal_engine * e, char const * str, cwal_midsize_t len){ - if(!e || CWAL_STRLEN_TOO_LONG(len)){ - return NULL ; - } - else if( !str || !len ){ - METRICS_REQ_INCR(e,CWAL_TYPE_STRING); - return CWAL_BUILTIN_VALS.sEmptyString; - } -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS - else if( 1==len - && ((unsigned char)*str)<=127 ){ - METRICS_REQ_INCR(e,CWAL_TYPE_STRING); - ++e->metrics.len1StringsSaved[0]; - return cwal_len1_ascii_string((signed char)*str); - } -#endif - else{ - cwal_value * c = 0; - cwal_string * s = 0; - assert(len); - if(CWAL_FEATURE_INTERN_STRINGS & e->flags){ - cwal_interned_search( e, str, len, &c, 0, 0 ); - } - if(c/* Got an interned string... */){ - s = cwal_value_get_string(c); - assert(0 != s); - METRICS_REQ_INCR(e,CWAL_TYPE_STRING); - CWAL_TR_V(e,c); - CWAL_TR3(e,CWAL_TRACE_VALUE_INTERNED, - "re-using interned string"); - assert(c->scope->level <= e->current->level); - } - else{ /* Create new string... */ - c = cwal_value_new(e, e->current, - CWAL_TYPE_STRING, - len); - if( c ){ - char * dest = NULL; - s = CWAL_STR(c); - assert( s ); - *s = cwal_string_empty; - s->length = ((cwal_size_t)CWAL_STRLEN_MASK) & len; - assert(CWAL_STRLEN(s) == len); - dest = cwal_string_str_rw(s) - /* maintenance note: this is the only place in the - library where _writing_ to a normal - (non-X/Z-string) cwal_string is allowed. - */; - assert( (NULL != dest) - && "Empty string should have been caught earlier!"); - { - unsigned char isAscii = 0; - unsigned char const * usrc = (unsigned char const *)str; - unsigned char * udest = (unsigned char *)dest; - unsigned char const * end = udest + len; - for( ; udest < end; ++udest, ++usrc ){ - isAscii |= (*udest = *usrc); - } - *udest = 0; - if(!(isAscii & 0x80)){ - s->length |= CWAL_STR_ASCII_MASK; - } - } - if((CWAL_FEATURE_INTERN_STRINGS & e->flags) - && (e->vtab->interning.is_internable - && e->vtab->interning.is_internable( e->vtab->interning.state, str, len ) - ) - ){ - cwal_interned_insert( e, c ) - /* This insertion effectively controls whether - or not interning of strings is on. If it - fails, the string is effectively not - interned, but otherwise no harm is - done. Allocation of a new interning table - could fail, but that's about the only - conceivable error condition here (and we - can ignore it by not interning). - */; - /* MARKER(("INTERNING rc=%d: %.*s\n", rc, (int)len, str)); */ - } - } - } - return s; - } -} - -cwal_string * cwal_new_xstring(cwal_engine * e, char const * str, - cwal_midsize_t len){ - if(!e || (len & ~((cwal_size_t)CWAL_STRLEN_MASK) /* too big */)){ - return NULL; - }else if( !len ){ - METRICS_REQ_INCR(e,CWAL_TYPE_XSTRING); - return CWAL_BUILTIN_VALS.sEmptyString; - } -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS - else if( 1==len - && ((unsigned char)*str)<=127 ){ - METRICS_REQ_INCR(e,CWAL_TYPE_XSTRING); - ++e->metrics.len1StringsSaved[1]; - return cwal_len1_ascii_string((signed char)*str); - } -#endif - else{ - cwal_value * c = NULL; - cwal_string * s = NULL; - c = cwal_value_new(e, e->current, CWAL_TYPE_XSTRING, 0); - if( c ){ - unsigned char const ** dest; - s = CWAL_STR(c); - assert( NULL != s ); - *s = cwal_string_empty; - s->length = CWAL_STR_XMASK | len; - assert(s->length > len); - assert(CWAL_STRLEN(s) == len); - assert(CWAL_STR_ISX(s)); - assert(CWAL_STR_ISXZ(s)); - dest = (unsigned char const **)(s+1); - *dest = (unsigned char const *)str; - cwal_string_check_for_ascii( s ); - } - return s; - } -} - -cwal_value * cwal_new_xstring_value(cwal_engine * e, char const * str, - cwal_midsize_t len){ - cwal_string * s = cwal_new_xstring(e, str, len); - return s ? cwal_string_value(s) : NULL; -} - -cwal_string * cwal_new_zstring(cwal_engine * e, char * str, cwal_midsize_t len){ - if(!e || (len & ~((cwal_size_t)CWAL_STRLEN_MASK) /* too big */)){ - return NULL; - }else if(!str){ - METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING); - return CWAL_BUILTIN_VALS.sEmptyString; - } - else if(!len){ - /* Special case: free source memory immediately. */ - METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING); - cwal_free(e, str); - return CWAL_BUILTIN_VALS.sEmptyString; - } -#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS - else if( 1==len - && ((unsigned char)*str)<=127 ){ - /* Special case: free source memory immediately. */ - cwal_string * rc = cwal_len1_ascii_string((signed char)*str); - assert(rc && 1==CWAL_STRLEN(rc)); - METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING); - cwal_free(e, str); - ++e->metrics.len1StringsSaved[2]; - return rc; - } -#endif - else{ - cwal_value * c = NULL; - cwal_string * s = NULL; - assert(len>0); - c = cwal_value_new(e, e->current, CWAL_TYPE_ZSTRING, 0); - if( c ){ - unsigned char ** dest; - s = CWAL_STR(c); - assert( NULL != s ); - *s = cwal_string_empty; - s->length = CWAL_STR_ZMASK | len; - e->metrics.bytes[CWAL_TYPE_ZSTRING] += len +1 - /* we're going to assume a NUL byte for - metrics purposes, because there essentially - always is one for z-strings. */; - assert(s->length > len); - assert(CWAL_STRLEN(s) == len); - assert(CWAL_STR_ISZ(s)); - assert(CWAL_STR_ISXZ(s)); - dest = (unsigned char **)(s+1); - *dest = (unsigned char *)str; - cwal_string_check_for_ascii( s ); - }else{ - /* See the API docs for why we do this. */ - cwal_free2( e, str, len/*+1 would be safe, until it wasn't.*/ ); - } - return s; - } -} - -cwal_value * cwal_new_zstring_value(cwal_engine * e, char * str, cwal_midsize_t len){ - cwal_string * s = cwal_new_zstring(e, str, len); - return s ? cwal_string_value(s) : NULL; -} - - -int cwal_buffer_reset( cwal_buffer * b ){ - if(!b) return CWAL_RC_MISUSE; - else{ - if(b->capacity){ - assert(b->mem); - b->mem[0] = 0; - } - b->used = 0; - return 0; - } -} - -int cwal_buffer_resize( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ){ - if( !buf ) return CWAL_RC_MISUSE; - else if(n && (buf->capacity == n+1)){ - buf->used = n; - buf->mem[n] = 0; - return 0; - }else{ - unsigned char * x = (unsigned char *)cwal_realloc( e, buf->mem, - n+1/*NUL*/ ); - if( ! x ) return CWAL_RC_OOM; - if(n > buf->capacity){ - /* zero-fill new parts */ - memset( x + buf->capacity, 0, n - buf->capacity +1/*NUL*/ ); - } - /* reminder to self: e->metrics.bytes[CWAL_TYPE_BUFFER] might be - 0 here because buf->mem might have come from a recycler. That - means we're not byte-counting buffer resize(), which is a bit - disturbing. We could measure it if over-allocation is on, but - we don't know which pool (if any) to modify the count in. e.g. - buf->mem might have come from the recycler after having been - allocated as a cwal_list::list. So... hmmm. - */ - /* assert(e->metrics.bytes[CWAL_TYPE_BUFFER]) >= buf->capacity; */ - /* e->metrics.bytes[CWAL_TYPE_BUFFER] -= buf->capacity; */ - buf->capacity = n + 1 /*NUL*/; - /* e->metrics.bytes[CWAL_TYPE_BUFFER] += buf->capacity; */ - buf->used = n; - buf->mem = x; - buf->mem[buf->used] = 0; - return 0; - } -} - - -cwal_string * cwal_new_stringfv(cwal_engine * e, char const * fmt, va_list args ){ - if(!e || !fmt) return 0; - else if(!*fmt) return cwal_new_string(e,"",0); - else{ - int rc; - cwal_size_t const oldUsed = e->buffer.used; - cwal_size_t slen; - rc = cwal_buffer_printfv(e, &e->buffer, fmt, args); - slen = e->buffer.used - oldUsed; - e->buffer.used = oldUsed; - return rc - ? NULL - : cwal_new_string(e, (char const*)(e->buffer.mem+oldUsed), slen); - ; - } -} - -cwal_string * cwal_new_stringf(cwal_engine * e, char const * fmt, ...){ - if(!e || !fmt) return 0; - else if(!*fmt) return cwal_new_string(e,NULL,0); - else{ - cwal_string * str; - va_list args; - va_start(args,fmt); - str = cwal_new_stringfv(e, fmt, args); - va_end(args); - return str; - } -} - - -cwal_value * cwal_string_value(cwal_string const * s){ - return s - ? (CWAL_STRLEN(s) - ? CWAL_VALPART(s) - : CWAL_BUILTIN_VALS.vEmptyString) - : NULL; -} - -cwal_engine * cwal_value_engine( cwal_value const * v ){ - return (v && v->scope) - ? v->scope->e - : 0; -} - -cwal_scope * cwal_value_scope( cwal_value const * v ){ - return v ? v->scope : NULL; -} - -cwal_value * cwal_string_concat( cwal_string const * s1, cwal_string const * s2 ){ - if(!s1 || !s2) return NULL; - else { - cwal_size_t newLen; - int rc; - cwal_engine * e = cwal_value_engine(cwal_string_value(s1)); - assert(e); - newLen = CWAL_STRLEN(s1) + CWAL_STRLEN(s2) + 1/*NUL byte*/; - if( CWAL_STRLEN_TOO_LONG(newLen) ) return NULL; - rc = cwal_buffer_reserve( e, &e->buffer, newLen ); - if(rc) return NULL; - e->buffer.used = 0; - rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s1), CWAL_STRLEN(s1) ); - if(rc) return NULL; - rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s2), CWAL_STRLEN(s2) ); - if(rc) return NULL; - e->buffer.mem[e->buffer.used] = 0; - return cwal_new_string_value( e, (char const *)e->buffer.mem, e->buffer.used ); - } -} - - -int cwal_value_fetch_bool( cwal_value const * val, char * v ) -{ - /** - FIXME: move the to-bool operation into cwal_value_vtab, like we - do in the C++ API. - */ - if( ! val || !val->vtab ) return CWAL_RC_MISUSE; - else - { - int rc = 0; - char b = NULL != CWAL_VOBASE(val); - if(!b) switch( val->vtab->typeID ){ - case CWAL_TYPE_BUFFER: - b = 1; - break; - case CWAL_TYPE_STRING: { - char const * str = cwal_string_cstr(cwal_value_get_string(val)); - b = (str && *str) ? 1 : 0; - break; - } - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - break; - case CWAL_TYPE_BOOL: - b = CWAL_BOOL(val); - break; - case CWAL_TYPE_INTEGER: { - cwal_int_t i = 0; - cwal_value_fetch_integer( val, &i ); - b = i ? 1 : 0; - break; - } - case CWAL_TYPE_DOUBLE: { - cwal_double_t d = 0.0; - cwal_value_fetch_double( val, &d ); - b = (0.0==d) ? 0 : 1; - break; - } - case CWAL_TYPE_UNIQUE: - b = 1; - break; - case CWAL_TYPE_TUPLE: - b = CWAL_TUPLE(val)->n ? 1 : 0; - break; - default: - rc = CWAL_RC_TYPE; - break; - } - if( !rc && v ) *v = b; - return rc; - } -} - -bool cwal_value_get_bool( cwal_value const * val ) -{ - char i = 0; - cwal_value_fetch_bool( val, &i ); - return i; -} - -int cwal_value_fetch_integer( cwal_value const * val, cwal_int_t * v ) -{ - if( ! val || !val->vtab ) return CWAL_RC_MISUSE; - else { - cwal_int_t i = 0; - int rc = 0; - switch(val->vtab->typeID){ - case CWAL_TYPE_UNIQUE: - case CWAL_TYPE_TUPLE: - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - i = 0; - break; - case CWAL_TYPE_BOOL:{ - char b = 0; - cwal_value_fetch_bool( val, &b ); - i = b; - break; - } - case CWAL_TYPE_INTEGER: { - i = *CWAL_INT(val); - break; - } - case CWAL_TYPE_DOUBLE:{ - cwal_double_t d = 0.0; - cwal_value_fetch_double( val, &d ); - i = (cwal_int_t)d; - break; - } - case CWAL_TYPE_STRING: - rc = cwal_string_to_int( cwal_value_get_string(val), - &i ); - break; - default: - rc = CWAL_RC_TYPE; - break; - } - if(!rc && v) *v = i; - return rc; - } -} - -cwal_int_t cwal_value_get_integer( cwal_value const * val ) -{ - cwal_int_t i = 0; - cwal_value_fetch_integer( val, &i ); - return i; -} - -int cwal_value_fetch_double( cwal_value const * val, cwal_double_t * v ) -{ - if( ! val || !val->vtab ) return CWAL_RC_MISUSE; - else - { - cwal_double_t d = 0.0; - int rc = 0; - switch(val->vtab->typeID) - { - case CWAL_TYPE_UNIQUE: - case CWAL_TYPE_TUPLE: - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - d = 0; - break; - case CWAL_TYPE_BOOL: { - char b = 0; - cwal_value_fetch_bool( val, &b ); - d = b ? 1.0 : 0.0; - break; - } - case CWAL_TYPE_INTEGER: { - cwal_int_t i = 0; - cwal_value_fetch_integer( val, &i ); - d = i; - break; - } - case CWAL_TYPE_DOUBLE: - memcpy(&d, CWAL_DBL_NONULL(val), sizeof(cwal_double_t)); - break; - case CWAL_TYPE_STRING: - rc = cwal_string_to_double( cwal_value_get_string(val), - &d ); - break; - default: - rc = CWAL_RC_TYPE; - break; - } - if(!rc && v) *v = d; - return rc; - } -} - -cwal_double_t cwal_value_get_double( cwal_value const * val ) -{ - cwal_double_t i = 0.0; - cwal_value_fetch_double( val, &i ); - return i; -} - -int cwal_value_fetch_string( cwal_value const * val, cwal_string ** dest ) -{ - if( ! val || ! dest ) return CWAL_RC_MISUSE; - else if( ! cwal_value_is_string(val) ) return CWAL_RC_TYPE; - else - { - if( dest ) *dest = CWAL_STR(val); - return CWAL_RC_OK; - } -} - -cwal_string * cwal_value_get_string( cwal_value const * val ) -{ - cwal_string * rc = NULL; - cwal_value_fetch_string( val, &rc ); - return rc; -} - -char const * cwal_value_get_cstr( cwal_value const * val, cwal_size_t * len ) -{ - switch(val ? val->vtab->typeID : 0){ - case CWAL_TYPE_STRING:{ - cwal_string const * s = cwal_value_get_string(val); - if(len) *len = CWAL_STRLEN(s); - return cwal_string_cstr(s); - } - case CWAL_TYPE_BUFFER:{ - cwal_buffer const * b = cwal_value_get_buffer(val); - if(len) *len = b->used; - return (char const *)b->mem; - } - default: - return NULL; - } -} - -int cwal_value_fetch_array( cwal_value const * val, cwal_array ** ar) -{ - if( ! val ) return CWAL_RC_MISUSE; - else if( !cwal_value_is_array(val) ) return CWAL_RC_TYPE; - else - { - if(ar) *ar = CWAL_ARRAY(val); - return 0; - } -} - -cwal_array * cwal_value_get_array( cwal_value const * v ) -{ - cwal_array * ar = NULL; - cwal_value_fetch_array( v, &ar ); - return ar; -} - -cwal_value * cwal_array_value(cwal_array const * s) -{ - return s - ? CWAL_VALPART(s) - : NULL; -} - -int cwal_array_unref(cwal_array *x) -{ - cwal_value * v = CWAL_VALPART(x); - return (v && v->scope) - ? cwal_value_unref2(v->scope->e, v) - : CWAL_RC_MISUSE; -} - -int cwal_array_value_fetch( cwal_array const * ar, cwal_size_t pos, cwal_value ** v ) -{ - if( !ar) return CWAL_RC_MISUSE; - if( pos >= ar->list.count ) return CWAL_RC_RANGE; - else - { - if(v) *v = (cwal_value*)ar->list.list[pos]; - return 0; - } -} - -cwal_value * cwal_array_get( cwal_array const * ar, cwal_midsize_t pos ) -{ - cwal_value *v = NULL; - cwal_array_value_fetch(ar, pos, &v); - return v; -} - -cwal_value * cwal_array_take( cwal_array * ar, cwal_size_t pos ) -{ - cwal_value *v = NULL; - cwal_array_value_fetch(ar, pos, &v); - if(v){ - if(CWAL_MEM_IS_BUILTIN(v)){ - cwal_array_set(ar, pos, NULL); - }else{ - cwal_value_ref(v); - cwal_array_set(ar, pos, NULL); - cwal_value_unhand(v); - assert(v->scope && "Already dead?"); - } - } - return v; -} - - -int cwal_array_length_fetch( cwal_array const * ar, cwal_midsize_t * v ) -{ - if( ! ar || !v ) return CWAL_RC_MISUSE; - else{ - if(v) *v = ar->list.count; - return 0; - } -} - -cwal_midsize_t cwal_array_length_get( cwal_array const * ar ) -{ - cwal_midsize_t i = 0; - cwal_array_length_fetch(ar, &i); - return i; -} - -int cwal_array_length_set( cwal_array * ar, cwal_midsize_t newSize ){ - cwal_midsize_t i; - if(!ar) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if(ar->list.count == newSize) return 0; - if( newSize < ar->list.count ){ - int rc = 0; - for( i = newSize; !rc && (i < ar->list.count); ++i ){ - rc = cwal_array_set( ar, i, NULL ); - } - ar->list.count = newSize; - return rc; - } - else { /* grow */ - int const rc = cwal_array_reserve( ar, newSize ); - if(!rc){ - ar->list.count = newSize; - } - return rc; - } -} - - -/** - Internal helper for recycling array list memory. li must be a new, - clean list with no memory (that might get assert()ed). If the - recyling list has an entry then that entry's memory is transfered - into li. If no entry is capable of holding it, li is left - unmolested. There are no error conditions except for precondition - violations (assertions). If minCount is not 0 then only a recycled - chunk with enough space for at least that many entries will serve - the request. -*/ -static void cwal_list_from_recycler( cwal_engine * e, cwal_list * li, - cwal_size_t minCount ); - -/** - Internal helper macro for array-centric functions. -*/ -#define SETUP_ARRAY_ARGS \ - cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0; \ - cwal_engine * e = s ? s->e : 0; \ - if(!s || !e) return CWAL_RC_MISUSE - -int cwal_array_reserve( cwal_array * ar, cwal_midsize_t size ) -{ - SETUP_ARRAY_ARGS; - if( ! ar ) return CWAL_RC_MISUSE; - else if( size <= ar->list.alloced ) - { - /* We don't want to introduce a can of worms by trying to - handle the cleanup from here. - */ - return 0; - } -#if 0 - else if(!ar->list.list){ - cwal_list_from_recycler(e, &ar->list, size); - if(ar->list.list){ - assert(ar->list.alloced>=size); - assert(NULL == ar->list.list[0]); - assert(size ? (NULL == ar->list.list[size-1]) : 1); - return 0; - } - } -#endif - else{ - CWAL_UNUSED_VAR cwal_size_t const oldLen = ar->list.alloced; - cwal_size_t rrc; - rrc = cwal_list_reserve( e, &ar->list, size ); - if(rrc < size) return CWAL_RC_OOM; - else{ - assert(rrc > oldLen); - return 0; - } - } -} - - -/** @internal - - cwal_list_visitor_f which expects V to be a (cwal_value*) and - and VParent to be its (cwal_value*) scoping parent. - This makes sure that (sub)child are properly up-scoped - if needed. Returns 0 on success. -*/ -static int cwal_xscope_visitor_children_array( void * V, void * VParent ){ - cwal_value * par = (cwal_value*)VParent; - cwal_value * child = (cwal_value*)V; - assert(par && par->scope); - return cwal_value_xscope( par->scope->e, par->scope, child, 0 ); -} - -static void cwal_htable_rescope(cwal_scope * const sc, - cwal_htable * const h){ - /* cwal_dump_v(nv,"Re-scoping cwal_htable children..."); */ - int rc = 0; - cwal_midsize_t const max = h->list.alloced>=h->hashSize - ? h->hashSize : h->list.alloced; - for( cwal_midsize_t i = 0; !rc && (i < max); ++i ){ - cwal_kvp * kvp = (cwal_kvp*)h->list.list[i]; - if(!kvp) continue; - cwal_kvp * next = NULL; - for( ; !rc && kvp; kvp = next){ - next = kvp->right; - assert(kvp->key); - assert(kvp->value); - rc = cwal_value_xscope(sc->e, sc, kvp->key, 0); - if(!rc && kvp->key != kvp->value){ - rc = cwal_value_xscope(sc->e, sc, kvp->value, 0); - } - /* cwal_dump_v(kvp->key,"Re-scoped key"); */ - /* cwal_dump_v(kvp->value,"Re-scoped value"); */ - } - assert(!rc && "Rescoping failure is no longer be possible " - "except in the case of memory corruption."); - } -} - -int cwal_rescope_children_obase( cwal_value * v ){ - cwal_obase * const b = CWAL_VOBASE(v); - int rc = CWAL_RC_OK; - assert(b); - assert(CWAL_V_IS_RESCOPING(v)); - assert(v->scope); -#if CWAL_OBASE_ISA_HASH - cwal_htable_rescope(v->scope, &b->hprops); -#else - cwal_obase_kvp_iter iter; - cwal_kvp const * kvp = cwal_obase_kvp_iter_init(v, &iter); - for( ; kvp && (0==rc); kvp = cwal_obase_kvp_iter_next(&iter) ){ - if(kvp->key){ - rc = cwal_value_xscope( v->scope->e, v->scope, kvp->key, 0 ); - } - if((0==rc) && kvp->value){ - rc = cwal_value_xscope( v->scope->e, v->scope, kvp->value, 0 ); - } - } - if(rc){ - assert(!"Rescoping failure should no longer be possible."); - } -#endif - return rc; -} - -int cwal_rescope_children_native( cwal_value * v ){ - int rc; - cwal_native * n = cwal_value_get_native(v); - assert(v->scope); - assert(n); - rc = cwal_rescope_children_obase(v); - if(!rc && n->rescoper){ - rc = n->rescoper( v->scope, v ); - } - return rc; -} - -int cwal_rescope_children_function( cwal_value * v ){ - int rc; - cwal_function * f = cwal_value_get_function(v); - assert(v->scope); - assert(f); - assert(CWAL_V_IS_RESCOPING(v)); - rc = cwal_rescope_children_obase(v); - if(!rc && f->rescoper){ - rc = f->rescoper( v->scope, v ); - } - return rc; -} - -int cwal_rescope_children_unique( cwal_value * v ){ - cwal_value * ch = *CWAL_UNIQUE_VALPP(v); - int rc = 0; - assert(v->scope); - if(ch){ - *CWAL_UNIQUE_VALPP(v) = 0 - /* a poor man's recursion-prevention scheme. */; - rc = cwal_value_rescope(v->scope, ch); - - *CWAL_UNIQUE_VALPP(v) = ch; - } - return rc; -} - -int cwal_rescope_children_tuple( cwal_value * v ){ - cwal_tuple * p = CWAL_TUPLE(v); - cwal_size_t i; - cwal_value * ch; - int rc = 0; - assert(!CWAL_MEM_IS_BUILTIN(v)); - assert(p->n || !p->list /* this gets called once from cwal_value_new() */); - assert(v->scope); - for( i = 0; !rc && i < p->n; ++i ){ - if( (ch = p->list[i]) ){ - rc = cwal_value_xscope(v->scope->e, v->scope, ch, 0); - } - } - return rc; -} - -int cwal_rescope_children_array( cwal_value * v ){ - int rc; - cwal_array * ar = cwal_value_get_array(v); - assert(ar); - assert(CWAL_V_IS_RESCOPING(v)); - rc = cwal_rescope_children_obase( v ); - if(rc) return rc; - rc = cwal_list_visit( &ar->list, - -1, cwal_xscope_visitor_children_array, v ); - return rc; -} - -int cwal_value_rescope( cwal_scope * s, cwal_value * v ){ - return (!s || !s->e) - ? CWAL_RC_MISUSE - : ((v && CWAL_MEM_IS_BUILTIN(v)) - ? 0 - : cwal_value_xscope( s->e, s, v, NULL ) ); -} - -/** - Transfers child to the given scope if child is in a lower-level (newer) - scope. - - Returns 0 on success and theoretically has no error cases except - bad arguments or as side-effects of serious internal errors - elsewhere. If res is not NULL, it will be set to one of these values: - - -1=child was moved to a higher-level scope (with a lower - scope->level value). - - 0=child was kept where it is. - - 1=child was... hmm... damn, i should have written the docs as i wrote - the code :/. - - It may set e->fatalCode, in which case it returns that. -*/ -static int cwal_value_xscope( cwal_engine * e, cwal_scope * par, - cwal_value * child, int * res ){ - cwal_obase * chb; - int RC = res ? *res : 0; - if(!res) res = &RC/*simplifies some code below*/; - if(!par) { - *res = 1; - return 0;/*par = e->current;*/ - } - assert( e && par && child ); - start: - if( CWAL_MEM_IS_BUILTIN(child) ) { - *res = 1; - return CWAL_RC_OK; - } - else if(child->scope == par) { - *res = 0; - return CWAL_RC_OK; - } - chb = CWAL_VOBASE(child); - assert(chb ? !CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_DESTRUCTING) : 1); - if(CWAL_V_IS_RESCOPING(child)){ - *res = 0; - assert(child->scope->level <= par->level); - /* MARKER(("Skipping re-rescoping.\n")); */ - /* haven't yet seen this happen! */ - return 0; - } - else -#if 0 - /* - 20160206: what was this for, way back when? This block IS being - triggered via s2 unit tests, but i'm curious what this is - supposed to accomplish. Seems to work fine without it, but - probably only because child->scope->level is always < par->level - in this case. And yet i'm not certain why! - - Was it for breaking cycles? The children-rescopers don't - set the CWAL_F_IS_VISITING flag. Maybe they used to? - - i think i see now... we've got a complex call chain which is - trying to upscope a child, but that child is currently being - visited. Failing to upscope would be an error, but continuing - from here would lead to an assertion later (and we'd be unable - to catch cycles). We need a flag for "is rescoping." - */ - if( chb && ( CWAL_F_IS_VISITING & chb->flags ) ){ - /* dump_val(child,"is visiting?"); */ - assert(child->scope->level <= par->level) - /* This assertion is triggered in at least 1 s2 unit test - script. Troubling. - */; - *res = 0; - return 0 - /* Normally we would return CWAL_RC_CYCLES_DETECTED, - but for this special case we need to return 0 - to keep list iteration from aborting. */; - } - else -#endif - { - int rc = CWAL_RC_OK; - if( child->scope ){ - CWAL_TR_V(e,child); - if( child->scope->level <= par->level ){ - CWAL_TR3(e,CWAL_TRACE_MESSAGE, - "Keeping value in its current scope."); - *res = 1; - return 0; - } - else{ - CWAL_TR3(e,CWAL_TRACE_MESSAGE, - "Migrating value to older scope."); - rc = cwal_value_take( e, child ); - if(rc){ - CWAL_TR_SV(e,child->scope,child); - CWAL_TR3(e,CWAL_TRACE_ERROR, - "POPPING FROM ITS PARENT SCOPE IS NO " - "LONGER SUPPOSED TO BE ABLE TO FAIL " - "IN THESE CONDITIONS."); - assert(!"Pop child from its scope failed."); - return e->fatalCode = rc; - } - } - } - assert(!child->scope); - *res = -1; - if( cwal_scope_insert( par, child ) ){ - assert(e->fatalCode); - return e->fatalCode; - } - if(child->vtab->rescope_children){ - /* For containers we now, for the sake of cross-scope - cycles, we need to ensure that any sub-(sub...)children - are up-scoped. - */ - if(!chb){ - assert(CWAL_TYPE_UNIQUE==child->vtab->typeID - || CWAL_TYPE_TUPLE==child->vtab->typeID); - /* - Doh... we have a potentially problem: we can - potentially endlessly cycle on cwal_uniques. They - have no flags which will let us stop recursion! - Does that matter, since the value being wrapped has - them (if a container, else it's moot)? In the - non-container case, the 2nd rescope would not - recurse because the scope level will have already - been adjusted. Whew. - */ - /* dump_val(child,"Has rescope_children but no obase!?!?!?"); */ - } - assert(!CWAL_V_IS_RESCOPING(child)); - CWAL_RCFLAG_ON(child,CWAL_RCF_IS_RESCOPING); - assert(CWAL_V_IS_RESCOPING(child)); - rc = child->vtab->rescope_children(child); - assert(CWAL_V_IS_RESCOPING(child)); - CWAL_RCFLAG_OFF(child,CWAL_RCF_IS_RESCOPING); - assert(!CWAL_V_IS_RESCOPING(child)); - if(0!=rc){ - /* Reminder: now that values are tracked in linked - lists, xscope can only fail if some assertion - fails. There is no longer the potential for an OOM - case. - */ - MARKER(("xscope returned %d/%s\n", rc, cwal_rc_cstr(rc))); - assert(!"NO RECOVERY STRATEGY HERE!"); - return e->fatalCode = rc; - } - } - assert(par == child->scope); - if(chb && chb->prototype && chb->prototype->scope - && chb->prototype->scope->level > par->level){ - /* - Added 20141205 when it suddenly occurred to me that we - do not otherwise make prototypes vacuum-safe (not that i - could find, anyway). Seems to have never been necessary - before. - */ - /* MARKER(("Rescoping my prototype! Why does this never need to happen?\n")); */ - child = chb->prototype; - res = &RC /* keep original res result in place */; - goto start; - } - return rc; - } -} - -int cwal_value_upscope( cwal_value * v ){ - cwal_engine * e = CWAL_VENGINE(v); - if(!e || !e->current || !v->scope) return CWAL_MEM_IS_BUILTIN(v) ? 0 : CWAL_RC_MISUSE; - else if(!e->current->parent){ - assert(1==e->current->level); - assert(e->current == v->scope); - return 0; - } - else if(e->current->parent == v->scope) return 0; - else { - int rc, dir = 0; - rc = cwal_value_xscope( e, e->current->parent, v, &dir); - assert(0==rc); - return rc; - } -} - - -cwal_value * cwal_propagating_get( cwal_engine * e ){ - return e->values.propagating; -} - -cwal_value * cwal_propagating_set( cwal_engine * e, cwal_value * v ){ - if(v != e->values.propagating){ - if(v) cwal_value_ref(v); - if(e->values.propagating){ - cwal_value_unref(e->values.propagating); - } - e->values.propagating = v; - } - return v; -} - -cwal_value * cwal_propagating_take( cwal_engine * e ){ - cwal_value * rv = e->values.propagating; - if(rv){ - cwal_value_ref(rv); - cwal_propagating_set(e, 0); - cwal_value_unhand(rv); - } - return rv; -} - - -int cwal_exception_set( cwal_engine * e, cwal_value * v ){ - if(!e || !e->current) return CWAL_RC_MISUSE; - else if(v && (v == e->values.exception)) /* This is ok. */ return CWAL_RC_EXCEPTION; - else if(!v) { -#if 0 - if(e->values.exception - && e->values.exception->scope==e->current - && 1==CWAL_REFCOUNT(e->values.exception)){ - /* Should we really be doing this? Assuming the scope - reference is the only ref? Previous attempts to do this - have bitten me in the buttox, but i "think" i can get away - with it for exceptions. - - Note that this routine does NOT add a ref to v, but instead - of taking back our own ref we're stealing that of the owning - scope. - - Nonono... this could open up cases where an exception is - moved around and set/unset as the exception state multiple - times, and we'd unref it too many times. So we'll only do - this "optimization" to when the scopes match and - 1==refcount, which we can fairly safely assume is the - scope-held ref unless the client has done something silly, - like... set exception state, move it into an object owned - by the same scope, unref the exception (refcount==1), set - the object as the exception state... blammo. Okay, turn - this off but leave the commentary as yet another reminder - of why we don't just generically unref values. - */ - cwal_value_unref2(e, e->values.exception); - } -#endif - if(e->values.exception) cwal_value_unref(e->values.exception); - e->values.exception = 0 /* its scope owns it */; - return 0; - } - else{ - cwal_value_ref(v); - if(e->values.exception) cwal_value_unref(e->values.exception); - e->values.exception = v; - /* cwal_value_ref(v); */ - return CWAL_RC_EXCEPTION; - } -} - -int cwal_exception_setfv(cwal_engine * e, int code, char const * fmt, va_list args){ - if(!e) return CWAL_RC_MISUSE; - else{ - int rc; - switch(code){ - case CWAL_RC_OOM: rc = code; - break; - default: { - cwal_exception * x; - x = (fmt && *fmt) - ? cwal_new_exceptionfv(e, code, fmt, args) - : cwal_new_exception(e, code, NULL); - if(!x) rc = CWAL_RC_OOM; - else{ - cwal_value * xv = cwal_exception_value(x); - cwal_value_ref(xv); - rc = cwal_exception_set( e, xv ); - cwal_value_unref(xv); - assert(0!=rc); - } - break; - } - } - return rc; - } -} -int cwal_exception_setf(cwal_engine * e, int code, char const * fmt, ...){ - if(!e || !fmt) return 0; - else{ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_exception_setfv(e, code, fmt, args); - va_end(args); - return rc; - } -} - -cwal_value * cwal_exception_get( cwal_engine * e ){ - return e ? e->values.exception : 0; -} - -#if 0 -/* keeping around for a look at its heurists later on. */ -/** - Internal helper for recycling buffer memory. dest must be a new, - clean buffer with no memory (that might get assert()ed). If the - recyling list has an entry which can serve at least forAllocSize - bytes, that entry's memory is transfered into dest. If no entry is - capable of holding it, dest->mem will still be 0 after - returning. There are no error conditions except for precondition - violations (assertions). - -*/ -/* static */ void cwal_buffer_steal_mem( cwal_engine * e, cwal_buffer * dest, - cwal_size_t forAllocSize){ -#if 0 - /*Enable this section to effectively disable buffer->mem recycling - for memory cost/savings comparisons. */ - return; -#else - assert(!dest->mem); - assert(!dest->used); - if(dest->mem || e->reBuf.cursor<0) return; - else{ - int i = e->reBuf.cursor; - /* Potential TODO: find the closest-(but >=)-fit entry */ - for( ; i < (int)(sizeof(e->reBuf.buffers)/sizeof(e->reBuf.buffers[0])); - ++i ){ - cwal_buffer * br = &e->reBuf.buffers[i]; - if(br->mem - /* Try an approximate fit... */ - && br->capacity>=forAllocSize -#if 1 - /* This heuristic is very basic, of course. */ - && (br->capacity <= 64 * CWAL_SIZE_T_BITS /* 1k, 2k, 4k */ - || br->capacity<= 2 * forAllocSize - ) -#endif - ){ - *dest = *br; - /* MARKER(("Re-using buffer memory (%"CWAL_SIZE_T_PFMT") from slot #%d\n", br->capacity, e->reBuf.cursor)); */ - if(e->reBuf.cursor != i){ - /* Move the final buffer in the list to this slot, - so that our list is always contiguous. */ - *br = e->reBuf.buffers[e->reBuf.cursor]; - e->reBuf.buffers[e->reBuf.cursor] = cwal_buffer_empty; - }else{ - *br = cwal_buffer_empty; - } - --e->reBuf.cursor; - break; - } - } - } -#endif -} -#endif - -void cwal_list_from_recycler( cwal_engine * e, cwal_list * list, - cwal_size_t minCount ){ -#if 0 - /*Enable this section to effectively disable array->list recycling - for memory cost/savings comparisons. */ - return; -#else - if(list->list) return; - else{ - void * mem; - cwal_size_t reqSize = minCount * sizeof(void*); - assert(!list->alloced); - if( (mem = cwal_memchunk_request(e, &reqSize, 1000, - minCount - ? "cwal_list_from_recycler(min)" - : "cwal_list_from_recycler(0)" - ))){ - assert(reqSize>= minCount * sizeof(void*)); - list->list = (void **)mem; - list->alloced = reqSize/sizeof(void*) - /* Yes, we might be losing a few bytes here. The alternatives - include: - - (A) Put it back and try for another (aligned) one - (or give up). - - (B) Add a flag to cwal_memchunk_request() which specifies - we need it aligned. - - (C) Make cwal_re/alloc() always align up and rely on that - in the recycler? The culprit is really cwal_buffer_reserve(), - so that would be the one to patch. - - (D) Something different? - */ - ; - assert(list->alloced >= minCount); - /*MARKER(("Reused array list memory: %u entries from %u bytes\n", - (unsigned)list->alloced, (unsigned)reqSize));*/ - } - } -#endif -} - -int cwal_array_set( cwal_array * const ar, cwal_midsize_t ndx, cwal_value * const v ) -{ - SETUP_ARRAY_ARGS; - if( !ar ) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if( (ndx+1) > CwalConsts.MaxSizeTCounter) /* overflow */return CWAL_RC_RANGE; - else{ - cwal_size_t len; - len = ar->list.alloced; - if(len <= ndx){ - len = cwal_list_reserve( e, &ar->list, - (ndxlist.list[ndx]; - assert( arV && (CWAL_TYPE_ARRAY==arV->vtab->typeID) ); - if( old ){ - if(old == v) return 0; - } - if(v){ - rc = cwal_value_xscope( e, arV->scope, v, 0 ); - if(rc){ - assert(!"WARNING: xscope failed! " - "THIS IS SUPPOSED TO BE IMPOSSIBLE :(\n"); - return rc; - } - cwal_value_ref2( e, v ); - } - if(old) cwal_value_unref2(e, old); - ar->list.list[ndx] = v; - if( ndx >= ar->list.count ){ - ar->list.count = ndx+1; - } - return 0; - } - } -} - -int cwal_array_append( cwal_array * const ar, cwal_value * const v ){ - SETUP_ARRAY_ARGS; - if( !ar ) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if( (ar->list.count+1) < ar->list.count ) return CWAL_RC_RANGE; - else{ - if(!ar->list.list) cwal_list_from_recycler(e, &ar->list, 0); - if( !ar->list.alloced - || (ar->list.count == ar->list.alloced-1)){ - unsigned const int n = ar->list.count - ? ar->list.alloced * 2 - : CwalConsts.InitialArrayLength; - if( n > cwal_list_reserve( e, &ar->list, n ) ){ - return CWAL_RC_OOM; - } - } - return cwal_array_set( ar, ar->list.count, v ); - } -} - -int cwal_array_prepend( cwal_array * const ar, cwal_value * const v ){ - SETUP_ARRAY_ARGS; - if( !ar || !v ) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else{ - int rc; - cwal_value ** vlist; - if(!ar->list.list) cwal_list_from_recycler(e, &ar->list, 1); - if( !ar->list.alloced - || (ar->list.count == ar->list.alloced-1)){ - unsigned const int n = ar->list.count - ? ar->list.alloced * 2 - : CwalConsts.InitialArrayLength; - if( n > cwal_list_reserve( e, &ar->list, n ) ){ - return CWAL_RC_OOM; - } - } - if(ar->list.count){ - unsigned char * mem = - (unsigned char *)ar->list.list; - memmove( mem+sizeof(cwal_value*), mem, - sizeof(cwal_value*)*ar->list.count); - } - vlist = (cwal_value **)ar->list.list; - vlist[0] = NULL; - ++ar->list.count; - rc = cwal_array_set( ar, 0, v ); - /* A recovery here on error would be messy, but the - set() can only fail on allocation error and we've - already done the allocation. - */ - assert(!rc); - assert(v == *((cwal_value**)ar->list.list)); - return rc; - } -} - -int cwal_array_shift( cwal_array * ar, cwal_value **rv ){ - SETUP_ARRAY_ARGS; - if( !ar ) return CWAL_RC_MISUSE; - else{ - cwal_value ** vlist; - unsigned char * mem; - cwal_value * v; - if(!ar->list.count) return CWAL_RC_RANGE; - vlist = (cwal_value **)ar->list.list; - v = vlist[0]; - if(rv) *rv = v; - mem = (unsigned char *)ar->list.list; - memmove( mem, mem+sizeof(cwal_value*), - sizeof(cwal_value*)*(ar->list.count-1)); - vlist[--ar->list.count] = NULL; - if(v){ - if(rv) cwal_value_unhand(v); - else cwal_value_unref(v); - } - return 0; - } -} - -int cwal_array_index_of( cwal_array const * ar, cwal_value const * v, - cwal_size_t * index, char strictComparison ){ - if(!ar) return CWAL_RC_MISUSE; - else if(!ar->list.count) return CWAL_RC_NOT_FOUND; - else{ - cwal_size_t i; - cwal_value * const arv = CWAL_VALPART(ar); - int opaque; - cwal_visit_list_begin(arv, &opaque); - for( i = 0; i < ar->list.count; ++i ){ - cwal_value const * rhs = (cwal_value const *)ar->list.list[i]; - if(v==rhs) break /* also match on NULL */; - else if((v && !rhs) || (!v && rhs)) continue; - else if(strictComparison){ - if(v->vtab->typeID == rhs->vtab->typeID - && 0 == cwal_value_compare(v, rhs)) break; - }else if(0 == cwal_value_compare(v, rhs)) break; - } - cwal_visit_list_end(arv, opaque); - if(i < ar->list.count){ - if(index) *index = i; - return 0; - }else{ - return CWAL_RC_NOT_FOUND; - } - } -} - - -int cwal_array_copy_range( cwal_array * ar, cwal_size_t offset, - cwal_size_t count, - cwal_array **dest ){ - SETUP_ARRAY_ARGS; - if( !ar || !dest || (ar==*dest) ) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(ar) || (*dest && CWAL_OB_IS_LOCKED(*dest))) return CWAL_RC_LOCKED; - else{ - int rc = 0; - cwal_size_t i; - cwal_array * tgt = *dest ? *dest : cwal_new_array(e); - cwal_value ** vlist; - cwal_value * const arv = CWAL_VALPART(ar); - int opaque; - cwal_visit_list_begin(arv, &opaque); - vlist = (cwal_value **)ar->list.list; - if(offsetlist.count){ - cwal_size_t to; - if(!count) count = ar->list.count - offset; - to = offset + count; - if(to > ar->list.count) to = ar->list.count; - rc = cwal_array_reserve( tgt, count ); - for( i = offset; - !rc && (iscope) - ? cwal_value_unref2(v->scope->e, v) - : CWAL_RC_MISUSE; -} - -/** - The C-string equivalent of cwal_obase_search_v(). -*/ -static cwal_value * cwal_obase_search( cwal_obase const * base, - bool searchProto, - char const * const key, - cwal_midsize_t keyLen, - cwal_kvp ** prev){ - if(!base || !key) return NULL; - else { - cwal_kvp * kvp; - cwal_value * rc = NULL; - while(base){ - if(CWAL_F_LOCKED & base->flags) break; -#if CWAL_OBASE_ISA_HASH - kvp = cwal_htable_search_impl_cstr(&base->hprops, key, keyLen, - NULL, prev); -#else - kvp = cwal_kvp_search( base->kvp, key, keyLen, prev ); -#endif - if(kvp) { - rc = kvp->value; - break; - } - else base = searchProto ? CWAL_VOBASE(base->prototype) : 0; - } - return rc; - } -} - -#if 0 -static int cwal_prop_getter_check( cwal_obase const * originalThis, - /* cwal_obase const * foundIn, */ - cwal_value const * key, - cwal_value * value, - cwal_value ** rv ){ - cwal_function * f = cwal_value_get_function(value); - if(!f || !(CWAL_CONTAINER_PROP_GETTER & f->base.flags)){ - *rv = value; - return 0; - }else{ - cwal_value * origin = CWAL_VALPART(originalThis); - cwal_value * argv[1] = { 0 }; - int rc; - argv[0] = (cwal_value*)key /* casting away the constness here is bad. - Fixing it requires non-const search keys. - Not a big deal in this context.*/; - rc = cwal_function_call(f, origin, rv, 1, argv); - /* How to propagate errors? We need a getter API which returns - an integer :/. */ - if(rc){ - *rv = 0; - } - return rc; - } -} -#endif - -/** - Counterpart of cwal_kvp_search_v(). Searches for key in base->kvp. - If not found and searchProto is true, it searches base->prototype - (recursively). If it encounters a locked object (base or one of its - prototypes) while searching, NULL is returned. It does not simply - skip over the locked member because the search results could then - arguably be inconsistent, depending on whether the locked member - actually overrides the property from a prototype further up the - chain. -*/ -static cwal_value * cwal_obase_search_v( cwal_obase const * base, - bool searchProto, - cwal_value const * key, - cwal_kvp ** prev){ - if(!base || !key) return NULL; - else { - cwal_kvp * kvp; - cwal_value * rc = NULL; - while(base){ - if(CWAL_F_LOCKED & base->flags) break; -#if CWAL_OBASE_ISA_HASH - kvp = cwal_htable_search_impl_v(&base->hprops, key, NULL, prev); -#else - kvp = cwal_kvp_search_v( base->kvp, key, prev ); -#endif - if(kvp) { - rc = kvp->value; - /* - Here is where we would check if kvp->value is - a function with the CWAL_CONTAINER_PROP_GETTER - container flag. If so, we would return the call - to that function, passing it (key). - */ - break; - } - else { - base = searchProto ? CWAL_VOBASE(base->prototype) : 0; - } - } - return rc; - } -} - - -bool cwal_prop_has( cwal_value const * v, - char const * key, cwal_midsize_t keyLen, - bool searchPrototype ) { - cwal_obase const * const base = CWAL_VOBASE(v); - return (base && key) - ? !!cwal_obase_search(base, searchPrototype, key, keyLen, NULL) - : false; -} - -bool cwal_prop_has_v( cwal_value const * v, - cwal_value const * key, - bool searchPrototype ){ - cwal_obase const * base = CWAL_VOBASE(v); - return (base && key) - ? !!cwal_obase_search_v(base, searchPrototype, key, NULL ) - : false; -} - -cwal_value * cwal_prop_get( cwal_value const * v, - char const * key, cwal_midsize_t keyLen ) { - cwal_obase const * const base = CWAL_VOBASE(v); - return (base && key) - ? cwal_obase_search( base, true, key, keyLen, NULL ) - : NULL; -} - - -cwal_value * cwal_prop_get_v( cwal_value const * c, - cwal_value const * key ) { - cwal_obase const * base = CWAL_VOBASE(c); - return (base && key) - ? cwal_obase_search_v( base, true, key, NULL ) - : NULL; -} - -cwal_kvp * cwal_prop_get_kvp( cwal_value * c, char const * key, - cwal_midsize_t keyLen, bool searchProtos, - cwal_value ** foundIn ){ - cwal_obase * b = CWAL_VOBASE(c); - if(!key || !*key || !b) return 0; - else{ - cwal_kvp * rc = 0; - while(b){ -#if CWAL_OBASE_ISA_HASH - rc = cwal_htable_search_impl_cstr(&b->hprops, key, keyLen, - NULL, NULL); -#else - rc = cwal_kvp_search(b->kvp, key, keyLen, NULL); -#endif - if(rc){ - if(foundIn) *foundIn = c; - break; - }else if(searchProtos - && (c = b->prototype) - && (b = CWAL_VOBASE(c))){ - continue; - } - b = 0; - }while(0); - return rc; - } -} - -cwal_kvp * cwal_prop_get_kvp_v( cwal_value * c, cwal_value const * key, - bool searchProtos, - cwal_value ** foundIn ){ - cwal_obase * b = CWAL_VOBASE(c); - if(!key || !b) return 0; - else{ - cwal_kvp * rc = 0; - while(b){ -#if CWAL_OBASE_ISA_HASH - rc = cwal_htable_search_impl_v(&b->hprops, key, NULL, NULL); -#else - rc = cwal_kvp_search_v(b->kvp, key, NULL); -#endif - if(rc){ - if(foundIn) *foundIn = c; - break; - }else if(searchProtos - && (c = b->prototype) - && (b = CWAL_VOBASE(c))){ - continue; - } - b = 0; - } - return rc; - } -} - -static cwal_function * cwal_value_is_interceptor( cwal_value const * v ){ - return (v - && (cwal_container_flags_get(v) & CWAL_CONTAINER_INTERCEPTOR)) - ? cwal_value_get_function(v) - : NULL; -} - -/** - Proxy for handling get/set interceptor calls. - - If cwal_value_is_interceptor(maybeFunc) then it is treated like an - interceptor, calling the function on self and passing it the - setterArg (if not NULL) or no arguments (assumed to be the getter - call form). If rv and setterArg are not NULL, the result goes in - *rv: the result of setters is ignored by the framework (the setter - APIs simply have no way to communicate overridden results all the - way back up the stack). - - propFoundIn must be the property in which maybeFunc was found, - presumably self or a prototype of self. -*/ -static int cwal_prop_check_intercept( cwal_value * propFoundIn, - cwal_value * self, - cwal_kvp * kvp, - cwal_value * setterArg, - cwal_value ** rv ){ - int rc = 0; - cwal_value * maybeFunc = kvp->value; - cwal_function* f = cwal_value_is_interceptor(maybeFunc); - assert( kvp ); - assert( propFoundIn ); - assert( self ); - assert( maybeFunc ); - if(f){ - cwal_value * xrv = 0; - /** - Getter/setter semantics: - - GET: value f.call(self, key) - - SET: void f.call(self, key, setterArg) - - We can't realistically propagate result values of setter - calls, because doing so would inherently replace the - interceptor with its call() result. - - It is up to e->cbHook.pre() to convey any needed local - script-side variables, e.g. "this" and the arguments array. - */ - rc = cwal_function_call2( f, - propFoundIn, - self, - (setterArg || !rv) ? NULL : &xrv, - setterArg ? 1 : 0, - setterArg ? &setterArg : NULL ); - if(!rc && rv && !setterArg){ - *rv = xrv ? xrv : cwal_value_undefined(); - } - }else if(setterArg && (CWAL_VAR_F_CONST & kvp->flags)){ - rc = CWAL_RC_CONST_VIOLATION; - }else if(rv){ - *rv = maybeFunc; - } - return rc; -} - -int cwal_prop_getX( cwal_value * c, - bool processInterceptors, - char const * key, - cwal_midsize_t keyLen, - cwal_value ** rv ){ - cwal_obase const * base = CWAL_VOBASE(c); - if(!base || !key) return CWAL_RC_MISUSE; - else{ - cwal_value * foundIn = 0; - cwal_kvp * kvp = cwal_prop_get_kvp( c, key, keyLen, 1, &foundIn ); - int rc; - if(!kvp){ - rc = CWAL_RC_NOT_FOUND; - }else if(processInterceptors){ - assert(foundIn); - rc = cwal_prop_check_intercept(foundIn, c, kvp, 0, rv); - }else{ - *rv = kvp->value; - rc = 0; - } - return rc; - } -} - -int cwal_prop_getX_v( cwal_value * c, - bool processInterceptors, - cwal_value const * key, - cwal_value ** rv ){ - cwal_obase const * base = CWAL_VOBASE(c); - if(!base || !key) return CWAL_RC_MISUSE; - else{ - cwal_value * foundIn = 0; - cwal_kvp * kvp = cwal_prop_get_kvp_v( c, key, 1, &foundIn ); - int rc; - if(!kvp){ - rc = CWAL_RC_NOT_FOUND; - }else if(processInterceptors){ - assert(foundIn); - rc = cwal_prop_check_intercept(foundIn, c, kvp, 0, rv); - }else{ - *rv = kvp->value; - rc = 0; - } - return rc; - } -} - - -int cwal_prop_unset( cwal_value * const c, - char const * key, cwal_midsize_t keyLen ) { - cwal_obase * const b = CWAL_VOBASE(c); - cwal_engine * const e = b ? CWAL_VENGINE(c) : NULL; - if(!e) return CWAL_RC_MISUSE; - else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - } -#if CWAL_OBASE_ISA_HASH - return cwal_htable_remove_impl_cstr(c, &b->hprops, key, keyLen); -#else - return cwal_kvp_unset( e, &b->kvp, key, keyLen ); -#endif -} - -int cwal_prop_unset_v( cwal_value * c, cwal_value * key ) { - cwal_obase * b = CWAL_VOBASE(c); - cwal_engine * e = b ? CWAL_VENGINE(c) : NULL; - if(!e) return CWAL_RC_MISUSE; - else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags) return CWAL_RC_DISALLOW_PROP_SET; -#if CWAL_OBASE_ISA_HASH - return cwal_htable_remove_impl_v(c, &b->hprops, key); -#else - return cwal_kvp_unset_v( e, &b->kvp, key ); -#endif -} - -#if !CWAL_OBASE_ISA_HASH -/** - Adds an entry or updates an existing one in the given kvp list. - listOwner must be the value which owns/manages list. key is the key - to search for and value is the value to set it - to. key->vtab->compare() is used to test for equivalence. - - On success listOwner->kvp may be modified. - - Returns 0 on success, CWAL_RC_MISUSE if any arguments are NULL, and - CWAL_RC_OOM on allocation error. - - Currently returns CWAL_RC_NOT_FOUND if the key is not found, but - that's highly arguable to do from this level. We do it here because - it would otherwise require a search-then-set (second lookup) in - some oft-called client code. - - Returns CWAL_RC_CONST_VIOLATION if key refers to an existing value - which is flagged as CWAL_VAR_F_CONST _unless_ v==theExistingValue - (in which case it is a silent no-op which reports success). The - (v==existing) workaround is admittedly to support operator - overloading in th1ish (and subsequently s2), where (x+=1) is - logically assigning to x (which may be const) but (because of the - overload) is actually doing something quite different. TODO: see if - we can remove this workaround - i don't think s2 needs it, but - th1ish might rely on it. - - Returns CWAL_RC_DISALLOW_PROP_SET if listOwner has the - CWAL_CONTAINER_DISALLOW_PROP_SET flag. - - Returns CWAL_RC_DISALLOW_NEW_PROPERTIES if listOwner is flagged - with the CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES flag _AND_ - key refers to a new property. - - If flags!=CWAL_VAR_F_PRESERVE then the flags of the kvp are set to - the given value (but the CONST check trumps this). -*/ -static int cwal_kvp_set_v( cwal_engine * e, cwal_value * listOwner, - cwal_value * key, cwal_value * v, uint32_t flags ){ - if( !e || !key || !listOwner || !v ) return CWAL_RC_MISUSE; - else { - int rc; - cwal_kvp * kvp = 0; - cwal_kvp * left = 0; - char i = 0; - bool iAlloccedKvp = 0; - cwal_obase * const ob = CWAL_VOBASE(listOwner); - assert(ob); - if(CWAL_CONTAINER_DISALLOW_PROP_SET & ob->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - } - kvp = cwal_kvp_search_v( ob->kvp, key, &left ); - if( !kvp ){ - if(CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & ob->flags){ - return CWAL_RC_DISALLOW_NEW_PROPERTIES; - } - else if(!v){ - return CWAL_RC_NOT_FOUND; - } - kvp = cwal_kvp_alloc( e ); - if( !kvp ) return CWAL_RC_OOM; - iAlloccedKvp = 1; - kvp->flags = (CWAL_VAR_F_PRESERVE==flags) - ? CWAL_VAR_F_NONE : flags; - } - /* - Here is where we would check if kvp->value is - a function with the CWAL_CONTAINER_PROP_SETTER - container flag. If so, we would return the call - to that function, passing it (key, value). - */ - else if(CWAL_VAR_F_CONST & kvp->flags){ - return CWAL_RC_CONST_VIOLATION; - } - if(CWAL_VAR_F_PRESERVE!=flags) kvp->flags = flags; - - /* Reminder: we have to increase the refcount of the key - even if it's the same pointer as a key we have or the same - as v. Remember that interning guarantees that we'll - eventually see the same key instances. - */ - for( i = 0; i < 2; ++i ){ /* 0==key, 1==value */ - cwal_value * vv = 0==i ? key : v; - rc = cwal_value_xscope( e, listOwner->scope, vv, 0 ); - if(rc){ - if(iAlloccedKvp){ - cwal_kvp_free( e/* , listOwner->scope */, kvp, 1 ); - } - assert(!"This cannot fail since the transition from arrays to linked lists."); - return rc; - } - /* The ref/unref order is important in case key==kvp->key or... - in case v==kvp->value, key, or listOwner. */ - cwal_value_ref2( e, vv ); - if(0==i) { /* KEY part... */ - if(kvp->key){ - cwal_value_unref2( e, kvp->key ); - } - kvp->key = vv; - }else{ /* VALUE part... */ - if(kvp->value){ - cwal_value_unref2( e, kvp->value ); - } - kvp->value = vv; - break; - } - } - assert(1 == i); - assert(kvp->key != 0); - assert(kvp->value != 0); - if(!iAlloccedKvp){ - /* kvp is already in *list */ - assert(left || ob->kvp==kvp); - }else if(left){ - /* kvp compares > left, so insert it here. */ - cwal_kvp * const r = left->right; - assert(!kvp->right); - left->right = kvp; - kvp->right = r; - }else{ - /* Make kvp the head of the list */ - assert(!left); - assert(ob->kvp != kvp); - assert(!kvp->right); - kvp->right = ob->kvp; - ob->kvp = kvp; - } - return CWAL_RC_OK; - } -} -#endif /* !CWAL_OBASE_ISA_HASH */ - - -#if 0 -/** - qsort(3) comparison func for a C-style (not cwal-style) array of - (cwal_kvp const *). -*/ -static int cwal_props_cmp(void const *l, void const *r){ - cwal_kvp const * lhs = *((cwal_kvp const **)l); - cwal_kvp const * rhs = *((cwal_kvp const **)r); - return cwal_value_compare( lhs ? lhs->key : NULL, rhs ? rhs->key : NULL ); -} -#endif - -static int cwal_prop_setX_impl_v( cwal_value * c, - CWAL_UNUSED_VAR bool processInterceptors, - cwal_value * key, cwal_value * v, - uint16_t flags ) { - cwal_obase * const b = CWAL_VOBASE(c); - cwal_engine * const e = CWAL_VENGINE(c); - assert(v ? (!!e || CWAL_MEM_IS_BUILTIN(v)) : 1); - if(!e || !key) return CWAL_RC_MISUSE; - else if(!b || !cwal_prop_key_can(key)) return CWAL_RC_TYPE; - else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - } - if(v){ -#if CWAL_OBASE_ISA_HASH - return cwal_htable_insert_impl_v(c, &b->hprops, key, v, true, flags, false); -#else - return cwal_kvp_set_v( e, c, key, v, flags ); -#endif - } - return cwal_prop_unset_v( c, key ); -} - -int cwal_prop_setX_with_flags_v( cwal_value * c, - bool processInterceptors, - cwal_value * key, cwal_value * v, - uint16_t flags ) { - return cwal_prop_setX_impl_v( c, processInterceptors, key, v, flags ); -} - -int cwal_prop_set_with_flags_v( cwal_value * c, cwal_value * key, cwal_value * v, - uint16_t flags ) { - return cwal_prop_setX_impl_v( c, 0, key, v, flags ); -} - - -int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ) { - return cwal_prop_set_with_flags_v( c, key, v, CWAL_VAR_F_PRESERVE ); -} - -static int cwal_prop_setX_impl( cwal_value * c, - bool processInterceptors, - char const * key, cwal_midsize_t keyLen, cwal_value * v, - uint16_t flags ) { - cwal_obase * const b = CWAL_VOBASE(c); - cwal_engine * const e = b ? CWAL_VENGINE(c) : 0; - if( !e || !key ) return CWAL_RC_MISUSE; - else if(!b) return CWAL_RC_TYPE; - else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - } - else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)){ - return CWAL_RC_DESTRUCTION_RUNNING; - } - else if( NULL == v ) return cwal_prop_unset( c, key, keyLen ); - cwal_value * const vkey = cwal_new_string_value(e, key, keyLen); - if(!vkey) return CWAL_RC_OOM; - int rc; - cwal_value_ref(vkey); - rc = cwal_prop_setX_impl_v( c, processInterceptors, vkey, v, flags); - cwal_value_unref2(e, vkey); - return rc; -} - -int cwal_prop_setX_with_flags( cwal_value * c, - bool processInterceptors, - char const * key, cwal_midsize_t keyLen, cwal_value * v, - uint16_t flags ) { - return cwal_prop_setX_impl( c, processInterceptors, key, - keyLen, v, flags ); -} - -int cwal_prop_set_with_flags( cwal_value * c, - char const * key, cwal_midsize_t keyLen, cwal_value * v, - uint16_t flags ) { - return cwal_prop_setX_impl( c, 0, key, keyLen, v, flags ); -} - - -int cwal_prop_set( cwal_value * c, - char const * key, cwal_midsize_t keyLen, cwal_value * v ) { - return cwal_prop_set_with_flags( c, key, keyLen, v, CWAL_VAR_F_PRESERVE ); -} - -cwal_value * cwal_prop_take( cwal_value * c, char const * key ){ - cwal_obase * const b = CWAL_VOBASE(c); - cwal_engine * const e = CWAL_VENGINE(c); - assert(c ? !!e : 1); - if( !e || !b || !key || CWAL_V_IS_VISITING(c)) return NULL; -#if CWAL_OBASE_ISA_HASH - else if(!b->hprops.list.count) return NULL; -#else - else if(!b->kvp) return NULL; -#endif - else { - /* FIXME: this is 90% identical to cwal_kvp_unset(), - only with different refcount handling. - Consolidate them. - - FIXME #2: instead of keeping our reference, - drop the reference and reprobate the value - if needed. - */ - cwal_kvp * kvp; - cwal_kvp * left = 0; - cwal_value * rv = NULL; -#if CWAL_OBASE_ISA_HASH - cwal_midsize_t ndx = 0; - kvp = cwal_htable_search_impl_cstr(&b->hprops, - key, cwal_strlen(key), - &ndx, &left); -#else - kvp = cwal_kvp_search( b->kvp, key, - cwal_strlen(key), &left ); -#endif - if( !kvp ) return NULL; - rv = kvp->value; - if(left){ - assert(kvp==left->right); - left->right = kvp->right; - }else{ -#if CWAL_OBASE_ISA_HASH - /* kvp is the only entry at ndx or the left-most entry in a hash - collision */ - assert(b->hprops.list.list[ndx] == kvp); - b->hprops.list.list[ndx] = kvp->right; -#else - assert(b->kvp == kvp && "kvp is the head of b->kvp"); - b->kvp = kvp->right; -#endif - } -#if CWAL_OBASE_ISA_HASH - --b->hprops.list.count; -#endif - kvp->right = NULL; - assert(rv); - cwal_value_unref2(e, kvp->key); - kvp->key = 0; - kvp->value = NULL/*steal its ref point*/; - cwal_kvp_free( e, kvp, 1 ); - if(!CWAL_MEM_IS_BUILTIN(rv)){ - assert( (CWAL_REFCOUNT(rv) > 0) && "Should still have kvp's reference!" ); - cwal_value_unhand(rv); - } - return rv; - } -} - -cwal_value * cwal_prop_take_v( cwal_value * c, cwal_value * key, - cwal_value ** takeKeyAsWell ){ - cwal_obase * const b = CWAL_VOBASE(c); - cwal_engine * const e = CWAL_VENGINE(c); - assert(c ? !!e : 1); - if( !e || !b || !key) return NULL; -#if CWAL_OBASE_ISA_HASH - else if(!b->hprops.list.count) return NULL; -#else - else if(!b->kvp) return NULL; -#endif - else if(CWAL_V_IS_VISITING(c)) return NULL; - else{ - cwal_kvp * left = 0; - cwal_value * rv = 0; - cwal_kvp * kvp; -#if CWAL_OBASE_ISA_HASH - cwal_midsize_t ndx = 0; - kvp = cwal_htable_search_impl_v(&b->hprops, key, &ndx, &left); -#else - kvp = cwal_kvp_search_v( b->kvp, key, &left ); -#endif - if(!kvp) return NULL; - rv = cwal_kvp_value(kvp); - if(left){ - assert(kvp==left->right); - left->right = kvp->right; - }else{ -#if CWAL_OBASE_ISA_HASH - /* kvp is only entry at ndx or the left-most entry - in a hash collision */ - assert(b->hprops.list.list[ndx] == kvp); - b->hprops.list.list[ndx] = kvp->right; -#else - assert(b->kvp == kvp && "kvp is the head b->kvp"); - b->kvp = kvp->right /* ? kvp->right : NULL */; -#endif - } -#if CWAL_OBASE_ISA_HASH - --b->hprops.list.count; -#endif - kvp->right = NULL; - kvp->value = NULL/*steal its ref point*/; - if(takeKeyAsWell){ - *takeKeyAsWell = kvp->key; - kvp->key = NULL; - cwal_value_unhand(*takeKeyAsWell); - } - cwal_kvp_free(e, kvp, 1); - if(!CWAL_MEM_IS_BUILTIN(rv)){ - assert( (CWAL_REFCOUNT(rv) > 0) && "Should still have kvp's reference!" ); - cwal_value_unhand(rv); - } - return rv; - } -} - -/** - "Visits" one member of a key/value pair. If mode is 0 then the key - is visited, and 1 means to visit the value. Any other value is - illegal. - - Returns the result of func( pairPair, fState ). -*/ -static int cwal_obase_visit_impl( cwal_kvp const *kvp, char mode, - cwal_value_visitor_f func, void * fState ){ - cwal_value * v = 0; - int rc; - switch( mode ){ - case 0: - v = kvp->key; - break; - case 1: - v = kvp->value; - assert(0 != v && "i am pretty sure we don't currently allow this."); - if(!v) return 0; - break; - default: - assert(!"Invalid visit mode."); - return CWAL_RC_CANNOT_HAPPEN; - } - assert(0 != v); - cwal_value_ref(v); - rc = func( v, fState ); - cwal_value_unhand(v); - return rc; -} - -bool cwal_props_can( cwal_value const * c ){ - return CWAL_VOBASE(c) ? 1 : 0; -} - -bool cwal_prop_key_can( cwal_value const * c ){ - switch(c->vtab->typeID){ - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_BOOL: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_HASH: - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_STRING: - case CWAL_TYPE_UNIQUE: - case CWAL_TYPE_XSTRING: - case CWAL_TYPE_ZSTRING: - return 1; - default: - return 0; - } -} - -int cwal_props_clear( cwal_value * c ){ - cwal_obase * b = CWAL_VOBASE(c); - cwal_engine * e = CWAL_VENGINE(c); - /* dump_val(c,"props_clear"); */ - if(!b) return CWAL_RC_TYPE; - else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - }else{ - cwal_cleanup_obase( e, b, false ); - return CWAL_RC_OK; - } -} - -static int cwal_kvp_visitor_props_copy( cwal_kvp const * kvp, - void * state ){ - cwal_value * const dest = (cwal_value*)state; - cwal_obase * const b = CWAL_VOBASE(dest); - cwal_engine * const e = CWAL_VENGINE(dest); - if( !e || !b ) return CWAL_RC_MISUSE; - /* Reminder: we have to keep the property flags intact. */ -#if CWAL_OBASE_ISA_HASH - return cwal_htable_insert_impl_v(dest, &b->hprops, kvp->key, - kvp->value, true, kvp->flags, false); -#else - return cwal_kvp_set_v( e, dest, kvp->key, kvp->value, kvp->flags ); -#endif -} - -int cwal_props_copy( cwal_value * src, cwal_value * dest ){ - cwal_obase const * const bSrc = CWAL_VOBASE(src); - cwal_obase const * const bDest = CWAL_VOBASE(dest); - if(!src || !dest) return CWAL_RC_MISUSE; - else if(!bSrc || !bDest) return CWAL_RC_TYPE; - else if(CWAL_V_IS_VISITING(dest)) return CWAL_RC_IS_VISITING; - /*else if(CWAL_OB_IS_LOCKED(bSrc) || CWAL_OB_IS_LOCKED(bDest)) return CWAL_RC_LOCKED;*/ - return cwal_props_visit_kvp( src, cwal_kvp_visitor_props_copy, dest ); -} - -bool cwal_props_has_any( cwal_value const * c ){ - cwal_obase const * b = CWAL_VOBASE(c); -#if CWAL_OBASE_ISA_HASH - return 0 < b->hprops.list.count; -#else - return b && b->kvp ? 1 : 0; -#endif -} - -cwal_midsize_t cwal_props_count( cwal_value const * c ){ - cwal_obase const * b = CWAL_VOBASE(c); -#if CWAL_OBASE_ISA_HASH - return b->hprops.list.count; -#else - cwal_size_t rc = 0; - cwal_kvp const * kvp = b->kvp; - for( ; kvp ; ++rc, kvp = kvp->right ){} - return rc; -#endif -} - -int cwal_props_visit_values( cwal_value * c, cwal_value_visitor_f f, void * state ){ - cwal_obase * b = CWAL_VOBASE(c); - if(!c || !f) return CWAL_RC_MISUSE; - else if(!b) return CWAL_RC_TYPE; - else { - int rc = 0; - int opaque; - cwal_obase_kvp_iter iter; - cwal_kvp const * kvp; - cwal_value_ref(c); - cwal_visit_props_begin(c, &opaque); - kvp = cwal_obase_kvp_iter_init(c, &iter); - for( ; kvp && (0==rc); kvp = cwal_obase_kvp_iter_next(&iter)){ - rc = (CWAL_VAR_F_HIDDEN & kvp->flags) - ? 0 - : cwal_obase_visit_impl( kvp, 1, f, state ); - } - cwal_visit_props_end(c, opaque); - cwal_value_unhand(c); - return rc; - } -} - -int cwal_props_visit_keys( cwal_value * c, cwal_value_visitor_f f, void * state ){ - cwal_obase * b = CWAL_VOBASE(c); - if(!c || !f) return CWAL_RC_MISUSE; - else if(!b) return CWAL_RC_TYPE; - else{ - int rc = 0; - int opaque; - cwal_obase_kvp_iter iter; - cwal_kvp const * kvp; - cwal_visit_props_begin(c, &opaque); - kvp = cwal_obase_kvp_iter_init(c, &iter); - for( ; kvp && (0==rc); kvp = cwal_obase_kvp_iter_next(&iter)){ - rc = (CWAL_VAR_F_HIDDEN & kvp->flags) - ? 0 - : cwal_obase_visit_impl( kvp, 0, f, state ); - } - cwal_visit_props_end(c, opaque); - return rc; - } -} - -bool cwal_value_may_iterate( cwal_value const * const c ){ - if(!c) return 0; - else if(CWAL_MEM_IS_BUILTIN(c)){ - return 0; - } - else{ -#if 0 - cwal_obase const * const ob = CWAL_VOBASE(c); - /* No... this will disable iteration even when array indexes - (but not properties) are locked... */ - return ob ? ((CWAL_F_LOCKED & ob->flags) ? 0 : 1) : 0; -#else - return !!CWAL_VOBASE(c); -#endif - } -} - -bool cwal_value_is_iterating_list( cwal_value const * const c ){ - return c ? CWAL_V_IS_VISITING_LIST(c) : 0; -} - -bool cwal_value_is_iterating_props( cwal_value const * const c ){ - return c ? CWAL_V_IS_VISITING(c) : 0; -} - -bool cwal_value_may_iterate_list( cwal_value const * const c ){ - if(!c) return 0; - switch(c->vtab->typeID){ - case CWAL_TYPE_TUPLE: - return 1 /* nothing can currently lock a tuple */; - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_HASH: - return (CWAL_F_LOCKED & CWAL_VOBASE(c)->flags) ? 0 : 1; - default: - return 0; - } -} - -int cwal_props_visit_kvp( cwal_value * c, cwal_kvp_visitor_f f, void * state ){ - cwal_obase * b = CWAL_VOBASE(c); - if(!c || !f) return CWAL_RC_MISUSE; - else if(!b) return CWAL_RC_TYPE; - else { - int rc = CWAL_RC_OK; - int opaque; - cwal_obase_kvp_iter iter; - cwal_kvp const * kvp; - cwal_visit_props_begin(c, &opaque); - kvp = cwal_obase_kvp_iter_init(c, &iter); - assert( CWAL_RCFLAG_HAS(c, CWAL_RCF_IS_VISITING) ); - cwal_value_ref(c); - for( ; kvp && (0==rc); - kvp = cwal_obase_kvp_iter_next(&iter) ){ - if(!(CWAL_VAR_F_HIDDEN & kvp->flags)){ - /* In case the callback does something untowards with - the kvp, we'll hold a couple new refs... */ - cwal_value * const lhs = kvp->key; - cwal_value * const rhs = kvp->value; - cwal_value_ref(lhs); - cwal_value_ref(rhs); - rc = f( kvp, state ); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - } - } - cwal_visit_props_end(c, opaque); - cwal_value_unhand(c); - return rc; - } -} - -int cwal_array_visit( cwal_array * const o, cwal_value_visitor_f const f, void * const state ){ - if(!o || !f) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED; - else { - cwal_size_t i; - cwal_value * v; - cwal_value * vSelf = cwal_array_value(o); - int rc = CWAL_RC_OK; - cwal_list * li = &o->list; - int opaque; - cwal_visit_list_begin(vSelf, &opaque); - cwal_value_ref(vSelf); - for( i = 0; - (CWAL_RC_OK==rc) - && (i < li->count); - ++i ){ - v = (cwal_value*)li->list[i]; - /* if(v){ */ - /* to support modification during traversal, we need - another ref... */ - if(v) cwal_value_ref(v); - rc = f( v, state ); - if(v) cwal_value_unref(v); - /* } */ - } - cwal_value_unhand(vSelf); - cwal_visit_list_end(vSelf, opaque); - return rc; - } -} - -int cwal_array_visit2( cwal_array * const o, cwal_array_visitor_f const f, void * const state ){ - if(!o || !f) return CWAL_RC_MISUSE; - else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED; - else{ - cwal_size_t i; - cwal_value * v; - cwal_value * vSelf = cwal_array_value(o); - int rc = CWAL_RC_OK; - cwal_list * li = &o->list; - int opaque; - cwal_visit_list_begin(vSelf, &opaque); - cwal_value_ref(vSelf); - for( i = 0; - (CWAL_RC_OK==rc) - && (i < li->count); - ++i ){ - v = (cwal_value*)li->list[i]; - /* if(v){ */ - /* to support modification during traversal, we need - another ref... */ - if(v) cwal_value_ref(v); - rc = f( o, v, i, state ); - if(v) cwal_value_unref(v); - /* } */ - } - cwal_value_unhand(vSelf); - cwal_visit_list_end(vSelf, opaque); - return rc; - } -} - -/** - qsort implementation adapted from: - - http://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#Iterative_Quicksort - - Based on the "iterative quicksort" C implementation. - - li is the cwal_list to sort. cmp is the comparison function to use - and the state parameter is passed as-is to cmp(). - - Returns 0 on success or non-0 if any call to cmp() returns, via - cmp()'s final parameter, a non-0 code (in which case that code is - returned). -*/ -static int cwal_value_list_sort_impl( cwal_engine * const e, - cwal_list * const li, - cwal_value_stateful_compare_f const cmp, - void * const state ){ - enum { MAX = 64 /* stack size for max 2^(64/2) array elements */}; - cwal_size_t left = 0, right, pos = 0, - seed = /*(cwal_size_t)rand()*/ - li->count/2 - /* reminder to self (20180620): using a random pivot requires - that we seed the RNG (call srand()), and it seems a bit - rude for the cwal core to do so, as the best we can do for - a seed is to call time(). Checkin f40e1afaede9a6fd (which - removed an srand() call in s2) uncovered an error - propagation problem when we use an unseeded random - pivot. Funnily enough, the checkin comment said: - - <<>> - - Haha. Running the s2 unit tests before checkin would have - revealed this problem then, rather than a month later - :|. */, - stack[MAX]; - cwal_size_t len = li->count; - int errc = 0, cmprc; - void * pivot = 0; - void * tmp; - assert(len > 1 && "this is never called for an empty- or length-1 array"); -#define CV (cwal_value*) - for( ;; ){ - for (; left+1 < len; ++len) { /* sort left to len-1 */ - if (pos == MAX) len = stack[pos = 0]; /* stack overflow, reset */ - pivot = li->list[left+seed%(len-left)];/* pick [possibly random] pivot */ - seed = seed*69069+1; /* next pseudorandom number */ - stack[pos++] = len; /* sort right part later */ - for(right = left-1; ; ) { /* inner loop: partitioning */ - while(1){ - /* look for greater element */ - cmprc = cmp( CV li->list[++right], CV pivot, state, &errc ); - if(errc) return errc; - else if(cmprc>=0) break; - } - while(1){ - /* look for smaller element */ - cmprc = cmp( CV pivot, CV li->list[--len], state, &errc ); - if(errc) return errc; - else if(cmprc>=0) break; - } - if(right >= len) break; /* partition point found? */ - tmp = li->list[right]; - li->list[right] = li->list[len]; /* the only swap */ - li->list[len] = tmp; - } /* partitioned, continue left part */ - } - if (pos == 0) break; /* stack empty? */ - left = len; /* left to right is sorted */ - len = stack[--pos]; /* get next range to sort */ - } -#undef CV - assert(!errc); - if(e){/*avoid unused param warning*/} - return 0; -} - -int cwal_array_sort_stateful( cwal_array * const ar, - cwal_value_stateful_compare_f cmp, - void * state ){ - cwal_value * const arv = CWAL_VALPART(ar); - if(!ar || !cmp || !arv) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(arv)) return CWAL_RC_IS_VISITING_LIST; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if(ar->list.count<2) return 0; - else { - cwal_engine * e; - int rc = 0; - int opaque; - cwal_visit_list_begin(arv, &opaque); - assert(arv->scope); - e = arv->scope->e; - CWAL_OB_LOCK(ar); - assert(CWAL_OB_IS_LOCKED(ar)); - rc = cwal_value_list_sort_impl( e, &ar->list, cmp, state ); - CWAL_OB_UNLOCK(ar); - assert(!CWAL_OB_IS_LOCKED(ar)); - cwal_visit_list_end(arv, opaque); - return rc ? rc : (e->values.exception ? CWAL_RC_EXCEPTION : 0); - } -} - -/** - Internal state for cwal_array_sort_func() and - cwal_array_sort_func_cb(). -*/ -struct ArrayFuncState { - cwal_function * f; - cwal_value * self; -}; -typedef struct ArrayFuncState ArrayFuncState; - -static int cwal_array_sort_func_cb( cwal_value * lhs, cwal_value * rhs, void * state, - int * errCode ){ - cwal_value * argv[2]; - ArrayFuncState * st = (ArrayFuncState *)state; - cwal_value * rv = 0; - int rc = 0; - argv[0] = lhs ? lhs : cwal_value_undefined(); - argv[1] = rhs ? rhs : cwal_value_undefined(); - *errCode = cwal_function_call(st->f, st->self, &rv, 2, argv); - cwal_value_ref(rv); - if(!*errCode && rv){ - /* We have to resolve this using doubles, not integers. See - https://github.com/ccxvii/mujs/issues/122 for why. */ - cwal_double_t const d = cwal_value_get_double(rv); - rc = (0.0==d) ? 0 : ((d<0) ? -1 : 1); - /* An alternate, more clever, formulation (via the above - ticket link), but i find it less readable: - rc = (d > 0) - (d < 0) */ - /* There's a corner-case bug here, though: if rv is an - integer value larger than cwal_double_t can represent, the - results will very possibly be wrong. */ - } - cwal_value_unref(rv); - return rc; -} - -int cwal_array_sort_func( cwal_array * ar, cwal_value * self, cwal_function * cmp ){ - if(!ar || !cmp) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if(ar->list.count<2) return 0; - else { - ArrayFuncState st; - st.f = cmp; - st.self = self ? self : cwal_function_value(cmp); - return cwal_array_sort_stateful( ar, cwal_array_sort_func_cb, &st ); - } -} - - -int cwal_array_sort( cwal_array * ar, int(*cmp)(void const *, void const *) ){ - if(!ar || !cmp) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST; - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if(ar->list.count<2) return 0; - else { - cwal_value * arv = CWAL_VALPART(ar); - int opaque; - cwal_visit_list_begin(arv, &opaque); - /* We don't know what cmp may do, thus we need to flag the visiting-related bits. */ - CWAL_OB_LOCK(ar); - qsort( ar->list.list, ar->list.count, sizeof(void*), cmp ); - CWAL_OB_UNLOCK(ar); - cwal_visit_list_end(arv, opaque); - return 0; - } -} - -int cwal_array_reverse( cwal_array * ar ){ - if(!ar) return CWAL_RC_MISUSE; -#if 0 - /* reversal is just a special case of get/set, so there seems to - be little harm in disallowing it during list - traversal. (Sorting is kinda also just a special case of - get/set, but it's a lot more involved.) */ - else if(CWAL_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST; -#endif - else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED; - else if(ar->list.count < 2) return 0; - else{ - cwal_size_t b = 0, e = ar->list.count-1; - void ** li = ar->list.list; - void * tmp; - for( ; b0); - if( *nDest < zLen ) return CWAL_RC_RANGE; - *nDest = (cwal_size_t)zLen; - memcpy( dest, z, zLen + 1/*NUL byte*/ ); - return 0; -#else - /*Implementation taken from th1 sources. Breaks on INT_MIN, - resulting in garbage. */ - if( iVal<0 ){ - isNegative = 1; - iVal = iVal * -1; - } - *(--z) = '\0'; - *(--z) = (char)(48+(iVal%10)); - while( (iVal = (iVal/10))>0 ){ - *(--z) = (char)(48+(iVal%10)); - assert(z>zBuf); - } - if( isNegative ){ - *(--z) = '-'; - } - zLen = zBuf+BufLen-z - 1/*NUL byte*/; - if( *nDest <= zLen ) return CWAL_RC_RANGE; - else{ - *nDest = zLen; - memcpy( dest, z, zLen + 1/*NUL byte*/ ); - MARKER("int %"CWAL_INT_T_PFMT"==>string: <<<%s>>>\n", iVal, dest ); - return CWAL_RC_OK; - } -#endif -} - -#if 0 /* cwal_double_to_cstr() ... */ -/* This conversion is easy to implement but fails some comparison - tests in cwal's test.c because 42.242 converts to 42.2419999... */ -int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){ - enum { BufLen = 256 }; - char zBuf[BufLen]; /* Output buffer */ - int n; - n = sprintf(zBuf, "%.17f", fVal); - if(n<1) return CWAL_RC_ERROR; - if(1==n){/*kludge!*/ - zBuf[1] = '.'; - zBuf[2] = '0'; - n += 2; - } - zBuf[n] = 0; - MARKER(("double zBuf=%s\n", zBuf)); - if(*nDest <= (cwal_size_t)n) return CWAL_RC_RANGE; - *nDest = (cwal_size_t)n; - memcpy(dest, zBuf, (size_t)n); - return 0; -} -#else -/** - Internal helper to proxy double-to-string conversions through - cwal_printf() (because it has far better precision handling - than i know how to implement properly). -*/ -typedef struct FixedBufferAppender { - char const * begin; - char const * end; - char * pos; - char const * dotPos; - int rc; -} FixedBufferAppender; -/** Internal cwal_printfv_appender() impl to append data to - a FixedBufferAppender. -*/ -static int cwal_printfv_appender_double( void * arg, char const * data, - unsigned n ){ - unsigned i; - FixedBufferAppender * ba = (FixedBufferAppender*)arg; - for( i = 0; i < n; ++i ){ - if(ba->pos==ba->end){ - return ba->rc = CWAL_RC_RANGE; - } - if('.' == (*ba->pos = data[i])) ba->dotPos = ba->pos; - else if('e' == data[i] || 'E' == data[i]){ - assert(!"We're expecting no exponent notation!"); - return ba->rc = CWAL_RC_CANNOT_HAPPEN; - } - ++ba->pos; - assert(ba->posend); - } - return 0; -} - -int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){ - enum { BufLen = 120 }; - char zBuf[BufLen] = {0,0,0,0,0,0}; - FixedBufferAppender dba; - cwal_size_t dLen; - dba.begin = dba.pos = zBuf; - dba.end = zBuf + BufLen; - dba.dotPos = 0; - dba.rc = 0; - cwal_printf( cwal_printfv_appender_double, &dba, "%lf", fVal ); - if(!dba.rc){ - if('N'==*zBuf || 'I'==*zBuf || 'I'==zBuf[1]){ - /* Assume NaN/[+-]Inf. */ - }else{ - assert(dba.dotPos - && "We're expecting the output to _always_ contain a decimal point!"); - if(!dba.dotPos) return CWAL_RC_CANNOT_HAPPEN; - } - *dba.pos = 0; - dLen = (cwal_size_t)(dba.pos - dba.begin); - --dba.pos; - /* The obligatory kludge: trim zeroes... */ - for( ; dba.dotPos && dba.pos > dba.dotPos+1 - && '0'==*dba.pos - /*&& '.' != *(dba.pos-1)*/; --dba.pos){ - *dba.pos = 0; - --dLen; - } - if(*nDest<=dLen+1/*NUL*/) return CWAL_RC_RANGE; - *nDest = dLen; - memcpy(dest, zBuf, dLen+1/*NUL*/); - } - return dba.rc; -} -#endif /* cwal_double_to_cstr() */ - -int cwal_cstr_to_int( char const * cstr, cwal_size_t slen, - cwal_int_t * dest ){ - cwal_int_t v = 0; - cwal_int_t oflow = 0; - char const * p = cstr+slen-1; - cwal_int_t mult = 0; - int digitCount = 0; - if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE; - for( ; p >= cstr; - --p, oflow = v ){ - if( (*p<'0') || (*p>'9') ) { - if(cstr == p){ - if('-'==*p){ - v = -v; - break; - } - else if('+'==*p){ - break; - } - } - return CWAL_RC_TYPE; - } - ++digitCount; - v += mult - ? (mult*(*p-'0')) - : (*p-'0'); - if(v < oflow) return CWAL_RC_RANGE; - mult = mult ? (mult*10) : 10; - } - if(!digitCount) return CWAL_RC_TYPE; - else if(dest) *dest = v; - return 0; -} - -int cwal_string_to_int( cwal_string const * s, cwal_int_t * dest ){ - return s - ? cwal_cstr_to_int( cwal_string_cstr(s), CWAL_STRLEN(s), dest ) - : CWAL_RC_MISUSE; -} - -int cwal_cstr_to_double( char const * cstr, cwal_size_t slen, - cwal_double_t * dest ){ - int rc; - char const * p = cstr+slen-1; - cwal_int_t rhs = 0; - cwal_int_t lhs = 0; - cwal_double_t rmult = 0.0; - if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE; - for( ; (p > cstr) && ('.' != *p); --p ){ - rmult = rmult ? (10*rmult) : 10; - } - if(p==cstr){ - /* Maybe it's an integer */ - if('.'==*p) return CWAL_RC_TYPE /* .1234 */; - else { - /* Try it as an integer */ - rc = cwal_cstr_to_int( p, slen, &lhs ); - if(!rc && dest) *dest = (cwal_double_t)lhs; - return rc; - } - } - - assert('.' == *p); - rc = cwal_cstr_to_int( p+1, cstr+slen-p-1, &rhs ); - if(rc) return rc; - else if((p>cstr) && (rhs<0)) return CWAL_RC_TYPE /* 123.-456 */; - - rc = cwal_cstr_to_int( cstr, p - cstr, &lhs ); - if(!rc && dest){ - if(lhs>=0){ - *dest = (cwal_double_t)lhs - + (rmult - ? (((cwal_double_t)rhs) / rmult) - : 0); - }else{ - *dest = (cwal_double_t)lhs - - (rmult - ? (((cwal_double_t)rhs) / rmult) - : 0); - } - - } - return rc; -} - -int cwal_string_to_double( cwal_string const * s, cwal_double_t * dest ){ - return s - ? cwal_cstr_to_double( cwal_string_cstr(s), CWAL_STRLEN(s), dest ) - : CWAL_RC_MISUSE; -} - -void cwal_hash_clear( cwal_hash * h, bool freeProps ){ - cwal_scope * const sc = h ? CWAL_VALPART(h)->scope : 0; - cwal_engine * const e = sc ? sc->e : 0; - assert(e && "Else invalid cwal_hash reference."); - cwal_cleanup_htable(e, &h->htable, false); - if(freeProps){ - cwal_cleanup_obase(e, &h->base, false); - } -} - -void cwal_value_cleanup_hash( cwal_engine * e, void * V ){ - cwal_value * vSelf = (cwal_value *)V; - cwal_hash * h = CWAL_HASH(vSelf); - /* MARKER("Freeing hash @%p\n", (void const *)h); */ - cwal_hash_clear( h, 1 ); - cwal_cleanup_htable(e, &h->htable, true); - *h = cwal_hash_empty; -} - -int cwal_rescope_children_hash( cwal_value * v ){ - cwal_hash * h = CWAL_HASH(v); - cwal_scope * sc = v->scope; - assert(sc && h && v); - assert(CWAL_V_IS_RESCOPING(v)); - cwal_rescope_children_obase(v); - /* cwal_dump_v(nv,"Re-scoping hashtable children..."); */ - cwal_htable_rescope(sc, &h->htable); - return 0; -} - -cwal_value * cwal_new_hash_value( cwal_engine * e, cwal_size_t hashSize){ - if(!e || !hashSize) return NULL; - else { - cwal_value * v = cwal_value_new(e, e->current, - CWAL_TYPE_HASH, 0); - if(v){ - cwal_hash * h = CWAL_HASH(v); - cwal_value * proto = h->base.prototype; - assert(v->scope); - *h = cwal_hash_empty; - h->base.prototype = proto; - if( 0 != cwal_hash_resize(h, hashSize) ){ - cwal_value_unref(v); - v = 0; - }else{ - assert(h->htable.list.alloced >= hashSize); - assert(!h->htable.list.count); - assert(hashSize == h->htable.hashSize); - } - } - return v; - } -} - -cwal_hash * cwal_value_get_hash( cwal_value * v ){ - return CWAL_HASH(v); -} - -cwal_hash * cwal_new_hash( cwal_engine * e, cwal_size_t hashSize ){ - cwal_value * v = cwal_new_hash_value(e, hashSize); - return v ? CWAL_HASH(v) : NULL; -} - -cwal_value * cwal_hash_value( cwal_hash * h ){ - return CWAL_VALPART(h); -} - -cwal_kvp * cwal_hash_search_kvp( cwal_hash * h, char const * key, - cwal_midsize_t keyLen ){ - return cwal_htable_search_impl_cstr(&h->htable, key, - keyLen, NULL, NULL); -} - -cwal_value * cwal_hash_search( cwal_hash * h, char const * key, - cwal_midsize_t keyLen ){ - cwal_kvp const * const kvp = - cwal_htable_search_impl_cstr(&h->htable, key, keyLen, - NULL, NULL); - return kvp ? kvp->value : 0; -} - -cwal_value * cwal_hash_search_v( cwal_hash * h, cwal_value const * key ){ - cwal_kvp const * const kvp = - cwal_htable_search_impl_v(&h->htable, key, NULL, NULL); - return kvp ? kvp->value : NULL; -} - -cwal_kvp * cwal_hash_search_kvp_v( cwal_hash * h, cwal_value const * key ){ - return cwal_htable_search_impl_v(&h->htable, key, NULL, NULL); -} - -int cwal_hash_insert_with_flags_v( cwal_hash * h, cwal_value * key, cwal_value * v, - bool allowOverwrite, cwal_flags16_t kvpFlags ){ - cwal_value * const hv = CWAL_VALPART(h); - if(!cwal_prop_key_can(key)) return CWAL_RC_TYPE; - else if(CWAL_V_IS_IN_CLEANUP(hv)) return CWAL_RC_DESTRUCTION_RUNNING; - else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST; - return cwal_htable_insert_impl_v(hv, &h->htable, key, v, - allowOverwrite, kvpFlags, false); -} - -int cwal_hash_insert_v( cwal_hash * h, cwal_value * key, cwal_value * v, - bool allowOverwrite ){ - return cwal_hash_insert_with_flags_v(h, key, v, allowOverwrite, - CWAL_VAR_F_PRESERVE); -} - -int cwal_hash_insert_with_flags( cwal_hash * h, char const * key, cwal_midsize_t keyLen, - cwal_value * v, bool allowOverwrite, - cwal_flags16_t kvpFlags ){ - cwal_value * const hv = CWAL_VALPART(h); - cwal_engine * const e = hv->scope->e; - cwal_value * const vKey = cwal_new_string_value(e, key, keyLen); - if(!vKey) return CWAL_RC_OOM; - int rc; - cwal_value_ref(vKey); - rc = cwal_hash_insert_with_flags_v(h, vKey, v, - allowOverwrite, kvpFlags); - cwal_value_unref(vKey); - return rc; -} - -int cwal_hash_insert( cwal_hash * h, char const * key, cwal_midsize_t keyLen, - cwal_value * v, bool allowOverwrite ){ - return cwal_hash_insert_with_flags( h, key, keyLen, v, allowOverwrite, CWAL_VAR_F_PRESERVE ); -} - -int cwal_hash_remove_v( cwal_hash * h, cwal_value * key ){ - cwal_value * const vSelf = CWAL_VALPART(h); - if(!h || !vSelf || !key) return CWAL_RC_MISUSE; - else if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - //else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags) return CWAL_RC_DISALLOW_PROP_SET; - else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST; - return cwal_htable_remove_impl_v(vSelf, &h->htable, key); -} - -int cwal_hash_remove( cwal_hash * h, char const * key, cwal_midsize_t keyLen ){ - cwal_value * const vSelf = CWAL_VALPART(h); - if(!h || !vSelf || !key) return CWAL_RC_MISUSE; - else if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING; - //else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags) return CWAL_RC_DISALLOW_PROP_SET; - else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST; - return cwal_htable_remove_impl_cstr(vSelf, &h->htable, key, keyLen); -} - -cwal_midsize_t cwal_hash_entry_count(cwal_hash const *h){ - return h->htable.list.count; -} - -cwal_midsize_t cwal_hash_size( cwal_hash const * h ){ - return h->htable.hashSize; -} - -int cwal_hash_visit_kvp( cwal_hash * h, cwal_kvp_visitor_f f, void * state ){ - cwal_value * const hv = (h && f) ? CWAL_VALPART(h) : NULL; - if(!hv || !f) return CWAL_RC_MISUSE; - else { - cwal_kvp * kvp; - cwal_size_t i; - int rc = CWAL_RC_OK; - int opaque; - cwal_visit_list_begin(hv, &opaque); - for( i = 0; !rc && (i < h->htable.hashSize); ++i ){ - kvp = (cwal_kvp*)h->htable.list.list[i]; - if(!kvp) continue; - else{ - cwal_kvp * next = 0; - for( ; !rc && kvp; kvp = next ){ - next = kvp->right; - rc = (CWAL_VAR_F_HIDDEN & kvp->flags) - ? 0 - : f( kvp, state ); - } - } - } - cwal_visit_list_end(hv, opaque); - return rc; - } -} -/** - mode: 1==visit the keys, 0==the values. -*/ -static int cwal_hash_visit_value_impl( cwal_hash * h, char mode, - cwal_value_visitor_f f, void * state ){ - cwal_value * hv = (h && f) ? CWAL_VALPART(h) : NULL; - cwal_obase * b = CWAL_VOBASE(hv); - if(!hv || !f) return CWAL_RC_MISUSE; - else if(!b) return CWAL_RC_TYPE; - else { - cwal_kvp * kvp; - cwal_size_t i; - int rc = CWAL_RC_OK; - int opaque; - cwal_visit_list_begin(hv, &opaque); - for( i = 0; !rc && (i < h->htable.hashSize); ++i ){ - kvp = (cwal_kvp*)h->htable.list.list[i]; - if(!kvp) continue; - else{ - cwal_kvp * next = 0; - for( ; !rc && kvp; kvp = next ){ - next = kvp->right; - rc = (CWAL_VAR_F_HIDDEN & kvp->flags) - ? 0 - : f( mode ? kvp->value : kvp->key, - state ); - } - } - } - cwal_visit_list_end(hv, opaque); - return rc; - } -} - -int cwal_hash_visit_keys( cwal_hash * h, cwal_value_visitor_f f, - void * state ){ - return cwal_hash_visit_value_impl(h, 0, f, state); -} - -int cwal_hash_visit_values( cwal_hash * h, cwal_value_visitor_f f, - void * state ){ - return cwal_hash_visit_value_impl(h, 1, f, state); -} - -int cwal_hash_resize( cwal_hash * h, cwal_size_t newSize ){ - cwal_value * const hv = CWAL_VALPART(h); - if(!h || !hv) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST; - /* highly arguable: newSize = cwal_trim_hash_size( newSize ); */ - return cwal_htable_resize(hv, &h->htable, newSize); -} - -cwal_midsize_t cwal_next_prime( cwal_midsize_t n ){ -#if 1 - int const * p = cwal_first_1000_primes(); - int const last = 999; - int i = 0; - if((int)n >= p[last]) return (cwal_hash_t)p[last]; - for( ; i < last; ++i){ - if(p[i] > (int)n) return (cwal_hash_t)p[i]; - } - return p[i]; -#else - /** - This list was more or less arbitrarily chosen by starting at - some relatively small prime and roughly doubling it for each - increment, then filling out some numbers in the middle. - */ - static const cwal_hash_t list[] = { - 5, 7, 11, 17, 19, 27, 41, 59, 71, 97, 109, 137, - 167, 199, 239, 373, 457, 613, 983, 1319, 2617, - 5801, 7919, 9973, 14563, 20011, - 30241, 40637, -#if CWAL_INT_T_BITS <= 16 - /* max for 16-bit */ - 49999, -#else - 60913, 80897, - 104729, 160969, -#endif - 0/*sentinel*/}; - cwal_hash_t const * i; - for( i = list; *i && *ihtable, load); -} - -int cwal_hash_take_props( cwal_hash * const h, cwal_value * const src, - int overwritePolicy ){ - cwal_obase * srcBase; - cwal_value * const hv = CWAL_VALPART(h); - if(!src || !h || !hv) return CWAL_RC_MISUSE; - else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST; - else if(CWAL_V_IS_VISITING(src)) return CWAL_RC_IS_VISITING; - else if(!(srcBase = CWAL_VOBASE(src))) return CWAL_RC_TYPE; -#if CWAL_OBASE_ISA_HASH - /** FIXME: what follows is a relatively inefficient implementation - compared to the !CWAL_OBASE_ISA_HASH variant in the following - #else block. It would seem that implementing an equivalent - no-alloc impl for the CWAL_OBASE_ISA_HASH case is far more - trouble (or else i'm simply getting old and it just *feels* like - far more trouble). */ - int rc = 0; - cwal_obase_kvp_iter iter; - cwal_kvp const * kvp; - cwal_array * keysToRm = 0; - int opaque = 0; - cwal_visit_props_begin(src, &opaque); - if(overwritePolicy<0) overwritePolicy=-1 /* keep existing props */; - else if(overwritePolicy>0) overwritePolicy=1 /* overwrite */; - else overwritePolicy=0 /* error on collision */; - kvp = cwal_obase_kvp_iter_init(src, &iter); - for( ; kvp && (0==rc); kvp = cwal_obase_kvp_iter_next(&iter) ){ - cwal_kvp * const hKvp = cwal_htable_search_impl_v(&h->htable, kvp->key, - NULL, NULL); - while(hKvp){ - switch(overwritePolicy){ - case -1: /* Keep existing keys */ - kvp = 0; - break; - case 0: /* Collision == error */ - rc = CWAL_RC_ALREADY_EXISTS; - kvp = 0; - break; - case 1: /* Overwrite... */ - break; - default: - assert(!"Cannot happen."); - abort(); - } - break; - } - if(!kvp) continue; - else if(!keysToRm){ - keysToRm = cwal_new_array(CWAL_VENGINE(hv)); - if(!keysToRm){ - rc = CWAL_RC_OOM; - break; - } - } - rc = cwal_htable_insert_impl_v(hv, &h->htable, kvp->key, kvp->value, - true, CWAL_VAR_F_PRESERVE, false); - if(!rc) rc = cwal_array_append(keysToRm, kvp->key); - } - cwal_visit_props_end(src, opaque); - if(!rc && keysToRm){ - cwal_midsize_t const nRm = cwal_array_length_get(keysToRm); - for(cwal_midsize_t i = 0; i < nRm; ++i){ - cwal_value * const k = cwal_array_get(keysToRm, i); - cwal_prop_unset_v(src, k); - } - } - cwal_value_unref(CWAL_VALPART(keysToRm)); - return rc; -#else - cwal_kvp * kvp; - cwal_kvp * keepThese = 0; - cwal_kvp * toKeepTail = 0; - int rc = 0; - cwal_engine * const e = hv->scope->e; - /*nope - will break following set ops: cwal_value_set_visiting_list(hv, 1);*/ - while( (kvp = srcBase->kvp) ){ - cwal_value * k = kvp->key; - cwal_value * v = kvp->value; - assert(CWAL_REFCOUNT(k) || CWAL_MEM_IS_BUILTIN(k)); - assert(CWAL_REFCOUNT(v) || CWAL_MEM_IS_BUILTIN(v)); - srcBase->kvp = kvp->right; - *kvp = cwal_kvp_empty; - e->values.hashXfer = kvp; - rc = cwal_hash_insert_v( h, k, v, overwritePolicy>0 ? 1 : 0 ); - switch(rc){ - case 0: - if(e->values.hashXfer){ - assert(0!=overwritePolicy); - cwal_kvp_free(e, e->values.hashXfer, 1); - e->values.hashXfer = 0; - }else{ - /* e->values.hashXfer was taken by insert() */ - assert(CWAL_REFCOUNT(k)>1 || CWAL_MEM_IS_BUILTIN(k)); - assert(CWAL_REFCOUNT(v)>1 || CWAL_MEM_IS_BUILTIN(v)); - assert(kvp->key == k); - assert(kvp->value == v); - } - /* account for src container refs */ - cwal_value_unref(k); - cwal_value_unref(v); - continue; - case CWAL_RC_ALREADY_EXISTS: - assert(kvp == e->values.hashXfer); - e->values.hashXfer = 0; - assert(overwritePolicy<=0); - if(overwritePolicy<0){ - rc = 0; - assert(!kvp->right); - assert(!kvp->key); - assert(!kvp->value); - kvp->right = keepThese; - kvp->key = k; - kvp->value = v; - keepThese = kvp; - if(!toKeepTail) toKeepTail = keepThese; - continue; - }else{ - goto fixkvp; - } - default: - assert(kvp == e->values.hashXfer); - e->values.hashXfer = 0; - goto fixkvp; - } - assert(!"not reached"); - fixkvp: - assert(CWAL_REFCOUNT(k) || CWAL_MEM_IS_BUILTIN(k)); - assert(CWAL_REFCOUNT(v) || CWAL_MEM_IS_BUILTIN(v)); - assert(0!=rc); - /* Put kvp back in srcBase */ - assert(!kvp->right); - kvp->right = srcBase->kvp; - srcBase->kvp = kvp; - kvp->key = k; - kvp->value = v; - break; - } - if(toKeepTail){ - assert(keepThese); - assert(toKeepTail->key); - assert(toKeepTail->value); - assert(CWAL_REFCOUNT(toKeepTail->key) || CWAL_MEM_IS_BUILTIN(toKeepTail->key)); - assert(CWAL_REFCOUNT(toKeepTail->value) || CWAL_MEM_IS_BUILTIN(toKeepTail->value)); - toKeepTail->right = srcBase->kvp; - srcBase->kvp = keepThese; - } - /* nope cwal_value_set_visiting_list(hv, 0); */ - return rc; -#endif -} - - -void cwal_value_cleanup_native( cwal_engine * e, void * V ){ - cwal_value * v = (cwal_value *)V; - cwal_native * n = v ? CWAL_V2NATIVE(v) : 0; - assert(v && n); - if(n->finalize){ - n->finalize( e, n->native ); - n->finalize = NULL; - n->native = NULL; - } - cwal_cleanup_obase( e, &n->base, 1 ) - /* Do this first in case any properties use the - native data. No... do it last in case - any of the properties are used in the finalizer. - i broke linenoiseish's auto-save-at-finalize - when i swapped this. */; - *n = cwal_native_empty; -} - -cwal_value * cwal_new_native_value( cwal_engine * e, void * N, - cwal_finalizer_f dtor, - void const * typeID ){ - if(!e || !N || !typeID) return NULL; - else{ - cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_NATIVE, 0); - if( NULL != v ){ - cwal_native * n = CWAL_V2NATIVE(v); - cwal_value * proto = n->base.prototype; -#if 0 - cwal_weak_annotate(e, N); -#endif - *n = cwal_native_empty; - n->base.prototype = proto; - n->native = N; - n->finalize = dtor; - n->typeID = typeID; - } - return v; - } -} - -int cwal_native_set_rescoper( cwal_native * nv, - cwal_value_rescoper_f rescoper){ - if(!nv) return CWAL_RC_MISUSE; - else { - nv->rescoper = rescoper; - return 0; - } -} - -int cwal_value_fetch_native( cwal_value const * val, cwal_native ** ar){ - if( ! val ) return CWAL_RC_MISUSE; - else if( CWAL_TYPE_NATIVE != val->vtab->typeID ) return CWAL_RC_TYPE; - else{ - if(ar) *ar = CWAL_V2NATIVE(val); - return 0; - } -} - -int cwal_native_fetch( cwal_native const * n, - void const * typeID, void ** dest){ - if( !n) return CWAL_RC_MISUSE; - else if(!typeID || n->typeID==typeID){ - if(dest) *dest = n->native; - return 0; - }else return CWAL_RC_TYPE; -} - -void * cwal_native_get( cwal_native const * n, void const * typeID){ - void * x = NULL; - cwal_native_fetch( n, typeID, &x ); - return x; -} - -void cwal_native_clear( cwal_native * n, char callFinalizer ){ - if(n && n->native){ - if(callFinalizer && n->finalize){ - cwal_value * nv = CWAL_VALPART(n); - assert(nv->scope && nv->scope->e); - n->finalize( nv->scope->e, n->native ); - } - n->native = NULL; - n->finalize = NULL; - n->typeID = NULL; - n->rescoper = NULL; - } -} - -cwal_value * cwal_native_value( cwal_native const * n ){ - return n ? CWAL_VALPART(n) : 0; -} - -cwal_native * cwal_new_native( cwal_engine * e, void * n, - cwal_finalizer_f dtor, - void const * typeID ){ - cwal_value * v = cwal_new_native_value( e, n, dtor, typeID ); - return v ? CWAL_V2NATIVE(v) : 0; -} - -cwal_native * cwal_value_get_native( cwal_value const * v ) { - cwal_native * ar = NULL; - cwal_value_fetch_native( v, &ar ); - return ar; -} - - -cwal_size_t cwal_engine_recycle_max_get( cwal_engine * e, cwal_type_id type ){ - if(!e) return 0; - else switch(type){ - case CWAL_TYPE_STRING: - return e->reString.maxLength; - default:{ - cwal_recycler const * re = cwal_recycler_get( e, type ); - return re ? re->maxLength : 0; - } - } -} - -int cwal_engine_recycle_max( cwal_engine * e, cwal_type_id typeID, - cwal_size_t max ){ - if(!e) return CWAL_RC_MISUSE; - else if( CWAL_TYPE_UNDEF == typeID ){ - int rc = 0; -#define DO(T) rc = cwal_engine_recycle_max(e, CWAL_TYPE_##T, max ); if(rc) return rc - DO(ARRAY); - DO(BUFFER); - DO(DOUBLE); - DO(EXCEPTION); - DO(FUNCTION); - DO(INTEGER); - DO(KVP); - DO(NATIVE); - DO(OBJECT); - DO(HASH); - DO(SCOPE); - DO(STRING); - DO(XSTRING/* also Z-strings*/); - DO(UNIQUE); - DO(TUPLE); - DO(WEAK_REF); -#undef DO - return rc; - } - else { - cwal_recycler * li; - void * mem; - li = cwal_recycler_get( e, typeID ); - if( !li ) return CWAL_RC_TYPE; - li->maxLength = max; - while( li->count > max ){ - /* If we're over our limit, give them back... */ - assert(li->list); - switch(typeID){ - case CWAL_TYPE_WEAK_REF:{ - cwal_weak_ref * r = (cwal_weak_ref *)li->list; - mem = r; - li->list = r->next; - r->next = NULL; - break; - } - case CWAL_TYPE_KVP:{ - cwal_kvp * kvp = (cwal_kvp *)li->list; - mem = kvp; - li->list = kvp->right; - kvp->right = 0; - break; - } - default: - mem = (cwal_value *)li->list; - li->list = cwal_value_snip((cwal_value *)li->list); - break; - } - --li->count; - cwal_free( e, mem ); - } - return CWAL_RC_OK; - } -} - -/** - Ensures that s->props is initialized. Returns 0 on success, non-0 - on error (which is serious and must not be ignored). After a - successful call for a given scope call it "cannot fail" on - subsequent calls unless the scope somehow loses its properties, in - which case this routine would create a new s->props object/hash. -*/ -static int cwal_scope_init_props(cwal_scope *s){ - if(s->props) return 0; - assert( s->e ); - s->props = (CWAL_FEATURE_SCOPE_STORAGE_HASH & s->e->flags) - ? cwal_new_hash_value(s->e, CwalConsts.DefaultHashtableSize) - : cwal_new_object_value(s->e); - /* reminder: its owning scope might be a different one! We'll - fix that below. */ - if(s->props){ - cwal_value * const pv = s->props; - cwal_obase * const obase = CWAL_VOBASE(s->props); - assert(obase); - cwal_value_ref2(s->e, pv) - /* we do this so that sweep() cannot wipe it out. - Yes, i've actually seen this object get swept - up before. */ - ; - obase->flags |= CWAL_F_IS_PROP_STORAGE; - if(pv->scope->level > s->level){ - /* This can happen if a scope creates no vars but one is - added after a subscope is active. Our newly-created pv - is owned by the newer scope initially, and we need to - back-door it into the scope on whose behalf it stores - properties (at least initially - it can move out later - on). - */ - cwal_value_xscope(s->e, s, pv, NULL); - }else{ - assert(s == pv->scope); - assert( obase->flags & CWAL_F_IS_PROP_STORAGE ); - if(cwal_scope_insert(pv->scope, pv)){ - assert(s->e->fatalCode); - return s->e->fatalCode; - } - assert( obase->flags & CWAL_F_IS_PROP_STORAGE ); - } - assert(pv->scope->mine.headSafe == pv); -#if 1 - if(obase->prototype){ - /** - If we don't do this then cwal_scope_search_v() - and friends will resolve prototype members installed - for the default prototype for props->proto's type by - the client. It might be interesting to install our own - prototype for these objects, but i'm not sure what we - might do with it. - - i.e. if we don't do this then (using th1ish as an example): - - scope { - get(xyz) // resolves to Object.get() inherited method - } - - 20181128: i still can't shake the feeling that there - might be interesting uses for that in s2. Could we - possibly implement a JS-like "with" feature using that, - by setting the prototype to the "with'd" value? - - Nevermind: cwal_scope_search_v() and friends, via - cwal_prop_get_kvp_v(), explicitly do not search - prototypes for values. Maybe they should? Enabling that - "shouldn't" break anything because scope properties - have explicitely (via this upcoming call) had no - prototypes since our earliest ancestors wrote code. - Adding that feature would require adding support for it - in cwal_hash_search_impl_v() and - cwal_hash_search_impl_cstr(), as those don't currently - support prototype traversal. Enabling prototype lookups - might also have negative side effects if we "with'd" - one scope properties with another and both scopes used - different storage types (hash vs object). All routines - involved would have to know to potentially switch - types/modes, and that quickly turns into a rat's nest - of special cases. - */ - cwal_value_prototype_set(pv, NULL); - } -#endif - } - return s->props - ? 0 - : CWAL_RC_OOM; -} - -static int cwal_scope_adjust_prop_size(cwal_scope * s){ - cwal_hash * h = CWAL_HASH(s->props); - return h ? cwal_hash_grow_if_loaded(h, -1.0) : 0; -} - -/** - Temporary internal macro used to set up the engine/scope - arguments for the cwal_var_xxx() family of functions. -*/ -#define SETUP_VAR_E_S if(!e) e=s?s->e:0; if(!s)s=e?e->current:0; \ - if(!s && !e) return CWAL_RC_MISUSE - -static int cwal_var_set_v_impl( cwal_engine * e, cwal_scope * s, - cwal_value * key, cwal_value * v, - bool searchParents, - uint16_t flags ){ - if(!e || !key) return CWAL_RC_MISUSE; - SETUP_VAR_E_S; - else{ - /* We need to take parent scopes into account when assigning a - variable. This causes duplicate lookup of the var via - cwal_kvp_set_v() (which also has to search). - */ - cwal_scope * foundIn = 0; - int rc = 0; - /* cwal_value * got = 0; */ - cwal_scope * origin = s; - cwal_hash * h; - cwal_kvp * kvp = 0; - assert(!(s->flags & CWAL_F_IS_DESTRUCTING)); - if(!v) searchParents = 0 /* do not remove props from parens this way! */; - while(s && !foundIn){ - kvp = cwal_scope_search_kvp_v( s, 0, key, &foundIn ); - if(kvp) break; - s = searchParents ? s->parent : 0; - } -#if 0 - dump_val(key,"setting this key"); - MARKER(("var set v impl new kvp flags=0x%04x\n", flags)); - if(kvp){ - MARKER(("var set v impl kvp flags=0x%04x, new flags=0x%04x\n", (int)kvp->flags, flags)); - dump_val(kvp->key,"key"); - dump_val(kvp->value,"val"); - } -#endif - if(kvp){ - /* Found a match. */ - /*MARKER(("kvp found. flags=0x%04d\n", (int)kvp->flags));*/ - /* dump_val(kvp->key,"key"); */ - assert(s == foundIn); - assert(foundIn->props); - if(CWAL_VAR_F_CONST & kvp->flags){ - return CWAL_RC_CONST_VIOLATION; - } - else if(v){ - rc = cwal_kvp_value_set2(kvp, v); - if(!rc) cwal_value_rescope(foundIn, v); - return rc; - } - } - /* Below here, we did not find a match, so we need to insert one, - OR we found a match but are about to do an UNSET. - - FIXME (2021-07-110: This is currently less efficient than it - could be. We now have enough infrastructure to be able to do - this without a second property lookup imposed by the upcoming - set/unset operations. - */ - if(!foundIn){ - if(!v) return CWAL_RC_NOT_FOUND; - foundIn = origin; - } - if(!foundIn->props){ - rc = cwal_scope_init_props(foundIn); - }else if(CWAL_V_IS_VISITING(foundIn->props)){ - rc = CWAL_RC_IS_VISITING; - }else if(CWAL_HASH(foundIn->props) && - CWAL_V_IS_VISITING_LIST(foundIn->props)){ - rc = CWAL_RC_IS_VISITING_LIST; - } - if(rc) return rc; - assert(foundIn && foundIn->props); -#if 0 - MARKER(("Setting [opaque key type] in scope#%d via scope#%d.\n", - (int)foundIn->level, (int)origin->level)); -#endif - if(CWAL_OBASE_ISA_HASH ? NULL : (h = CWAL_HASH(foundIn->props))){ -#if 0 - dump_val(key,"setting this key in hash"); - dump_val(foundIn->props,"in this container"); - MARKER(("var set v impl new kvp flags=0x%04x\n", flags)); - if(kvp){ - MARKER(("var set v impl kvp flags=0x%04x, new flags=0x%04x\n", (int)kvp->flags, flags)); - dump_val(kvp->key,"key"); - dump_val(kvp->value,"val"); - } -#endif - rc = v - ? cwal_hash_insert_with_flags_v( h, key, v, 1, flags ) - : cwal_hash_remove_v(h, key); - //if(v && !rc) rc = cwal_scope_adjust_prop_size(foundIn); - }else{ - /* Object-type scope properties... */ - cwal_obase * const b = CWAL_VOBASE(foundIn->props); - assert(b); - assert(foundIn->props); -#if CWAL_OBASE_ISA_HASH - rc = v - ? cwal_htable_insert_impl_v(foundIn->props, &b->hprops, - key, v, true, flags, false) - : cwal_htable_remove_impl_v(foundIn->props, &b->hprops, - key); -#else - rc = v - ? cwal_kvp_set_v( e, foundIn->props, key, v, flags ) - : cwal_kvp_unset_v( e, &b->kvp, key ); -#endif - } - return rc; - - } -} - -static int cwal_kvp_visitor_scope_import_props( cwal_kvp const * kvp, - void * state ){ - cwal_scope * dest = (cwal_scope*)state; - return cwal_var_set_v_impl( dest->e, dest, kvp->key, kvp->value, 0, kvp->flags ); -} - -int cwal_scope_import_props( cwal_scope * dest, cwal_value * src ){ - if(!src || !dest || src==dest->props) return CWAL_RC_MISUSE; - else if(CWAL_F_IS_DESTRUCTING & dest->flags) return CWAL_RC_DESTRUCTION_RUNNING; - else if(!cwal_props_can(src)) return CWAL_RC_TYPE; - else{ - int rc = cwal_scope_init_props(dest); - cwal_hash * const h = (rc || CWAL_OBASE_ISA_HASH) ? NULL : CWAL_HASH(src); - if(rc) return rc; - assert(dest->props); - if(CWAL_V_IS_VISITING(dest->props)) return CWAL_RC_IS_VISITING; - else if(h && CWAL_V_IS_VISITING_LIST(dest->props)) return CWAL_RC_IS_VISITING_LIST; - if(dest->props && CWAL_V_IS_IN_CLEANUP(dest->props)){ - assert(!"This really shouldn't be possible unless this " - "scope is destructing, which it's not."); - return CWAL_RC_DESTRUCTION_RUNNING; - } - rc = h - ? cwal_hash_visit_kvp( h, cwal_kvp_visitor_scope_import_props, dest ) - : cwal_props_visit_kvp( src, cwal_kvp_visitor_scope_import_props, dest ); -#if CWAL_OBASE_ISA_HASH - /*MARKER(("src prop count=%d, dest prop count=%d\n", - (int)CWAL_VOBASE(src)->hprops.list.count, - (int)CWAL_VOBASE(dest->props)->hprops.list.count));*/ - if(!h){ - assert(CWAL_VOBASE(dest->props)->hprops.list.count >= CWAL_VOBASE(src)->hprops.list.count); - } -#endif - return rc; - } -} - - -cwal_scope * cwal_scope_parent( cwal_scope * s ){ - return s ? s->parent : 0; -} - -cwal_scope * cwal_scope_top( cwal_scope * s ){ - for( ; s && s->parent; s = s->parent ){} - return s; -} - -cwal_value * cwal_scope_properties( cwal_scope * s ){ - if(!s) return NULL; - else if(!s->props) cwal_scope_init_props(s); - return s->props; -} - -cwal_kvp * cwal_scope_search_kvp_v( cwal_scope * s, - int upToDepth, - cwal_value const * key, - cwal_scope ** foundIn ){ - if(!s || !key) return NULL; - else if(!s->props && !upToDepth) return NULL; - else { - cwal_scope * os; - cwal_kvp * kvp = 0; - if( upToDepth<0 ){ - assert(s->level); - upToDepth = (int)(s->level-1); - } - for( os = s; - os && (upToDepth>=0); - --upToDepth ){ - cwal_hash * const h = CWAL_OBASE_ISA_HASH ? NULL : CWAL_HASH(os->props); - if(h) kvp = cwal_hash_search_kvp_v(h, key); - else if(os->props) kvp = cwal_prop_get_kvp_v( os->props, key, 0, NULL ); - if(kvp){ - if(foundIn) *foundIn = os; - break; - } - else os = os->parent; - } -#if 0 - if(kvp){ - dump_val(os ? os->props : 0,"cwal_scope_search_kvp_v() storage"); - MARKER(("kvp->flags=0x%04x\n", kvp->flags)); - } -#endif - return kvp; - } -} - -cwal_value * cwal_scope_search_v( cwal_scope * s, - int upToDepth, - cwal_value const * key, - cwal_scope ** foundIn ){ - cwal_kvp * kvp = cwal_scope_search_kvp_v(s, upToDepth, key, foundIn); - return kvp ? kvp->value : NULL; -} - -cwal_kvp * cwal_scope_search_kvp( cwal_scope * s, - int upToDepth, - char const * key, - cwal_midsize_t keyLen, - cwal_scope ** foundIn ){ - if(!s || !key) return NULL; - else if(!s->props && !upToDepth) return NULL; - else { - cwal_scope * os; - cwal_kvp * kvp = 0; - if( upToDepth<0 ){ - assert(s->level); - upToDepth = (int)(s->level-1); - } - for( os = s; - os && (upToDepth>=0); - --upToDepth ){ - cwal_hash * const h = CWAL_OBASE_ISA_HASH ? NULL : CWAL_HASH(os->props); - if(h){ - kvp = cwal_hash_search_kvp(h, key, keyLen); - }else if(os->props){ - kvp = cwal_prop_get_kvp( os->props, key, keyLen, 0, 0 ); - } - if(kvp){ - if(foundIn) *foundIn = os; - break; - } - else os = os->parent; - } - return kvp; - } -} - -cwal_value * cwal_scope_search( cwal_scope * s, int upToDepth, - char const * key, - cwal_midsize_t keyLen, - cwal_scope ** foundIn ){ - cwal_kvp * kvp = cwal_scope_search_kvp(s, upToDepth, - key, keyLen, foundIn); - return kvp ? kvp->value : NULL; -} - -int cwal_scope_chain_set_with_flags_v( cwal_scope * s, int upToDepth, - cwal_value * k, cwal_value * v, - uint16_t flags ){ - if(!s || !k) return CWAL_RC_MISUSE; - else if(CWAL_F_IS_DESTRUCTING & s->flags) return CWAL_RC_DESTRUCTION_RUNNING; - else { - int rc; - cwal_scope * os = NULL; - cwal_hash * h; - cwal_kvp * kvp = cwal_scope_search_kvp_v( s, upToDepth, k, &os ); - cwal_obase * ob; - assert(!(s->flags & CWAL_F_IS_DESTRUCTING)); - if(!os){ - os = s; - } - if(os->props && CWAL_RCFLAG_HAS(os->props,CWAL_RCF_IS_DESTRUCTING)){ - return CWAL_RC_DESTRUCTION_RUNNING; - }else if(os->props && CWAL_V_IS_VISITING(os->props)){ - return CWAL_RC_IS_VISITING; - }else if(CWAL_HASH(os->props) && CWAL_V_IS_VISITING_LIST(os->props)){ - return CWAL_RC_IS_VISITING_LIST; - } - ob = CWAL_VOBASE(os->props); - if(ob){ - if(CWAL_CONTAINER_DISALLOW_PROP_SET & ob->flags){ - return CWAL_RC_DISALLOW_PROP_SET; - }else if(!kvp && (CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & ob->flags)){ - return CWAL_RC_DISALLOW_NEW_PROPERTIES; - } - } - if(kvp && (CWAL_VAR_F_CONST & kvp->flags)){ - return CWAL_RC_CONST_VIOLATION; - } -#if 0 - if(kvp){ - MARKER(("kvp flags=0x%08x, new flags=0x%08x\n", (int)kvp->flags, flags)); - dump_val(kvp->key,"key"); - dump_val(kvp->value,"val"); - } -#endif - if(v && kvp){ - /* avoid a second lookup for the property... */ - assert(os->props); - rc = cwal_kvp_value_set2( kvp, v ); - if(!rc) cwal_value_rescope(os->props->scope, v); - return rc; - }else if(!os->props){ - rc = cwal_scope_init_props( os ); - if(rc) return rc; - assert(os->props); - } - if( CWAL_OBASE_ISA_HASH ? NULL : (h = CWAL_HASH(os->props)) ){ - rc = v - ? cwal_hash_insert_with_flags_v( h, k, v, 1, flags ) - : cwal_hash_remove_v(h, k); - if(!rc) rc = cwal_scope_adjust_prop_size(os); - }else{ - rc = cwal_prop_set_with_flags_v( os->props, k, v, flags ); - } - return rc; - } -} - -int cwal_scope_chain_set_v( cwal_scope * s, int upToDepth, - cwal_value * k, cwal_value * v ){ - return cwal_scope_chain_set_with_flags_v(s, upToDepth, k, v, CWAL_VAR_F_PRESERVE); -} - -int cwal_scope_chain_set_with_flags( cwal_scope * s, int upToDepth, - char const * k, cwal_midsize_t keyLen, - cwal_value * v, uint16_t flags ){ - if(!s || !k) return CWAL_RC_MISUSE; - else { - int rc; - cwal_value * kv = cwal_new_string_value(s->e, k, keyLen); - if(!v) return CWAL_RC_OOM; - cwal_value_ref(kv); - rc = cwal_scope_chain_set_with_flags_v( s, upToDepth, kv, v, flags ); - cwal_value_unref(kv); - return rc; - } -} - -int cwal_scope_chain_set( cwal_scope * s, int upToDepth, - char const * k, cwal_midsize_t keyLen, - cwal_value * v ){ - return cwal_scope_chain_set_with_flags( s, upToDepth, k, keyLen, - v, CWAL_VAR_F_PRESERVE ); -} - -int cwal_var_decl_v( cwal_engine * e, cwal_scope * s, - cwal_value * key, cwal_value * v, - uint16_t flags ){ - if(!e || !key) return CWAL_RC_MISUSE; - SETUP_VAR_E_S; - else{ - int const rc = cwal_scope_init_props(s); - if(rc) return rc; - assert(s->props); - if(CWAL_V_IS_VISITING(s->props)) return CWAL_RC_IS_VISITING; - else if(CWAL_HASH(s->props) && CWAL_V_IS_VISITING_LIST(s->props)) return CWAL_RC_IS_VISITING_LIST; - return (cwal_scope_search_v(s, 0, key, NULL) - ? CWAL_RC_ALREADY_EXISTS - : cwal_var_set_v_impl( e, s, key, - v ? v : cwal_value_undefined(), - 0, flags)); - } -} - -int cwal_var_decl( cwal_engine * e, cwal_scope * s, char const * key, - cwal_midsize_t keyLen, cwal_value * v, uint16_t flags ){ - if(!e || !key || !*key) return CWAL_RC_MISUSE; - SETUP_VAR_E_S; - else { - int rc; - cwal_value * k = cwal_new_string_value(e, key, keyLen); - if(!k) return CWAL_RC_OOM; - cwal_value_ref(k); - rc = cwal_var_decl_v(e, s, k, v, flags); - cwal_value_unref(k); - return rc; - } -} - -#undef SETUP_VAR_E_S - -cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v ){ - return (cwal_hash_t) - ((CWAL_TYPE_NULL==v->vtab->typeID) - ? -1 : -2); -} - -cwal_hash_t cwal_value_hash_bool( cwal_value const * v ){ - return (cwal_hash_t) (CWAL_BOOL(v) ? -4 : -3); -} - -cwal_hash_t cwal_value_hash_int( cwal_value const * v ){ - return (cwal_hash_t) *CWAL_INT(v); -} - -cwal_hash_t cwal_value_hash_double( cwal_value const * v ){ -#if CWAL_PLATFORM_ARM - return (cwal_hash_t) cwal_value_get_double(v) - /* See comments below. This formulation does not - bus fault on my ARM, but is slower. */ - ; -#else - return (cwal_hash_t) *CWAL_DBL(v) - /* On my ARM box this is causing a bus fault for the builtin - double 1.0, but not for a dynamically-allocated double! */ - ; -#endif -} - -cwal_hash_t cwal_value_hash_string( cwal_value const * v ){ - cwal_string const * s = CWAL_STR(v); - assert(s); - return cwal_hash_bytes( cwal_string_cstr(s), CWAL_STRLEN(s) ); -} - -cwal_hash_t cwal_value_hash_ptr( cwal_value const * v ){ -#if CWAL_VOID_PTR_IS_BIG - /* IF THE DEBUGGER LEADS YOU NEAR HERE... - try changing ptr_int_t back to uint64_t. - */ - typedef uint64_t ptr_int_t; -#else - typedef uint32_t ptr_int_t; -#endif - return (cwal_hash_t)(((ptr_int_t)v / (ptr_int_t)sizeof(void*)) - + ((ptr_int_t)v % (ptr_int_t)sizeof(void*))); -} - -cwal_hash_t cwal_value_hash_tuple( cwal_value const * v ){ -#if 1 - return cwal_value_hash_ptr(v); -#else - /* needed? If so, do we want to start with the ptr hash or 0? - No - then we cannot use tuples as hash keys (hash value must - be constant).*/ - cwal_hash_t h = cwal_value_hash_ptr(v); - cwal_tuple const * p = CWAL_TUPLE(v); - uint16_t i = 0; - for( ; i < p->n; ++i ) h += cwal_value_hash(p->list[i]); - return h; -#endif -} - -cwal_hash_t cwal_value_hash( cwal_value const * const v ){ - return (v && v->vtab && v->vtab->hash) - ? v->vtab->hash(v) - : 0; -} - -int cwal_value_compare( cwal_value const * lhs, cwal_value const * rhs ){ - if(lhs == rhs) return 0; - else if(!lhs) return -1; - else if(!rhs) return 1; - else return lhs->vtab->compare( lhs, rhs ); -} - -#define COMPARE_TYPE_IDS(L,R) ((L)->vtab->typeID - (R)->vtab->typeID) -/* (((L)->vtab->typeID < (R)->vtab->typeID) ? -1 : 1) */ - -int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs ){ - if(lhs==rhs) return 0; - else if(lhs->vtab->typeID==rhs->vtab->typeID) return - ((void const*)lhs < (void const *)rhs) - ? -1 : 1 /* - Why do this? Because it at least provides - consistent ordering for two different instances - within a given value's lifetime. i can't think of - any saner alternative :/. - */ - ; - else return COMPARE_TYPE_IDS(lhs,rhs); -} - -int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs ){ - if(lhs==rhs) return 0; - else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs); - else { - cwal_function const * l = cwal_value_get_function(lhs); - cwal_function const * r = cwal_value_get_function(rhs); - assert(l); - if(!l) return r ? -1 : 0; - else if(!r) return 1; - else if((l->callback == r->callback) && (l->state.data==r->state.data)) return 0; - else return - ((void const*)lhs < (void const *)rhs) - ? -1 : 1 /* see comments in cwal_value_cmp_ptr_only() */; - } -} - -int cwal_value_cmp_tuple( cwal_value const * lhs, cwal_value const * rhs ){ - if(lhs==rhs) return 0; - else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs); - else { - /* - Compare the cwal_tuple::list parts using normal value - compare semantics... Only compare equivalent if all - entries compare equivalent. - */ - cwal_tuple const * l = CWAL_TUPLE(lhs); - cwal_tuple const * r = CWAL_TUPLE(rhs); - assert(l); - assert(r); - assert(l != r); - if(l->n != r->n) return (int)l->n - (int)r->n; - else{ - cwal_size_t i; - int cmp = 0; - assert(l->n && r->n && "We can't have made it this far with the 0-tuple."); - for( i = 0; i < l->n && !cmp; ++i ){ - cmp = cwal_value_compare(l->list[i], r->list[i]); - } - return cmp; - } - } -} - -int cwal_value_cmp_buffer( cwal_value const * lhs, cwal_value const * rhs ){ - if(lhs==rhs) return 0; - else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs); - else { - cwal_buffer const * l = cwal_value_get_buffer(lhs); - cwal_buffer const * r = cwal_value_get_buffer(rhs); - assert(l); - if(!l) return r ? -1 : 0; - else if(!r) return 1; - else if(l->used == r->used){ - return l->used ? memcmp(l->mem, r->mem, l->used) : 0; - }else{ - cwal_size_t const min = (l->used < r->used) ? l->used : r->used; - int const cmp = min - ? memcmp(l->mem, r->mem, min) - : ((l->used==min) ? -1 : 1); - return cmp ? cmp : ((l->used==min) ? -1 : 1); - } - } -} - -int cwal_compare_cstr( char const * s1, cwal_size_t len1, - char const * s2, cwal_size_t len2 ){ - if(!len1) return len2 ? -1 : 0; - else if(!len2) return 1; - else if(!s1) return s2 ? -1 : 0; - else if(!s2) return 1; - else { - cwal_size_t const max = (len1vtab->typeID==rhs->vtab->typeID) - ? 0 - : COMPARE_TYPE_IDS(lhs,rhs); -} -#endif - -int cwal_value_cmp_nullundef( CWAL_UNUSED_VAR cwal_value const * lhs, cwal_value const * rhs ){ -#ifdef DEBUG - char const isNull = (CWAL_TYPE_NULL==lhs->vtab->typeID) ? 1 : 0; - assert(isNull || CWAL_TYPE_UNDEF==lhs->vtab->typeID); -#endif - switch( rhs->vtab->typeID ){ - case CWAL_TYPE_NULL: - case CWAL_TYPE_UNDEF: - return 0; - default: - return cwal_value_get_bool(rhs) ? -1 : 0; - } -} - -int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs ){ - cwal_string const * s = CWAL_STR(lhs); - assert(s && "lhs is not-a string"); - switch(rhs->vtab->typeID){ -#if 0 - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_EXCEPTION: - /* Seems to be how JS does it. */ - return CWAL_STRLEN(s) ? -1 : 1; -#endif - case CWAL_TYPE_STRING:{ - cwal_string const * r = CWAL_STR(rhs); - return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s), - cwal_string_cstr(r), CWAL_STRLEN(r) ); - } - case CWAL_TYPE_BOOL: /* BOOL here is sooo arguable. */ - return CWAL_STRLEN(s) - ? (cwal_value_get_bool(rhs) ? 0 : 1) - : (cwal_value_get_bool(rhs) ? -1 : 0); -#if 1 - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE:{ - /* FIXME: for number-string conversions it would make - more sense to do it the other way around: convert - the string to a number, then compare. That way we - have no problems with trailing 0's and whatnot. - - 20181127: tried that (see #if'd-out block below) - and it introduce this Damned Weird behaviour: - - var o = {2:'two'}; - assert o.hasOwnProperty(2); // okay - assert o.hasOwnProperty('2'); // FAILS - - And yet: - - var o = {a:1, 2: 'two'} - assert o.hasOwnProperty(2); // okay - assert o.hasOwnProperty('2'); // okay - - That's a bonafide bug but i'm far too tired to chase - it down, so we'll go back to this comparison for - the time being. - */ - enum { BufSize = 100 }; - char buf[BufSize] = {0,}; - cwal_size_t sz = BufSize; - switch(rhs->vtab->typeID){ - case CWAL_TYPE_INTEGER: - cwal_int_to_cstr( cwal_value_get_integer(rhs), buf, &sz); - break; - case CWAL_TYPE_DOUBLE: - cwal_double_to_cstr( cwal_value_get_double(rhs), buf, &sz); - break; - case CWAL_TYPE_BOOL: - /* FIXME? Use "true" and "false" instead of 0 and 1? */ - default: - assert(!"CANNOT HAPPEN"); - break; - } - return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s), - buf, sz ); - } -#else - /* This breaks unit tests */ - case CWAL_TYPE_INTEGER:{ - cwal_int_t i = 0, iR = cwal_value_get_integer(rhs); - cwal_string_to_int( s, &i ); - return i==iR ? 0 : ivtab->typeID); - switch( rhs->vtab->typeID ){ - case CWAL_TYPE_BOOL: - /* reminder: all booleans point either to the same value - (for true) or NULL (for false). */ - return (lhs==rhs) - ? 0 - : (CWAL_BOOL(lhs) ? 1 : -1); - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE:{ - char const l = CWAL_BOOL(lhs); - char const r = cwal_value_get_bool(rhs); - return l==r - ? 0 : (lvtab->compare(rhs, lhs)); -#if 0 - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - return -1; -#endif - default: - return COMPARE_TYPE_IDS(lhs,rhs); - }; -} - -int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs ){ - assert(CWAL_TYPE_INTEGER==lhs->vtab->typeID); - switch( rhs->vtab->typeID ){ - case CWAL_TYPE_DOUBLE:{ - cwal_int_t const l = *CWAL_INT(lhs); - cwal_double_t const r = cwal_value_get_double(rhs); - return l==r ? 0 : (lvtab->compare(rhs, lhs)); -#if 0 - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL:{ - cwal_int_t const l = *CWAL_INT(lhs); - return (l==0) ? 0 : (l<0 ? -1 : 1) - /* in xemacs's font that looks like - one's instead of ell's. - */ - ; - } -#endif - default: - return COMPARE_TYPE_IDS(lhs,rhs); - }; -} - -int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs ){ - assert(CWAL_TYPE_DOUBLE==lhs->vtab->typeID); - switch( rhs->vtab->typeID ){ - case CWAL_TYPE_BOOL: - case CWAL_TYPE_INTEGER:{ - cwal_double_t const l = cwal_value_get_double(lhs); - cwal_int_t const r = cwal_value_get_integer(rhs); - return l==r ? 0 : (lvtab->compare(rhs, lhs)); -#if 0 - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL:{ - cwal_double_t const l = *CWAL_DBL(lhs); - return (l==0.0) ? 0 : (l<0.0 ? -1 : 1); - } -#endif - default: - return COMPARE_TYPE_IDS(lhs,rhs); - }; -} - - -int cwal_stream( cwal_input_f inF, void * inState, - cwal_output_f outF, void * outState ){ - if(!inF || !outF) return CWAL_RC_MISUSE; - else{ - int rc = 0; - enum { BufSize = 1024 * 4 }; - unsigned char buf[BufSize]; - cwal_size_t rn = BufSize; - for( ; !rc && - (rn==BufSize) - && (0==(rc=inF(inState, buf, &rn))); - rn = BufSize){ - if(rn) rc = outF(outState, buf, rn); - else break; - } - return rc; - } -} - -int cwal_output_f_cwal_engine( void * state, void const * src, cwal_size_t n ){ - return cwal_output( (cwal_engine *)state, src, n ); -} - -int cwal_output_f_cwal_outputer( void * state, void const * src, cwal_size_t n ){ - cwal_outputer * out = (cwal_outputer *)state; - if(!src || !n) return 0; - else return out->output ? out->output( out->state.data, src, n ) : 0; -} - -cwal_outputer const * cwal_engine_outputer_get( cwal_engine const * e ){ - return e ? &e->vtab->outputer : 0; -} - -void cwal_engine_outputer_set( cwal_engine * e, - cwal_outputer const * replacement, - cwal_outputer * tgt ){ - assert(e && replacement); - if(tgt) *tgt = e->vtab->outputer; - e->vtab->outputer = *replacement; -} - - -int cwal_buffer_fill_from( cwal_engine * e, cwal_buffer * dest, cwal_input_f src, void * state ) -{ - int rc; - enum { BufSize = 512 * 8 }; - char rbuf[BufSize]; - cwal_size_t total = 0; - cwal_size_t rlen = 0; - if( ! dest || ! src ) return CWAL_RC_MISUSE; - dest->used = 0; - while(1) - { - rlen = BufSize; - rc = src( state, rbuf, &rlen ); - if( rc ) break; - total += rlen; - if(totalcapacity < (total+1) ) - { - rc = cwal_buffer_reserve( e, dest, - total + ((rlenmem + dest->used, rbuf, rlen ); - dest->used += rlen; - if( rlen < BufSize ) break; - } - if( !rc && dest->used ) - { - assert( dest->used < dest->capacity ); - dest->mem[dest->used] = 0; - } - return rc; -} - -int cwal_input_f_FILE( void * state, void * dest, cwal_size_t * n ){ - FILE * f = (FILE*) state; - if( ! state || ! n || !dest ) return CWAL_RC_MISUSE; - else if( !*n ) return CWAL_RC_RANGE; - *n = (cwal_size_t)fread( dest, 1, *n, f ); - return !*n - ? (feof(f) ? 0 : CWAL_RC_IO) - : 0; -} - -int cwal_buffer_fill_from_FILE( cwal_engine * e, cwal_buffer * dest, FILE * src ){ - if(!e || !dest || !src) return CWAL_RC_MISUSE; - else{ - long pos = ftell(src); - int rc = (pos>=0) ? fseek(src, 0, SEEK_END) : -1; - long epos = rc ? 0 : ftell(src); - /* Ignore any tell/fseek-related errors */ - if(!rc){ - rc = fseek(src, pos, SEEK_SET); - if(!rc){ - assert(epos>=pos); - if(epos>pos){ - rc = cwal_buffer_reserve(e, dest, - dest->used + - (cwal_size_t)(epos-pos) + 1); - if(rc) return rc; - } - } - } - if(rc) errno = 0; - return cwal_buffer_fill_from( e, dest, cwal_input_f_FILE, src ); - } -} - - - -int cwal_buffer_fill_from_filename( cwal_engine * e, cwal_buffer * dest, char const * filename ){ - if(!e || !dest || !filename || !*filename) return CWAL_RC_MISUSE; - else{ - int rc; - FILE * src = (('-'==*filename)&&!*(filename+1)) ? stdin : fopen(filename,"rb"); - if(!src) return CWAL_RC_IO; - rc = (stdin==src) - ? cwal_buffer_fill_from( e, dest, cwal_input_f_FILE, src ) - : cwal_buffer_fill_from_FILE( e, dest, src) /* to get pre-allocating behaviour */ - ; - if(stdin!=src) fclose(src); - return rc; - } -} - -int cwal_buffer_fill_from_filename2( cwal_engine * e, cwal_buffer * dest, char const * filename, - cwal_size_t nameLen){ - enum { BufSize = 2048 }; - if(!e || !dest || !filename || !*filename) return CWAL_RC_MISUSE; - else if(!nameLen || nameLen>=(cwal_size_t)BufSize) return CWAL_RC_RANGE; - else{ - char nameBuf[BufSize] = {0}; - memcpy( nameBuf, filename, (size_t)nameLen ); - nameBuf[nameLen] = 0; - return cwal_buffer_fill_from_filename(e, dest, filename); - } -} - - - -int cwal_buffer_clear( cwal_engine * e, cwal_buffer * b ){ - return cwal_buffer_reserve(e, b, 0); -} - -int cwal_buffer_reserve( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ){ - if( !e || !buf ) return CWAL_RC_MISUSE; - else if( 0 == n ){ - if(buf->mem){ - assert(buf->capacity); - assert((cwal_size_t)-1 != buf->capacity); - cwal_memchunk_add(e, buf->mem, buf->capacity); - cwal_buffer_wipe_keep_self(buf); - assert(0==buf->mem); - assert(0==buf->capacity); - assert(0==buf->used); - } - return 0; - } - else if( buf->capacity >= n ){ - return 0; - } - - if(!buf->mem){ - cwal_size_t reqSize = n; - void * mem = cwal_memchunk_request(e, &reqSize, 1000, - "cwal_buffer_reserve()"); - if(mem){ - assert(reqSize>=n); - buf->mem = (unsigned char *)mem; - buf->capacity = reqSize; - buf->mem[0] = 0; - return 0; - } - /* else fall through */ - } - - { - unsigned char * x; -#if 0 - /* A theoretical micro-optimization for the memchunk - recycler. In s2's unit tests (20141206) it gains no - recycling and costs ~80 bytes. */ - if(nmem, n ); - if( ! x ) return CWAL_RC_OOM; - e->metrics.bytes[CWAL_TYPE_BUFFER] += (n - buf->capacity); - memset( x + buf->used, 0, n - buf->used ); - buf->mem = x; - buf->capacity = n; - return 0; - } -} - -cwal_size_t cwal_buffer_fill( cwal_buffer * buf, unsigned char c ){ - if( !buf || !buf->capacity || !buf->mem ) return 0; - else{ - memset( buf->mem, c, buf->capacity ); - return buf->capacity; - } -} - -int cwal_buffer_replace_str( cwal_engine * e, cwal_buffer * self, - unsigned char const * needle, cwal_size_t needleLen, - unsigned char const * repl, cwal_size_t replLen, - cwal_size_t limit, - cwal_size_t * changeCount){ - int rc = 0; - if(!e || !self || !needle || (!repl && replLen>0)) rc = CWAL_RC_MISUSE; - else if(!needleLen) rc = CWAL_RC_RANGE; - if(rc || !self->used || needleLen>self->used){ - /* nothing to do */ - if(changeCount) *changeCount = 0; - return rc; - }else{ - cwal_size_t matchCount = 0; - cwal_size_t slen = self->used; - unsigned char const * pos = self->mem; - unsigned char const * eof = pos + slen; - unsigned char const * start = pos; - cwal_buffer * buf; - cwal_buffer bufLocal = cwal_buffer_empty; - buf = &bufLocal; - while( 1 ){ - if( (pos>=eof) - || (0==memcmp(needle, pos, needleLen)) - ){ - cwal_size_t sz; - char last = (pos>=eof) ? 1 : 0; - if(!last && matchCount++==limit && limit>0){ - pos = eof; - last = 1; - } - else if(pos>eof) pos=eof; - sz = pos-start; - if(sz){ - /* Append pending unmatched part... */ - rc = cwal_buffer_append(e, buf, start, sz); - } - if(!rc && posself; - btmp.self = 0; - *self = *buf; - self->self = vself; - *buf = btmp; - }/*else self->buf is still okay*/ - } - if(changeCount) *changeCount = matchCount; - cwal_buffer_clear(e, buf); - assert(self->capacity > self->used); - self->mem[self->used] = 0; - return rc; - } -} - -int cwal_buffer_replace_byte( cwal_engine * e, cwal_buffer * buf, - unsigned char needle, unsigned char repl, - cwal_size_t limit, - cwal_size_t * changeCount){ - cwal_size_t matchCount = 0; - int rc = 0; - if(!e || !buf){ - rc = CWAL_RC_MISUSE; - }else if(!buf->used || needle==repl){ - /* nothing to do! */ - rc = 0; - }else{ - cwal_size_t i = 0; - for( ; i < buf->used; ++i ){ - if(buf->mem[i] == needle){ - buf->mem[i] = repl; - if(++matchCount && limit && matchCount==limit) break; - } - } - } - if(changeCount) *changeCount = matchCount; - return rc; -} - - - -void cwal_engine_adjust_client_mem( cwal_engine * e, cwal_int_t amount ){ - assert(e); - if(!amount) return; - else if(amount<0){ - cwal_size_t const x = (cwal_size_t)(-amount); - if(x >= e->metrics.clientMemCurrent) e->metrics.clientMemCurrent = 0; - else e->metrics.clientMemCurrent -= x; - }else{ - e->metrics.clientMemCurrent += amount; - e->metrics.clientMemTotal += amount; - } -} - -void cwal_dump_allocation_metrics( cwal_engine * e ){ - int i = 0; - cwal_size_t totalRequests = 0; - cwal_size_t totalAlloced = 0; - cwal_size_t totalSizeof = 0; - cwal_size_t grandTotal = 0; - struct { - char const * note; - int size; - } sizes[] = { - {/*CWAL_TYPE_UNDEF*/ 0, 0}, - {/*CWAL_TYPE_NULL*/ 0, 0}, - {/*CWAL_TYPE_BOOL*/ 0, 0}, - {/*CWAL_TYPE_INTEGER*/ 0, sizeof(cwal_value)+sizeof(cwal_int_t)}, - {/*CWAL_TYPE_DOUBLE*/0, sizeof(cwal_value)+sizeof(cwal_double_t)}, - {/*CWAL_TYPE_STRING*/ "Incl. string bytes plus NULs", sizeof(cwal_value)+sizeof(cwal_string)}, - {/*CWAL_TYPE_ARRAY*/ "Not incl. cwal_list memory", sizeof(cwal_value)+sizeof(cwal_array)}, - {/*CWAL_TYPE_OBJECT*/ 0, sizeof(cwal_value)+sizeof(cwal_object)}, - {/*CWAL_TYPE_FUNCTION*/ 0, sizeof(cwal_value)+sizeof(cwal_function)}, - {/*CWAL_TYPE_EXCEPTION*/ 0, sizeof(cwal_value)+sizeof(cwal_exception)}, - {/*CWAL_TYPE_NATIVE*/ "Not including (opaque/unknown) native memory", sizeof(cwal_value)+sizeof(cwal_native)}, - {/*CWAL_TYPE_BUFFER*/ "[2] Incl. total allocated buffer memory and non-Value buffers", sizeof(cwal_value)+sizeof(cwal_buffer_obj)}, - {/*CWAL_TYPE_HASH*/ "Not incl. cwal_list table memory.", sizeof(cwal_value)+sizeof(cwal_hash)}, - {/*CWAL_TYPE_SCOPE*/ "[3]", sizeof(cwal_scope)}, - {/*CWAL_TYPE_KVP*/ "Key/value pairs (obj. properties/hash entries)", sizeof(cwal_kvp)}, - {/*CWAL_TYPE_WEAK_REF*/ 0, sizeof(cwal_weak_ref)}, - {/*CWAL_TYPE_XSTRING*/ "Not incl. external string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)}, - {/*CWAL_TYPE_ZSTRING*/ "Incl. string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)}, - {/*CWAL_TYPE_UNIQUE*/ 0, sizeof(cwal_value)+sizeof(cwal_value*)}, - {/*CWAL_TYPE_TUPLE*/ "[4]", sizeof(cwal_value)+sizeof(cwal_tuple)}, - {/*CWAL_TYPE_LISTMEM*/ "[5] Total alloced cwal_list::list memory used by arrays, hashtables, ...", 0}, - {0,0} - }; - assert(e); - - - cwal_outputf(e, "%-18s %-16s" - "Actually allocated count * Value-type sizeof() " - "==> total bytes from allocator\n\n", - "TypeId/Bin[1]/Name", - "AllocRequests" - ); - - for( i = CWAL_TYPE_UNDEF; i < CWAL_TYPE_end; ++i ){ - int reIndex; - if(!e->metrics.bytes[i] && !e->metrics.requested[i]) continue; - - reIndex = cwalRecyclerInfo.indexes[i] - /* because the index cwal_recycler_index() - would give for strings is misleading. */ - ; - switch(i){ - case CWAL_TYPE_SCOPE: - /* The stack-allocatedness of these skews the metrics. - Don't count them for grand total statistics. */ - totalRequests += e->metrics.allocated[i]; - break; - default: - totalRequests += e->metrics.requested[i]; - break; - } - totalAlloced += e->metrics.allocated[i]; - totalSizeof += sizes[i].size * e->metrics.allocated[i]; - grandTotal += e->metrics.bytes[i]; - cwal_outputf(e, "%-3d%2d %-15s" - "%-16"CWAL_SIZE_T_PFMT - "%-7"CWAL_SIZE_T_PFMT - "(%06.2f%%) " /* reminder to self: the 06 applies to the _whole_ number! */ - " * %2d " - "==> %-8"CWAL_SIZE_T_PFMT" %s" - "\n", - i, - reIndex, - cwal_type_id_name((cwal_type_id)i), - (cwal_size_t)e->metrics.requested[i], - (cwal_size_t)e->metrics.allocated[i], - e->metrics.requested[i] - ? (double)e->metrics.allocated[i]/e->metrics.requested[i]*100.0 - : 0, - sizes[i].size, - (cwal_size_t)e->metrics.bytes[i], - sizes[i].note ? sizes[i].note : "" - ); - } - - if(totalRequests){ - cwal_outputf(e, - "\nTotals: " - "%-16"CWAL_SIZE_T_PFMT - "%-7"CWAL_SIZE_T_PFMT - "(%06.2f%%)" - "[1] ==> %-8"CWAL_SIZE_T_PFMT - "\n", - (cwal_size_t)totalRequests, - (cwal_size_t)totalAlloced, - (double)totalAlloced/totalRequests*100.0, - (cwal_size_t)grandTotal); - cwal_outputf(e,"\nNotes:\n"); - cwal_outputf(e,"\nThe recycler moves some bits of memory around, " - "counting them only once, so do not search for an Absolute " - "Truth in these metrics!\n"); - cwal_outputf(e,"\n [1] = Types with the same 'bin' value share a " - "recycling bin. A value of -1 indicates either no " - "recycling or a separate mechanism.\n"); - cwal_outputf(e,"\n [2] = " - "%% applies to allocation counts, not sizes. " - "The total alloc count does not account for " - "cwal_buffer::mem (re)allocations, but the total size " - "does. A request/allocation count of 0 and memory >0 " - "means that only non-Value buffers allocated memory.\n"); - cwal_outputf(e, "\n [3] = Scopes are always stack allocated " - "and skew the statistics, so only allocated scopes " - "are counted for totals purposes.\n"); - cwal_outputf(e, "\n [4] = Tuples of all lengths>0, excluding list " - "memory (managed via the chunk recycler).\n"); - cwal_outputf(e, "\n [5] = cwal_list memory is internally a special case: " - "1) 'sizeof()' is meaningless and 2) the allocation count " - "includes re-allocations to larger sizes, but the total includes " - "only the maximum (re)alloc'd size of each list. List memory " - "recycled from other places (e.g. the chunk recycler) is not " - "counted against this type's memory total, but do count as alloc " - "requests.\n"); - } - - if(CWAL_TYPE_UNDEF != e->metrics.highestRefcountType){ - cwal_outputf(e, "\nHighest individual refcount=%d on a value of type '%s'.\n" - "(FYI, that's essentially guaranteed to be one of the " - "base-most prototypes.)\n", - (unsigned)e->metrics.highestRefcount, - cwal_type_id_name( e->metrics.highestRefcountType )); - } - - { /* Recycling bin sizes... */ - int i, head = 0; - unsigned rtotal = 0, binCount = 0, itemCount = 0; - cwal_recycler * re; - cwal_outputf(e, "\nValue/KVP Recycling: %u recycled, " - "%u recycler misses.\n", - (unsigned)e->metrics.valuesRecycled, - (unsigned)e->metrics.valuesRecycleMisses); - for( i = 0; i < (int)cwalRecyclerInfo.recyclerCount; ++i){ - char const * specialCaseLabel = 0; - re = e->recycler + i; - if(!re->id) continue; - if(re->hits || re->misses){ - if(!head){ - ++head; - cwal_outputf(e, "Bin# SlotSize " - "Hits Misses Current# Capacity " - "(recyclable for type(s))\n"); - } - if(re->count){ - rtotal += re->id * re->count; - itemCount += re->count; - ++binCount; - } - cwal_outputf(e, "%3d %-9d %-8d %-7d %-9d %d ", - i, re->id, - (int)re->hits, (int)re->misses, - re->count, re->maxLength); - if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_KVP]){ - specialCaseLabel = cwal_type_id_name(CWAL_TYPE_KVP); - }else if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_WEAK_REF]){ - specialCaseLabel = cwal_type_id_name(CWAL_TYPE_WEAK_REF); - }else if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_SCOPE]){ - specialCaseLabel = cwal_type_id_name(CWAL_TYPE_SCOPE); - } - if(specialCaseLabel){ - cwal_outputf(e, "\t(%s)\n", specialCaseLabel); - }else{ - int x, x2; - for( x2=0, x = CWAL_TYPE_UNDEF; x < CWAL_TYPE_end; ++x ){ - switch((cwal_type_id)x){ - case CWAL_TYPE_KVP: - case CWAL_TYPE_WEAK_REF: - case CWAL_TYPE_SCOPE: - case CWAL_TYPE_STRING: - continue; - default: - if(re->id==(int)cwal_type_id_sizeof((cwal_type_id)x)){ - cwal_outputf(e, "%s%s", (x2 ? ", " : "\t("), - cwal_type_id_name((cwal_type_id)x)); - ++x2; - } - } - } - cwal_outputf(e, "%s\n", x2 ? ")" : ""); - } - } - } - re = &e->reString; - if(re->count){ - ++binCount; - cwal_outputf(e, "strings: %-9d%-8d%-9d %d\n", - (int)re->hits, (int)re->misses, re->count, re->maxLength); - rtotal += cwal_type_id_sizeof(CWAL_TYPE_STRING) * re->count; - } - if(rtotal){ - cwal_outputf(e, "Currently holding %u bytes%s in " - "%u slot(s) in %u bin(s).\n", - rtotal, - re->count ? " (sans raw string bytes)" : "", - itemCount, binCount); - } - } - - { /* cwal_ptr_table memory... */ - int x; - for(x = 0; x < 2; ++x ){ /* 0==string interning table, 1==weak ref table */ - char const * label = 0; - cwal_ptr_table const * ptbl = 0; - switch(x){ - case 0: label = "String interning tables:"; ptbl = &e->interned; break; - case 1: label = "Weak ref tables:"; ptbl = &e->weakp; break; - } - assert(label && ptbl); - if(ptbl->pg.head){ - unsigned i = 0; - unsigned entryCount = 0; - cwal_ptr_page const * h = ptbl->pg.head; - unsigned const pgSize = sizeof(cwal_ptr_page) + - (ptbl->hashSize * sizeof(void*)); - for( ; h ; h = h->next, ++i ){ - entryCount += h->entryCount; - } - grandTotal += i * pgSize; - cwal_outputf(e, "\n%-25s %u page(s) of size %u (%u bytes) ==> %u bytes " - "holding %u entry(ies).\n", - label, - i, (unsigned)ptbl->hashSize, pgSize, - i * pgSize, - entryCount); - } - if(0==x && ptbl->pg.head){ - unsigned totalRefCount = 0; - cwal_ptr_page const * pPage; - cwal_value const * sv; - cwal_string const * str; - uint64_t size1 = 0, size2 = 0; - for( pPage = ptbl->pg.head; pPage; pPage = pPage->next ){ - for(i = 0; i < ptbl->hashSize; ++i ){ - sv = (cwal_value const *)pPage->list[i]; - str = CWAL_STR(sv); - if(str){ - cwal_size_t const len = CWAL_STRLEN(str) + 1/*NUL*/; - size1 += len; - size2 += len * CWAL_REFCOUNT(sv); - totalRefCount += CWAL_REFCOUNT(sv); - } - } - } - cwal_outputf(e, "Total size of all interned strings: %"PRIu64" bytes", - size1); - cwal_outputf(e, ", adjusted for %u refcounts = %"PRIu64" bytes\n", - totalRefCount, size2); - - } - } - } - - if(e->metrics.clientMemTotal){ - cwal_outputf(e,"\nClient-reported memory: currently %u of %u total reported bytes\n", - (unsigned)e->metrics.clientMemCurrent, - (unsigned)e->metrics.clientMemTotal); - grandTotal += e->metrics.clientMemTotal; - } - - cwal_outputf(e, "\nAll built-in Values (sizeof(CWAL_BUILTIN_VALS)): " - "%u static bytes\n", - sizeof(CWAL_BUILTIN_VALS)); - - if(e->buffer.capacity){ - cwal_outputf(e,"\nGeneral-purpose buffer capacity: %u bytes\n", - (unsigned)e->buffer.capacity); - grandTotal += e->buffer.capacity; - } - if(!e->reChunk.metrics.peakChunkCount){ - cwal_outputf(e,"\nChunk recycler went unused.\n"); - }else{ - cwal_memchunk_recycler * re = &e->reChunk; - cwal_outputf(e,"\nChunk recycler capacity=%u chunks, %u bytes.\n", - (unsigned)re->config.maxChunkCount, - (unsigned)re->config.maxTotalSize - ); - cwal_outputf(e,"Chunk requests: %u, Comparisons: %u, Misses: %u, " - "Smallest chunk: %u bytes, Largest: %u bytes\n", - (unsigned)re->metrics.requests, - (unsigned)re->metrics.searchComparisons, - (unsigned)re->metrics.searchMisses, - (unsigned)re->metrics.smallestChunkSize, - (unsigned)re->metrics.largestChunkSize - ); - cwal_outputf(e,"Reused %u chunk(s) totaling %u byte(s), " - "with peaks of %u chunk(s) and %u byte(s).\n", - (unsigned)re->metrics.totalChunksServed, - (unsigned)re->metrics.totalBytesServed, - (unsigned)re->metrics.peakChunkCount, - (unsigned)re->metrics.peakTotalSize - ); - cwal_outputf(e,"Running averages: chunk size: %u, " - "response size: %u\n", - (unsigned)re->metrics.runningAverageSize, - (unsigned)re->metrics.runningAverageResponseSize - ); - if(e->metrics.recoveredSlackCount){ - cwal_outputf(e, "Recovered %u byte(s) of \"slack\" memory " - "from %u block(s) via memory-capping metadata.\n", - (unsigned)e->metrics.recoveredSlackBytes, - (unsigned)e->metrics.recoveredSlackCount); - } - if(re->headCount){ - /* Output list of chunks... */ - cwal_memchunk_overlay * c; - cwal_memchunk_overlay * prev = 0; - int colCount = -1, sameSizeCount = 0, entriesPerLine = 8; - cwal_outputf(e,"Currently contains %u chunk(s) " - "totaling %u byte(s).\n", - (unsigned)re->headCount, (unsigned)re->currentTotal - ); - cwal_outputf(e,"Current chunks, by size:\n"); - for( c = re->head; c; prev = c, c = c->next) { - if(prev){ - assert(prev->size<=c->size); - } - if(prev && prev->size==c->size){ - ++sameSizeCount; - }else{ - if(sameSizeCount){ - cwal_outputf( e, " (x%d)", sameSizeCount+1); - sameSizeCount = 0; - } - if(++colCount == entriesPerLine){ - colCount = 0; - cwal_output( e, ",\n\t", 3); - prev = 0; - }else if(!prev){ - cwal_output( e, "\t", 1); - } - cwal_outputf( e, "%s%u", prev ? ", " : "", - (unsigned)c->size); - } - } - if(sameSizeCount){ - cwal_outputf( e, " (x%d)", sameSizeCount+1); - } - cwal_outputf(e,"\n"); - } - } - if(CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS) - { /* e->metrics.len1StringsSaved[...] */ - int i, showTotals = 0; - cwal_size_t len1TotalCount = 0; - cwal_size_t len1TotalMem = 0; - cwal_size_t const sizeOfStringBase - = sizeof(cwal_value) + sizeof(cwal_string) - /** - The real alloced size for normal stings depends on - CwalConsts.StringPadSize. - */; - cwal_outputf(e, "\nLength-1 ASCII string optimization " - "savings..."); - for( i = 0; i < 3; ++i ){ - cwal_size_t len1This; - cwal_size_t sz; - len1This = e->metrics.len1StringsSaved[i]; - if(len1This) ++showTotals; - len1TotalCount += len1This; - sz = (unsigned)len1This - * (sizeOfStringBase - + (i - ? (unsigned)sizeof(unsigned char **)/*x-/z-strings*/ - : (unsigned)CwalConsts.StringPadSize/*normal strings*/)); - len1TotalMem += sz; - cwal_outputf(e,"\n %13s:\t%u alloc(s), %u bytes", - (0==i ? "plain strings" - : (1==i ? "x-strings" : "z-strings")), - (unsigned)len1This, - (unsigned)sz); - } - if(showTotals>1){ - if(len1TotalMem){ - cwal_outputf(e,"\nTotal savings: %u alloc(s), %u bytes\n", - (unsigned)len1TotalCount, - (unsigned)len1TotalMem); - }else{ - cwal_outputf(e,"\n\tnone :`(\n"); - } - }else{ - cwal_outputf(e,"\n"); - } - } - cwal_outputf(e,"\ncwal_engine instance "); - if(e->allocStamp){ - unsigned int const sz = (unsigned)sizeof(cwal_engine); - cwal_outputf(e,"is malloced: sizeof=%u\n", sz); - grandTotal += sz; - }else{ - cwal_outputf(e,"was not allocated by cwal_engine_init(). " - "sizeof(cwal_engine)=%u\n", - (unsigned)sizeof(cwal_engine)); - } - - - if(CWAL_F_TRACK_MEM_SIZE & e->flags){ - cwal_outputf(e,"\nMemory size tracking/capping enabled:\n"); -#define OUT(OPT) cwal_outputf(e,"\tvtab->memcap.%s = %u\n", #OPT, (unsigned)e->vtab->memcap.OPT) - OUT(maxTotalAllocCount); - OUT(maxTotalMem); - OUT(maxConcurrentAllocCount); - OUT(maxConcurrentMem); - OUT(maxSingleAllocSize); -#undef OUT -#define OUT(OPT) cwal_outputf(e,"\tengine->memcap.%s = %u\n", #OPT, (unsigned)e->memcap.OPT) - OUT(currentMem); - OUT(currentAllocCount); - OUT(peakMem); - OUT(peakAllocCount); - OUT(totalAllocCount); - OUT(totalMem); -#undef OUT - cwal_outputf(e, - "\tOver-allocation overhead =\n" - "\t\tengine->memcap.totalAllocCount (%u) " - "* sizeof(void*) (%u) =\n" - "\t\t%u bytes\n", - (unsigned)e->memcap.totalAllocCount, - (unsigned)sizeof(void*), - (unsigned)(e->memcap.totalAllocCount * sizeof(void*))); - grandTotal += e->memcap.totalAllocCount * sizeof(void*); - } - - - if(grandTotal){ - cwal_outputf(e, "\nTotal bytes allocated for metrics-tracked resources: %"CWAL_SIZE_T_PFMT"\n", - (cwal_size_t)grandTotal); - } - -} - -void cwal_dump_interned_strings_table( cwal_engine * e, - char showEntries, - cwal_size_t includeStrings ){ - enum { BufSize = 1024 }; - char buf[BufSize] = {0,}; - cwal_ptr_table * t = &e->interned; - uint16_t i, x; - int rc; - cwal_output_f f = e->vtab->outputer.output; - void * outState = e->vtab->outputer.state.data; - uint32_t total = 0; - unsigned int pageSize = sizeof(cwal_ptr_page)+(t->hashSize*sizeof(void*)); - unsigned int pageCount = 0; - unsigned totalRefCount = 0; - cwal_ptr_page const * p; - assert( NULL != f ); - assert(0==buf[5]); - assert(e && f); - for( p = t->pg.head; p; p = p->next ) ++pageCount; - - rc = sprintf( buf, "Interned value table for engine@%p: ", (void*)e); -#define RC (cwal_size_t)rc - f( outState, buf, RC ); - rc = sprintf( buf, " Page size=%"PRIu16" (%u bytes) " - "count=%u (%u bytes)\n", - t->hashSize, (unsigned)pageSize, - (unsigned)pageCount, - (unsigned)(pageSize * pageCount)); - f( outState, buf, RC ); - for( i = 0, p = t->pg.head; p; p = p->next, ++i ){ - uint16_t seen = 0; - rc = sprintf( buf, " Page #%d: entry count=%"PRIu16"\n", - (int)i+1, p->entryCount ); - f( outState, buf, RC ); - if(!showEntries){ - total += p->entryCount; - continue; - } - for( x = 0; - (x < t->hashSize); /*&& (seenentryCount);*/ - ++x ){ - cwal_value * v = (cwal_value *)p->list[x]; - if(!v) continue; - ++seen; - rc = sprintf( buf, " #%"PRIu16":\t %s" - "@%p [scope=%d] " - "refcount=%u", - x, v->vtab->typeName, - (void const *)v, (int)v->scope->level, - (unsigned)CWAL_REFCOUNT(v)); - f( outState, buf, RC ); - totalRefCount += CWAL_REFCOUNT(v); - if(includeStrings){ - switch(v->vtab->typeID){ - case CWAL_TYPE_STRING:{ - cwal_string const * s = cwal_value_get_string(v); - rc = sprintf(buf, " len=%u bytes=[", (unsigned)CWAL_STRLEN(s)); - f( outState, buf, RC ); - if(includeStrings >= CWAL_STRLEN(s)){ - f(outState, cwal_string_cstr(s), CWAL_STRLEN(s)); - }else{ - f(outState, cwal_string_cstr(s), includeStrings); - f(outState, ">>>", 3); - } - f( outState, "]", 1 ); - break; - } - case CWAL_TYPE_INTEGER: - rc = sprintf(buf, " value=%"CWAL_INT_T_PFMT, - cwal_value_get_integer(v)); - f( outState, buf, RC ); - break; - case CWAL_TYPE_DOUBLE: - rc = sprintf(buf, " value=%"CWAL_DOUBLE_T_PFMT, - cwal_value_get_double(v)); - f( outState, buf, RC ); - break; - default: - break; - - } - } - f( outState, "\n", 1 ); - } - assert( (seen == p->entryCount) && "Mis-management of p->entryCount detected." ); - if(seen){ - total += seen; - rc = sprintf( buf, " End Page #%d (%"PRIu16" entry(ies))\n", - (int)i+1, p->entryCount ); - f( outState, buf, RC ); - } - } - rc = sprintf(buf, " Total entry count=%"PRIu32", with %u refcount point(s).\n", - total, totalRefCount); - f(outState, buf, RC ); -#undef RC - -} - -void cwal_engine_tracer_close_FILE( void * filePtr ){ - FILE * f = (FILE*)filePtr; - if(f && (f!=stdout) && (f!=stderr) && (f!=stdin)){ - fclose(f); - } -} - -#if CWAL_ENABLE_TRACE -static char const * cwal_tr_cstr( cwal_trace_flags_e ev ){ - switch(ev){ -#define CASE(X) case CWAL_TRACE_##X: return #X - CASE(NONE); - CASE(ALL); - - CASE(GROUP_MASK); - - CASE(MEM_MASK); - CASE(MEM_MALLOC); - CASE(MEM_REALLOC); - CASE(MEM_FREE); - CASE(MEM_TO_RECYCLER); - CASE(MEM_FROM_RECYCLER); - CASE(MEM_TO_GC_QUEUE); - - CASE(VALUE_MASK); - CASE(VALUE_CREATED); - CASE(VALUE_SCOPED); - CASE(VALUE_UNSCOPED); - CASE(VALUE_CLEAN_START); - CASE(VALUE_CLEAN_END); - CASE(VALUE_CYCLE); - CASE(VALUE_INTERNED); - CASE(VALUE_UNINTERNED); - CASE(VALUE_VISIT_START); - CASE(VALUE_VISIT_END); - CASE(VALUE_REFCOUNT); - - CASE(SCOPE_MASK); - CASE(SCOPE_PUSHED); - CASE(SCOPE_CLEAN_START); - CASE(SCOPE_CLEAN_END); - CASE(SCOPE_SWEEP_START); - CASE(SCOPE_SWEEP_END); - - CASE(ENGINE_MASK); - CASE(ENGINE_STARTUP); - CASE(ENGINE_SHUTDOWN_START); - CASE(ENGINE_SHUTDOWN_END); - - CASE(FYI_MASK); - CASE(MESSAGE); - CASE(ERROR_MASK); - CASE(ERROR); - - }; - assert(!"MISSING cwal_tr_cstr() ENTRY!"); - return NULL; -#undef CASE -} -#endif -/*CWAL_ENABLE_TRACE*/ - -void cwal_engine_tracer_f_FILE( void * filePtr, - cwal_trace_state const * ev ){ -#if CWAL_ENABLE_TRACE - FILE * f = (FILE*)filePtr; - if(!f) f = stdout; - fprintf(f, "%s\tengine@%p scope#%"CWAL_SIZE_T_PFMT"@%p", - cwal_tr_cstr(ev->event), - (void const *)ev->e, - ev->scope ? ev->scope->level : 0, - (void const *)ev->scope ); - fprintf(f, "\t%s():%d", - ev->cFunc, ev->cLine ); - - if(ev->msg && *ev->msg) fprintf( f, "\n\t%s", ev->msg ); - fputc( '\n', f ); - - if(ev->value){ - cwal_obase const * b = CWAL_VOBASE(ev->value); - cwal_value const * v = ev->value; - - fprintf(f, "\t%s@%p", v->vtab->typeName, (void*)v); - if(v->scope){ - fprintf(f, "(->scope#%"CWAL_SIZE_T_PFMT"@%p)", - v->scope->level, (void*)v->scope); - } - fprintf(f, " refCount=%"CWAL_SIZE_T_PFMT, CWAL_REFCOUNT(v)); - if(b){ - fprintf( f, " flags=%02x", b->flags ); -#if 0 - fprintf( f, " childCount=%"CWAL_SIZE_T_PFMT, - b->list.count ); -#endif - } - if( cwal_value_is_string(v) ){ - const cwal_size_t truncLen = 16; - cwal_string const * s = cwal_value_get_string(v); - if(s && CWAL_STRLEN(s)){ - fprintf( f, " strlen=%"CWAL_MIDSIZE_T_PFMT, CWAL_STRLEN(s) ); - if( CWAL_STRLEN(s) <= truncLen ){ - fprintf( f, " bytes=[%s]", cwal_string_cstr(s) ); - }else{ - fprintf( f, " bytes=[%.*s...] (truncated)", (int)truncLen, cwal_string_cstr(s) ); - /*fprintf( f, " bytes=[%s]", cwal_string_cstr(s) );*/ - } - }else{ - fprintf( f, " (STILL INITIALIZING!)" ); - } - }else if(cwal_value_is_integer(v)){ - fprintf( f, " value=[%"CWAL_INT_T_PFMT"]",cwal_value_get_integer(v)); - }else if(cwal_value_is_double(v)){ - fprintf( f, " value=[%"CWAL_DOUBLE_T_PFMT"]", cwal_value_get_double(v)); - }else if(cwal_value_is_bool(v)){ - fprintf( f, " value=[%s]", cwal_value_get_bool(v) ? "true" : "false" ); - } - fputc( '\n', f ); - } - if(ev->memory||ev->memorySize){ - fputc('\t',f); - if( ev->memory) fprintf(f, "memory=%p ", ev->memory); - if( ev->memorySize) fprintf(f, "(%"CWAL_SIZE_T_PFMT" bytes)", - ev->memorySize); - fputc('\n',f); - } - - fflush(f); -#else - if(ev || filePtr){/*avoid unused param warning*/} -#endif -} - -uint32_t cwal_engine_feature_flags( cwal_engine * e, int32_t mask ){ - if(!e) return -1; - else{ - uint32_t const rc = e->flags & CWAL_FEATURE_MASK; - if(mask>0){ - e->flags &= ~CWAL_FEATURE_MASK; - e->flags |= CWAL_FEATURE_MASK & mask; - } - /** If auto-interning was disabled, free up the - table memory. - */ - if((CWAL_FEATURE_INTERN_STRINGS & rc) - && !(CWAL_FEATURE_INTERN_STRINGS & e->flags)){ - cwal_ptr_table_destroy( e, &e->interned ); - } - return rc; - } -} - -int32_t cwal_engine_trace_flags( cwal_engine * e, int32_t mask ){ -#if !CWAL_ENABLE_TRACE - if(e || mask){/*avoid unused param warning*/} - return -1; -#else - if(!e) return -1; - else if(-1==mask) return e->trace.mask; - else { - int32_t const rc = e->trace.mask; - e->trace.mask = mask; - return rc; - } -#endif -} -void cwal_trace( cwal_engine * e ){ -#if CWAL_ENABLE_TRACE - int32_t const m = e->trace.mask; - /*MARKER("TRACE()? ev=%08x mask=%08x\n", e->trace.event, e->trace.mask);*/ - - if(! - ((m & (CWAL_TRACE_GROUP_MASK&e->trace.event)) - /*&& (e->trace.mask & (~CWAL_TRACE_GROUP_MASK&e->trace.event))*/ - )){ - goto end; - } - else if(!e->vtab->tracer.trace) goto end; - else { - if( e->trace.msg && !e->trace.msgLen && *e->trace.msg ){ - e->trace.msgLen = cwal_strlen(e->trace.msg); - } - e->vtab->tracer.trace( e->vtab->tracer.state, &e->trace ); - /* fall through */ - } - end: - e->trace = cwal_trace_state_empty; - e->trace.e = e; - e->trace.mask = m; - assert(0==e->trace.msg); - assert(0==e->trace.msgLen); -#else - if(e){/*avoid unused param warning*/} -#endif -} - -#define CwalRcFallbackCount 8 -static struct { - /* List of fallback handlers for cwal_rc_cstr() */ - unsigned short count; - cwal_rc_cstr_f entries[CwalRcFallbackCount]; -} CwalRcFallbacks = { -0, {0} -}; - -void cwal_rc_cstr_fallback(cwal_rc_cstr_f f){ - if(CwalRcFallbacks.count==CwalRcFallbackCount){ - assert(!"Too many cwal_rc_cstr_fallback() registrations."); - fprintf(stderr,"%s:%d: " - "Too many cwal_rc_cstr_fallback() registrations!\n", - __FILE__, __LINE__); - abort(); - } - CwalRcFallbacks.entries[CwalRcFallbacks.count++] = f; -} -#undef CwalRcFallbackCount - - -char const * cwal_rc_cstr(int rc) -{ - char const * s = cwal_rc_cstr2(rc); - return s ? s : "Unknown result code"; -} - -char const * cwal_rc_cstr2(int rc) -{ - switch(rc){ -#define C(N) case N: return #N - C(CWAL_RC_OK); - C(CWAL_RC_ERROR); - C(CWAL_RC_OOM); - C(CWAL_RC_FATAL); - - C(CWAL_RC_CONTINUE); - C(CWAL_RC_BREAK); - C(CWAL_RC_RETURN); - C(CWAL_RC_EXIT); - C(CWAL_RC_EXCEPTION); - C(CWAL_RC_ASSERT); - - C(CWAL_RC_MISUSE); - C(CWAL_RC_NOT_FOUND); - C(CWAL_RC_ALREADY_EXISTS); - C(CWAL_RC_RANGE); - C(CWAL_RC_TYPE); - C(CWAL_RC_UNSUPPORTED); - C(CWAL_RC_ACCESS); - - C(CWAL_RC_IS_VISITING); - C(CWAL_RC_IS_VISITING_LIST); - C(CWAL_RC_DISALLOW_NEW_PROPERTIES); - C(CWAL_RC_DISALLOW_PROP_SET); - C(CWAL_RC_DISALLOW_PROTOTYPE_SET); - C(CWAL_RC_CONST_VIOLATION); - C(CWAL_RC_LOCKED); - - C(CWAL_RC_CYCLES_DETECTED); - C(CWAL_RC_DESTRUCTION_RUNNING); - C(CWAL_RC_FINALIZED); - C(CWAL_RC_HAS_REFERENCES); - C(CWAL_RC_INTERRUPTED); - C(CWAL_RC_CANCELLED); - C(CWAL_RC_IO); - C(CWAL_RC_CANNOT_HAPPEN); - - C(CWAL_RC_JSON_INVALID_CHAR); - C(CWAL_RC_JSON_INVALID_KEYWORD); - C(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE); - C(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE); - C(CWAL_RC_JSON_INVALID_NUMBER); - C(CWAL_RC_JSON_NESTING_DEPTH_REACHED); - C(CWAL_RC_JSON_UNBALANCED_COLLECTION); - C(CWAL_RC_JSON_EXPECTED_KEY); - C(CWAL_RC_JSON_EXPECTED_COLON); - - C(CWAL_SCR_CANNOT_CONSUME); - C(CWAL_SCR_INVALID_OP); - C(CWAL_SCR_UNKNOWN_IDENTIFIER); - C(CWAL_SCR_CALL_OF_NON_FUNCTION); - C(CWAL_SCR_MISMATCHED_BRACE); - C(CWAL_SCR_MISSING_SEPARATOR); - C(CWAL_SCR_UNEXPECTED_TOKEN); - C(CWAL_SCR_UNEXPECTED_EOF); - C(CWAL_SCR_DIV_BY_ZERO); - C(CWAL_SCR_SYNTAX); - C(CWAL_SCR_EOF); - C(CWAL_SCR_TOO_MANY_ARGUMENTS); - C(CWAL_SCR_EXPECTING_IDENTIFIER); - - C(CWAL_RC_CLIENT_BEGIN); - default: - if(CwalRcFallbacks.count){ - unsigned short i = CwalRcFallbacks.count; - char const * str = 0; - for(; i>0 ; --i){ - str = CwalRcFallbacks.entries[i-1](rc); - if(str) return str; - }} - return 0; - } -#undef C -} - -int cwal_prototype_base_set( cwal_engine * e, cwal_type_id t, cwal_value * proto ){ - if(!e) return CWAL_RC_MISUSE; - else if(!cwal_engine_prototypes(e)) return CWAL_RC_OOM; - else{ - assert((t>=0) && (tvalues.prototypes, (cwal_size_t)t, proto ); - } -} - -cwal_value * cwal_prototype_base_get( cwal_engine * e, cwal_type_id t ){ - if(!e || !e->values.prototypes) return NULL; - else{ - assert((t>=0) && (tvalues.prototypes, (cwal_size_t)t); - } -} - - -int cwal_value_prototype_set( cwal_value * v, cwal_value * prototype ){ - cwal_obase * child = CWAL_VOBASE(v); - cwal_obase * chPro = child ? CWAL_VOBASE(prototype) : NULL; - cwal_obase * pBase; - cwal_value * pCheck; -#if 0 - cwal_engine * e; -#endif - if((prototype && !chPro) || CWAL_MEM_IS_BUILTIN(v)){ - return CWAL_RC_TYPE; - }else if(!child || (child == chPro)){ - return CWAL_RC_MISUSE; - } - if(CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET & child->containerFlags){ - return CWAL_RC_DISALLOW_PROTOTYPE_SET; - } - if( prototype == child->prototype ){ - return 0; - } -#if 0 - e = v->scope ? v->scope->e : (prototype->scope ? prototype->scope->e : 0); - assert(e); -#endif - if(prototype){ - /* Ensure that v is nowhere in prototype's chain. */ - /* - TODO: Strictly speaking, there's little reason to disallow - non-containers as prototypes. - - Maybe refactor to use cwal_prototype_base_get() for - non-containers and allow non-container prototypes. - */ - for( pCheck = prototype; pCheck ; ){ - if(pCheck == v) return CWAL_RC_CYCLES_DETECTED; - pBase = CWAL_VOBASE(pCheck); - pCheck = (pBase?pBase->prototype:NULL); - } - cwal_value_ref2( v->scope->e, prototype ); - cwal_value_rescope( v->scope, prototype ); - } - if(child->prototype){ - cwal_value_unref/* _from */(/* child->scope, */child->prototype ); - } - child->prototype = prototype; - return 0; -} - -cwal_value * cwal_value_prototype_get(cwal_engine * e, cwal_value const * v){ - if(!v) return NULL; - else{ - cwal_obase const * b = CWAL_VOBASE(v); - if(b) return b->prototype; - else{ - if(!e && v->scope) e = v->scope->e; - return cwal_prototype_base_get(e, v->vtab->typeID); - } - } -} - -bool cwal_value_derives_from( cwal_engine * e, - cwal_value const * v, - cwal_value const * p ){ - if(!e || !v || !p) return 0; - else if(v==p) return 1; - else { - cwal_type_id const tid = v->vtab->typeID; - while(v){ - cwal_obase const * base = CWAL_VOBASE(v); - cwal_value const * theP = - base ? base->prototype : cwal_prototype_base_get(e, tid); - if(p==theP) return 1; - else v = theP; - } - return 0; - } -} - - -cwal_object * cwal_value_object_part( cwal_engine * e, - cwal_value * v ){ - cwal_object * obj; - do{ - if( (obj = CWAL_OBJ(v)) ) return obj; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_buffer * cwal_value_buffer_part( cwal_engine * e, - cwal_value * v ){ - cwal_buffer_obj * f; - do{ - if( (f = CWAL_BUFOBJ(v)) ) return &f->buf; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_native * cwal_value_native_part( cwal_engine * e, - cwal_value * v, - void const * typeID){ - cwal_native * n; - do{ - if( (n = cwal_value_get_native(v)) - && (!typeID || typeID==n->typeID) ){ - return n; - } - v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_function * cwal_value_function_part( cwal_engine * e, - cwal_value * v ){ - cwal_function * f; - do{ - if( (f = cwal_value_get_function(v)) ) return f; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_array * cwal_value_array_part( cwal_engine * e, - cwal_value * v ){ - cwal_array * a; - do{ - if( (a = CWAL_ARRAY(v)) ) return a; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_exception * cwal_value_exception_part( cwal_engine * e, - cwal_value * v ){ - cwal_exception * f; - do{ - if( (f = cwal_value_get_exception(v)) ) return f; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_hash * cwal_value_hash_part( cwal_engine * e, - cwal_value * v ){ - cwal_hash * f; - do{ - if( (f = CWAL_HASH(v)) ) return f; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_string * cwal_value_string_part( cwal_engine * e, - cwal_value * v ){ - cwal_string * f; - do{ - if( (f = CWAL_STR(v)) ) return f; - else v = cwal_value_prototype_get(e, v); - }while(v); - return NULL; -} - -cwal_value * cwal_value_container_part( cwal_engine * e, cwal_value * v ){ - while(v){ - if(cwal_props_can(v)) return v; - v = cwal_value_prototype_get(e, v); - } - return 0; -} - -cwal_size_t cwal_list_reserve( cwal_engine * e, cwal_list * self, cwal_size_t n ) -{ - assert(e); - assert(self); - if( !e || !self ) return 0; - else if(0 == n) - { - if(0 == self->alloced) return 0; - cwal_memchunk_add(e, self->list, self->alloced * sizeof(void*)); - self->list = NULL; - self->alloced = self->count = 0; - return 0; - } - if( self->alloced >= n ){ - return self->alloced; - } - METRICS_REQ_INCR(e,CWAL_TYPE_LISTMEM); - if(!self->list){ - /* Check the e->reChunk recycler... */ - cwal_size_t reqSize = n * sizeof(void*); - void** m = (void**)cwal_memchunk_request( e, &reqSize, 1000, - "cwal_list_reserve(n)"); - if(m){ - self->alloced = reqSize/sizeof(void*); - self->list = m; - assert(self->alloced >= n); - /*MARKER(("list recycler got: n=%d, reqSize=%d, self->alloced=%d\n", - (int)n, (int)reqSize, (int)self->alloced));*/ - return self->alloced; - } - /* else fall through... */ - } - { - size_t const sz = sizeof(void*) * n; - cwal_size_t i; - void ** m = (void**)cwal_realloc( e, self->list, sz ); - if( ! m ) return self->alloced; -#if 0 - memset( m + self->alloced, 0, (sizeof(void*)*(n-self->alloced))); -#else - for( i = self->count; i < n; ++i ) m[i] = NULL; -#endif - ++e->metrics.allocated[CWAL_TYPE_LISTMEM] - /* This isn't _quite_ pedantically true, as it counts - reallocs as allocs, and we don't _really_ know if the - system actually had to realloc the memory, but it'll - do. We "could" only increment this is self->alloced==0, - but that would also not be quite right. Better to err - on the side of reporting more allocs (via realloc) - here. */; - e->metrics.bytes[CWAL_TYPE_LISTMEM] += (n - self->alloced) * sizeof(void*); - self->alloced = n; - self->list = m; - return n; - } -} - -int cwal_list_append( cwal_engine * e, cwal_list * self, void* cp ) -{ - if( !e || !self || !cp ) return CWAL_RC_MISUSE; - else if( self->alloced > cwal_list_reserve(e, self, self->count+1) ) - { - return CWAL_RC_OOM; - } - else - { - self->list[self->count++] = cp; - - if(self->count< self->alloced-1) self->list[self->count]=0; - - return 0; - } -} - -int cwal_list_visit( cwal_list * self, int order, - cwal_list_visitor_f visitor, void * visitorState ) -{ - int rc = CWAL_RC_OK; - if( self && self->count && visitor ) - { - cwal_size_t i = 0; - cwal_size_t pos = (order<0) ? self->count-1 : 0; - int step = (order<0) ? -1 : 1; - for( rc = 0; (i < self->count) && (0 == rc); ++i, pos+=step ) - { - - void* obj = self->list[pos]; - if(obj) rc = visitor( obj, visitorState ); - if( obj != self->list[pos] ){ - --i; - if(order>=0) pos -= step; - } - } - } - return rc; -} - - -int cwal_list_visit_p( cwal_list * self, int order, - char shiftIfNulled, - cwal_list_visitor_f visitor, void * visitorState ) -{ - int rc = CWAL_RC_OK; - if( self && self->count && visitor ) - { - int i = 0; - int pos = (order<0) ? self->count-1 : 0; - int step = (order<0) ? -1 : 1; - for( rc = 0; (i < (int)self->count) && (0 == rc); ++i, pos+=step ) - { - void* obj = self->list[pos]; - if(obj) { - assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!"); - rc = visitor( &self->list[pos], visitorState ); - if( shiftIfNulled && !self->list[pos]){ - int x = pos; - int const to = self->count-pos; - - assert( to < (int) self->alloced ); - for( ; x < to; ++x ) self->list[x] = self->list[x+1]; - if( x < (int)self->alloced ) self->list[x] = 0; - --i; - --self->count; - if(order>=0) pos -= step; - } - } - } - } - return rc; -} - - -cwal_value * cwal_value_from_arg(cwal_engine * e, - char const *arg){ - cwal_size_t sLen; - if(!e) return NULL; - else if(!arg) return cwal_value_null(); - sLen = cwal_strlen(arg); - if((('0'>*arg) || ('9'<*arg)) && ('+'!=*arg) && ('-'!=*arg)){ - goto do_string; - } - else{ /* try numbers... */ - cwal_int_t nI = 0; - cwal_double_t nD = 0.0; - if(0==cwal_cstr_to_int(arg, sLen, &nI)){ - return cwal_new_integer(e, (cwal_int_t)nI); - } -#if 1 - /* cwal_cstr_to_double() might have some overflow-related - problems (not certain - we've never made heavy use of - doubles in client code!). If so, we can fall back - to strtod(). */ - else if(0==cwal_cstr_to_double(arg, sLen, &nD)){ - return cwal_new_double(e, nD); - } -#else - else { - char const * end = 0; - double const val = strtod(arg, &end); - if(!*end){ - return cwal_new_double(e, val); - } - } -#endif - /* fall through and treat it like a string... */ - } - do_string: - if(4==sLen){ - if(0==strcmp("true",arg)) return cwal_value_true(); - if(0==strcmp("null",arg)) return cwal_value_null(); - } - else if(5==sLen && 0==strcmp("false",arg)) return cwal_value_false(); - else if(9==sLen && 'u'==*arg && 0==strcmp("undefined",arg)) return cwal_value_undefined(); - else if(sLen>1 && (('\''==*arg && '\''==arg[sLen-1]) - ||('"'==*arg && '"'==arg[sLen-1]))){ - /* trim quote marks */ - ++arg; - sLen -= 2; - } - return cwal_new_string_value(e, arg, sLen); -} - - -int cwal_parse_argv_flags( cwal_engine * e, - int argc, char const * const * argv, - cwal_value ** tgt ){ - cwal_value * o = NULL; - cwal_value * flags = NULL; - cwal_array * nonFlags = NULL; - cwal_array * tgtArray = 0; - char const allocateTgt = (tgt && *tgt) ? 0 : 1; - int rc = 0; - int i = 0; - if(!e || !tgt) return CWAL_RC_MISUSE; - else if(*tgt && !cwal_props_can(*tgt)) return CWAL_RC_MISUSE; - else if(argc<0) return CWAL_RC_RANGE; - - flags = cwal_new_object_value(e); - if(!flags) return CWAL_RC_OOM; - cwal_value_ref(flags); - cwal_value_prototype_set(flags, 0); - nonFlags = cwal_new_array(e); - if(!nonFlags) { - cwal_value_unref(flags); - return CWAL_RC_OOM; - } - cwal_value_ref(cwal_array_value(nonFlags)); - o = allocateTgt ? cwal_new_object_value(e) : *tgt; - if(!o){ - cwal_value_unref(flags); - cwal_value_unref(cwal_array_value(nonFlags)); - return CWAL_RC_OOM; - } - if(allocateTgt) cwal_value_ref(o); - else{ - tgtArray = cwal_value_get_array(o); - if(tgtArray){ - cwal_array_reserve( tgtArray, - cwal_array_length_get(tgtArray) - + (unsigned)argc ); - } - } - rc = cwal_prop_set(o, "flags", 5, flags); - if(rc){ - goto end; - } - rc = cwal_prop_set(o, "nonFlags", 8, cwal_array_value(nonFlags)); - if(rc){ - goto end; - } - for( i = 0; i < argc; ++i ){ - char const * arg = argv[i]; - char const * key = arg; - char const * pos; - cwal_value * k = NULL; - cwal_value * v = NULL; - char invertFlag = 0; - if(!arg) continue; - else if(tgtArray){ - v = cwal_new_string_value(e, arg, cwal_strlen(arg)); - if(v){ - cwal_value_ref(v); - rc = cwal_array_append(tgtArray, v); - cwal_value_unref(v); - }else{ - rc = CWAL_RC_OOM; - } - if(rc) break; - } - if('+' == *arg){ - invertFlag = 1; - ++key; - } - else if('-' != *arg){ - v = cwal_new_string_value(e, arg, cwal_strlen(arg)); - if(!v){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(v); - rc = cwal_array_append(nonFlags, v); - cwal_value_unref(v); - if(rc) break; - continue; - } - while('-'==*key) ++key; - if(!*key) continue; - pos = key; - while( *pos && ('=' != *pos)) ++pos; - k = cwal_new_string_value(e, key, pos-key); - if(!k){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(k); - if(!*pos){ /** --key or +key */ - v = invertFlag - ? cwal_value_false() - : cwal_value_true(); - }else{ /** --key=...*/ - assert('=' == *pos); - ++pos /*skip '='*/; - v = cwal_value_from_arg(e, pos); - if(!v){ - cwal_value_unref(k); - rc = CWAL_RC_OOM; - break; - } - } - cwal_value_ref(v); - rc=cwal_prop_set_v(flags, k, v); - cwal_value_unref(k); - cwal_value_unref(v); - if(rc) break; - } - end: - /* On success, o holds refs to flags/nonFlags via - its properties. */ - cwal_value_unref(cwal_array_value(nonFlags)); - cwal_value_unref(flags); - if(rc){ - if(allocateTgt) cwal_value_unref(o); - else assert(o == *tgt); - }else{ - if(allocateTgt){ - *tgt = o; - cwal_value_unhand(o); - }else{ - assert(o == *tgt); - } - } - return rc; -} - -bool cwal_strtok( char const ** inp, char separator, - char const ** end ){ - char const * pos = NULL; - assert( inp && end && *inp ); - if( ! inp || !end ) return 0; - else if( *inp == *end ) return 0; - pos = *inp; - if( !*pos ) - { - *end = pos; - return 0; - } - for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ } - *inp = pos; - for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ } - *end = pos; - return (pos > *inp) ? 1 : 0; -} - -int cwal_prop_fetch_sub( cwal_value * obj, cwal_value ** tgt, char const * path, char sep ) -{ - if( ! obj || !path ) return CWAL_RC_MISUSE; - else if( !*path || !sep ) return CWAL_RC_RANGE; - else{ - char const * beg = path; - char const * end = NULL; - unsigned int i, len; - unsigned int tokenCount = 0; - cwal_value * cv = NULL; - cwal_value * curObj = obj; - enum { BufSize = 128 }; - char buf[BufSize]; - memset( buf, 0, BufSize ); - - while( cwal_strtok( &beg, sep, &end ) ){ - if( beg == end ) break; - else{ - ++tokenCount; - beg = end; - end = NULL; - } - } - if( 0 == tokenCount ) return CWAL_RC_RANGE; - beg = path; - end = NULL; - for( i = 0; i < tokenCount; ++i, beg=end, end=NULL ){ - CWAL_UNUSED_VAR char const rc = cwal_strtok( &beg, sep, &end ); - assert( 1 == rc ); - assert( beg != end ); - assert( end > beg ); - len = end - beg; - if( len > (BufSize-1) ) return CWAL_RC_RANGE; - /* memset( buf, 0, len + 1 ); */ - memcpy( buf, beg, len ); - buf[len] = 0; - cv = cwal_prop_get( curObj, buf, len ); - if( NULL == cv ) return CWAL_RC_NOT_FOUND; - else if( i == (tokenCount-1) ){ - if(tgt) *tgt = cv; - return 0; - } - else if( cwal_props_can(cv) ){ - curObj = cv; - } - /* TODO: arrays. Requires numeric parsing for the index. */ - else{ - return CWAL_RC_NOT_FOUND; - } - } - assert( i == tokenCount ); - return CWAL_RC_NOT_FOUND; - } -} - -int cwal_prop_fetch_sub2( cwal_value * obj, cwal_value ** tgt, char const * path ) -{ - if( ! obj || !path ) return CWAL_RC_MISUSE; - else if( !*path || !*(1+path) ) return CWAL_RC_RANGE; - else return cwal_prop_fetch_sub(obj, tgt, path+1, *path); -} - - -cwal_value * cwal_prop_get_sub( cwal_value * obj, char const * path, char sep ) -{ - cwal_value * v = NULL; - cwal_prop_fetch_sub( obj, &v, path, sep ); - return v; -} - -cwal_value * cwal_prop_get_sub2( cwal_value * obj, char const * path ) -{ - cwal_value * v = NULL; - cwal_prop_fetch_sub2( obj, &v, path ); - return v; -} - -int cwal_callback_hook_set(cwal_engine * e, cwal_callback_hook const * h ){ - if(!e) return CWAL_RC_MISUSE; - e->cbHook = h ? *h : cwal_callback_hook_empty; - return 0; -} - -cwal_midsize_t cwal_strlen( char const * str ){ - return str ? (cwal_midsize_t)strlen(str) : 0U; -} - -bool cwal_value_is_vacuum_proof( cwal_value const * v ){ - return v->scope - ? CWAL_V_IS_VACUUM_SAFE(v) - : CWAL_MEM_IS_BUILTIN(v); -} - -int cwal_value_make_vacuum_proof( cwal_value * v, char yes ){ - if(!v) return CWAL_RC_MISUSE; - else if(CWAL_MEM_IS_BUILTIN(v)) return 0; - else if(yes && CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VACUUM_PROOF)){ - return 0; - }else if(!yes && !CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VACUUM_PROOF)){ - return 0; - }else{ - cwal_scope * s = v->scope; - assert(s); - if(yes) CWAL_RCFLAG_ON(v, CWAL_RCF_IS_VACUUM_PROOF); - else CWAL_RCFLAG_OFF(v, CWAL_RCF_IS_VACUUM_PROOF); - return cwal_scope_insert(s, v); - } -} - -int cwal_scope_vacuum( cwal_scope * s, int * sweepCount ){ - cwal_engine * e = s->e; - int rc = 0; - cwal_scope s2 = cwal_scope_empty; - cwal_size_t origCount = 0; -#define SHOWCOUNTS 0 -#if SHOWCOUNTS - cwal_value * check = 0; -#endif - if(!e || !s) return CWAL_RC_MISUSE; - assert(s->level>0); - assert(s->parent || (1==s->level/*top scope*/)); - if(!s->props && !s->mine.headSafe){ - /* s has no safe properties, so we clean up everything... */ - if(sweepCount){ - *sweepCount = (int)cwal_scope_value_count(s); - } - cwal_scope_clean(e, s); - return 0; - } - -# define VLIST_COUNT(WHO) check = WHO; rc = 0; while(check){++rc; check=check->right;}(void)0 -# define VCOUNT(WHO) VLIST_COUNT(WHO); cwal_outputf(e, #WHO" count: %d\n", rc); rc = 0 - if(sweepCount){ - /* Count starting number of Values in the scope ... */ - origCount = cwal_scope_value_count(s); - } -#if SHOWCOUNTS - VCOUNT(s->mine.headPod); - VCOUNT(s->mine.headObj); - VCOUNT(s->mine.headSafe); - VCOUNT(s->mine.r0); -#endif - - s2.level = s->level - 1; - s2.parent = s->parent; - s->parent = &s2; - s2.e = e; - if(s->props){ - cwal_value * pv = s->props; -#ifdef DEBUG - cwal_obase * obase = CWAL_VOBASE(pv); - assert(obase->flags & CWAL_F_IS_PROP_STORAGE); -#endif - cwal_value_rescope( &s2, pv ); - assert(pv->scope == &s2); - s2.props = s->props; - s->props = 0 /* transfer ref point */; - } - while(s->mine.headSafe){ -#ifdef DEBUG - cwal_value const * vcheck = s->mine.headSafe; -#endif - cwal_value_rescope( &s2, s->mine.headSafe ); -#ifdef DEBUG - assert(s->mine.headSafe != vcheck); -#endif - } - /* Clean up, fake a lower/newer scope level, - and move the vars back... */ - cwal_scope_clean( e, s ); - -#if SHOWCOUNTS - VCOUNT(s->mine.headPod); - VCOUNT(s->mine.headObj); - VCOUNT(s->mine.headSafe); - VCOUNT(s->mine.r0); - - VCOUNT(s2.mine.headPod); - VCOUNT(s2.mine.headObj); - VCOUNT(s2.mine.headSafe); - VCOUNT(s2.mine.r0); -#endif - - /* Make s2 be s's child */ - s->parent = s2.parent; - s2.parent = s; - s2.level = s->level + 1; - /* Move properties and vacuum-proof values back to s */ - if(s2.props){ - cwal_value_rescope( s, s2.props ); - assert(s2.props->scope == s); - s->props = s2.props; - s2.props = 0 /* transfer ref point */; - } - while(s2.mine.headSafe){ -#ifdef DEBUG - cwal_value const * vcheck = s2.mine.headSafe; -#endif - cwal_value_rescope( s, s2.mine.headSafe ); -#ifdef DEBUG - assert(s2.mine.headSafe != vcheck); -#endif - } - -#if 0 - if(e->values.propagating && e->values.propagating->scope==&s2){ - cwal_value_rescope(s, e->values.propagating); - } - if(e->values.exception && e->values.exception->scope==&s2){ - cwal_value_rescope(s, e->values.exception); - } -#else - cwal_scope_clean(e, &s2) /* move specially-propagating values */; -#endif - assert(0==s2.mine.r0); - assert(0==s2.mine.headPod); - assert(0==s2.mine.headObj); - assert(0==s2.mine.headSafe); - assert(0==s2.props); - if(sweepCount){ - cwal_size_t newCount = cwal_scope_value_count(s); -#if SHOWCOUNTS - VCOUNT(s->mine.headPod); - VCOUNT(s->mine.headObj); - VCOUNT(s->mine.headSafe); - VCOUNT(s->mine.r0); - - VCOUNT(s2.mine.headPod); - VCOUNT(s2.mine.headObj); - VCOUNT(s2.mine.headSafe); - VCOUNT(s2.mine.r0); - MARKER(("origCount=%d, newCount=%d\n", (int)origCount, (int)newCount)); -#endif - /* - Having more items than before is a sign that we imported - more than we should have. - */ - assert(newCount <= origCount); - *sweepCount = (int)origCount - (int)newCount; - } -#if 0 - cwal_scope_clean(e, &s2) /* not strictly necessary - s2 must - be empty by now or our universe's - physics are all wrong. */; -#endif -#undef SHOWCOUNTS -#undef VCOUNT -#undef VLIST_COUNT - return rc; -} - -int cwal_engine_vacuum( cwal_engine * e, int * sweepCount ){ - cwal_scope * s = e ? e->current : 0; - if(!e || !s) return CWAL_RC_MISUSE; - return cwal_scope_vacuum( s, sweepCount ); -} - -cwal_flags16_t cwal_container_flags_set( cwal_value * v, cwal_flags16_t flags ){ - cwal_obase * b = CWAL_VOBASE(v); - if(!b) return 0; - else{ - cwal_flags16_t const rc = b->containerFlags; - b->containerFlags = flags; - return rc; - } -} - -cwal_flags16_t cwal_container_flags_get( cwal_value const * v ){ - cwal_obase const * b = CWAL_VOBASE(v); - return b ? b->containerFlags : 0; -} - -cwal_flags16_t cwal_container_client_flags_set( cwal_value * v, cwal_flags16_t flags ){ - cwal_obase * b = CWAL_VOBASE(v); - if(!b) return 0; - else{ - cwal_flags16_t const rc = b->clientFlags; - b->clientFlags = flags; - return rc; - } -} - -cwal_flags16_t cwal_container_client_flags_get( cwal_value const * v ){ - cwal_obase const * b = CWAL_VOBASE(v); - return b ? b->clientFlags : 0; -} - - - -/** - Internal implementation details for cwal_printfv_appender_stringbuf. -*/ -typedef struct cwal_printfv_stringbuf -{ - /** dynamically allocated buffer */ - char * buffer; - /** bytes allocated to buffer */ - cwal_size_t alloced; - /** Current position within buffer. */ - cwal_size_t pos; - cwal_engine * e; - /* TODO: replace the above bits with cwal_buffer */ -} cwal_printfv_stringbuf; -static const cwal_printfv_stringbuf cwal_printfv_stringbuf_init = { 0, 0, 0, 0 /* TODO:, cwal_buffer_empty_m*/ }; - -/** - A cwal_printfv_appender implementation which requires arg to be a - (cwal_printfv_stringbuf*). It appends n bytes of data to the - cwal_printfv_stringbuf object's buffer, reallocating it as - needed. Returns less than 0 on error, else the number of bytes - appended to the buffer. The buffer will always be null terminated. -*/ -static int cwal_printfv_appender_stringbuf( void * arg, char const * data, - unsigned n ) -{ - cwal_printfv_stringbuf * const sb = (cwal_printfv_stringbuf*)arg; - assert(sb); - if( ! n ) return 0; - else{ - unsigned N; - size_t npos = sb->pos + n; - if( npos >= sb->alloced ){ - const size_t asz = (3 * npos / 2) + 1; - if( asz < npos ) return CWAL_RC_RANGE /* overflow */; - else{ - char * buf = (char *)cwal_realloc(sb->e, sb->buffer, asz ); - if( ! buf ) return -1; - memset( buf + sb->pos, 0, (npos + 1 - sb->pos) ); /* the +1 adds our NUL for us*/ - sb->buffer = buf; - sb->alloced = asz; - } - } - N = 0; - for( ; N < n; ++N, ++sb->pos ) sb->buffer[sb->pos] = data[N]; - return 0; - } -} - -char * cwal_printfv_cstr( cwal_engine * e, char const * fmt, va_list vargs ){ - if( ! fmt ) return 0; - else{ - cwal_printfv_stringbuf sb = cwal_printfv_stringbuf_init; - long rc = cwal_printfv( cwal_printfv_appender_stringbuf, &sb, fmt, vargs ); - if( rc <= 0 ) - { - cwal_free2( e, sb.buffer, (cwal_size_t)sb.alloced ); - sb.buffer = 0; - } - return sb.buffer; - } -} - -char * cwal_printf_cstr( cwal_engine * e, char const * fmt, ... ){ - va_list vargs; - char * ret; - va_start( vargs, fmt ); - ret = cwal_printfv_cstr( e, fmt, vargs ); - va_end( vargs ); - return ret; -} - - -int const * cwal_first_1000_primes(){ - static const int first1000Primes[1001] = { - 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, - 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, - 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, - 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, - 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, - 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, - 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, - 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, - 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, - 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, - 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, - 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, - 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, - 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, - 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, - 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, - 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, - 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, - 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, - 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, - 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, - 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, - 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, - 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, - 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, - 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, - 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, - 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, - 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, - 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, - 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, - 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, - 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, - 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, - 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, - 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, - 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, - 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, - 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, - 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, - 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, - 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, - 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, - 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, - 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, - 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, - 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, - 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, - 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, - 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, - 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, - 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, - 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, - 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, - 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, - 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, - 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, - 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, - 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, - 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, - 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, - 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, - 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, - 0 -#if 0 - , 0 /* causes an "excess elements in initializer" error if - we've counted properly above. */ -#endif - }; - return first1000Primes; -} - -cwal_build_info_t const * cwal_build_info(){ - static const cwal_build_info_t info = { - - /* cwal_size_t's */ - CWAL_SIZE_T_BITS, - CWAL_INT_T_BITS, - CWAL_STRLEN_MASK /* maxStringLength */, - - /* strings... */ - CWAL_VERSION_STRING, - CWAL_CPPFLAGS, - CWAL_CFLAGS, - CWAL_CXXFLAGS, - - /* booleans... */ - - CWAL_ENABLE_JSON_PARSER/* isJsonParserEnabled */, -#if defined(DEBUG) - /* isDebug */ - 1, -#else - 0, -#endif - {/* sizeofs... */ - sizeof(CWAL_BUILTIN_VALS), - sizeof(cwal_value), - sizeof(void*) - } - }; - return &info; -} - -cwal_value * cwal_build_info_object(cwal_engine * e){ - cwal_value * obj; - cwal_value * setter; - cwal_value * v; - int rc; - cwal_build_info_t const * const bi = cwal_build_info(); - char const * key; - if(!e || !e->current) return 0; - assert(e); - obj = cwal_new_object_value(e); - if(!obj) goto fail; - cwal_value_ref(obj); - setter = obj; - -#define SET1 \ - if(!v) goto fail; \ - cwal_value_ref(v); \ - rc = cwal_prop_set(setter, key, cwal_strlen(key), v); \ - cwal_value_unref(v); \ - v = 0; \ - if(rc) goto fail - - /************************************************************* - Numeric members... - */ -#define SET_INT(NAME,MEMBER) \ - v = cwal_new_integer(e, (cwal_int_t) bi->MEMBER); \ - key = (char const *)NAME ? (char const *)NAME : #MEMBER; \ - SET1 - - SET_INT(0,size_t_bits); - SET_INT(0,int_t_bits); - SET_INT(0,maxStringLength); - - /************************************************************* - Boolean members... - */ -#define SET_BOOL(NAME,MEMBER) \ - v = bi->MEMBER ? cwal_value_true() : cwal_value_false(); \ - key = NAME ? NAME : #MEMBER; \ - SET1 - - SET_BOOL(0,isJsonParserEnabled); - SET_BOOL(0,isDebug); - - /************************************************************* - String members... - */ -#define SET_STR(NAME,MEMBER) \ - v = cwal_new_string_value(e, bi->MEMBER, cwal_strlen(bi->MEMBER)); \ - key = NAME ? NAME : #MEMBER; \ - SET1 - - SET_STR(0,versionString); - SET_STR(0,cppFlags); - SET_STR(0,cFlags); - SET_STR(0,cxxFlags); - - /************************************************************* - sizeofs... - */ - { - cwal_value * so = cwal_new_object_value(e); - if(!so) goto fail; - cwal_value_ref(so); - rc = cwal_prop_set(obj, "sizeofs", 7, so); - cwal_value_unref(so); - if(rc) goto fail; - setter = so; -#define SO(X) SET_INT(#X, sizeofs.X) - SO(voidPointer); - SO(builtinValues); - SO(cwalValue); -#undef SO - } - -#undef SET1 -#undef SET_INT -#undef SET_BOOL -#undef SET_STR - cwal_value_unhand(obj); - return obj; - fail: - cwal_value_unref(obj); - return 0; -} - -int cwal_callback_f_build_info(cwal_callback_args const * args, cwal_value ** rv){ - return (*rv = cwal_build_info_object(args->engine)) - ? 0 - : CWAL_RC_OOM; -} - -enum { -/** - Indicates that one of the cwal_visit_XXX_begin() routines - set a flag on its operand value. -*/ -visitOpaqueMarkerYes = 0x01234567, -/** - Indicates that one of the cwal_visit_XXX_begin() routines - did not set a flag on its operand value. -*/ -visitOpaqueMarkerNo = 0x76543210 -}; - -void cwal_visit_props_begin( cwal_value * const v, int * const opaque ){ -#if CWAL_OBASE_ISA_HASH - cwal_obase * const ob = CWAL_VOBASE(v); - assert(!ob->hprops.list.isVisiting); -#endif -#ifdef DEBUG - assert(CWAL_VOBASE(v) && "Invalid use of cwal_visit_props_begin()"); -#endif - assert(v); - assert(opaque); - if(CWAL_V_IS_VISITING(v)){ - *opaque = visitOpaqueMarkerNo; - }else{ - *opaque = visitOpaqueMarkerYes; - CWAL_RCFLAG_ON(v,CWAL_RCF_IS_VISITING); -#if CWAL_OBASE_ISA_HASH - ob->hprops.list.isVisiting = true; -#endif - } -} - -void cwal_visit_props_end( cwal_value * const v, int opaque ){ -#if CWAL_OBASE_ISA_HASH - cwal_obase * const ob = CWAL_VOBASE(v); - assert(ob->hprops.list.isVisiting && "Else internal API misuse."); -#elif defined(DEBUG) - assert(CWAL_VOBASE(v) && "Invalid use of cwal_visit_props_end()"); -#endif - assert(v); - assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque); - if(visitOpaqueMarkerYes==opaque){ -#if CWAL_OBASE_ISA_HASH - ob->hprops.list.isVisiting = false; -#endif - CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING); - } -} - -void cwal_visit_list_begin( cwal_value * const v, int * const opaque ){ - cwal_hash * const h = CWAL_HASH(v); - cwal_array * const a = h ? NULL : CWAL_ARRAY(v); - assert(v); - assert(CWAL_TYPE_TUPLE==v->vtab->typeID - || CWAL_TYPE_ARRAY==v->vtab->typeID - || CWAL_TYPE_HASH==v->vtab->typeID); - assert(opaque); - if(CWAL_V_IS_VISITING_LIST(v)){ - *opaque = visitOpaqueMarkerNo; - if(h) assert(h->htable.list.isVisiting); - if(a) assert(a->list.isVisiting); - }else{ - *opaque = visitOpaqueMarkerYes; - CWAL_RCFLAG_ON(v,CWAL_RCF_IS_VISITING_LIST); - if(h){ - assert(!h->htable.list.isVisiting); - h->htable.list.isVisiting = true; - } - else if(a){ - assert(!a->list.isVisiting); - a->list.isVisiting = true; - } - } -} - -void cwal_visit_list_end( cwal_value * const v, int opaque ){ - assert(v); - assert(CWAL_TYPE_TUPLE==v->vtab->typeID - || CWAL_TYPE_ARRAY==v->vtab->typeID - || CWAL_TYPE_HASH==v->vtab->typeID); - assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque); - if(visitOpaqueMarkerYes==opaque){ - cwal_hash * const h = CWAL_HASH(v); - cwal_array * const a = h ? NULL : CWAL_ARRAY(v); - CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING_LIST); - if(h){ - assert(h->htable.list.isVisiting); - h->htable.list.isVisiting = false; - } - else if(a){ - assert(a->list.isVisiting); - a->list.isVisiting = false; - } - } -} - -int cwal_visit_acyclic_begin( cwal_value * v, int * opaque ){ - assert(v); - assert(opaque); - switch(v->vtab->typeID){ - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_TUPLE: - if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VISITING_ACYCLIC)){ - *opaque = visitOpaqueMarkerNo; - return CWAL_RC_CYCLES_DETECTED; - }else{ - CWAL_RCFLAG_ON(v, CWAL_RCF_IS_VISITING_ACYCLIC); - *opaque = visitOpaqueMarkerYes; - return 0; - } - break /* not reached */; - default: - *opaque = visitOpaqueMarkerNo; - return 0; - } -} - -void cwal_visit_acyclic_end( cwal_value * v, int opaque ){ - assert(v); - assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque); - if(visitOpaqueMarkerYes==opaque){ - CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING_ACYCLIC); - } -} - - -int cwal_error_setv( cwal_engine * e, cwal_error * err, int code, - char const * fmt, va_list args){ - int rc = 0; - if(!err) err = &e->err; - err->msg.used = 0; - err->code = code; - if(CWAL_RC_OOM != (err->code = code)){ - if(!fmt || !*fmt) cwal_buffer_reset(&err->msg); - else rc = cwal_buffer_printfv(e, &err->msg, fmt, args); - }else if(err->msg.capacity){ - /* make sure it's nul-terminated :/ */ - err->msg.mem[0] = 0; - } - return rc ? rc : code; -} - -int cwal_error_set( cwal_engine * e, cwal_error * err, int code, char const * fmt, ... ){ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_error_setv(e, err, code, fmt, args); - va_end(args); - return rc; -} - -int cwal_error_copy( cwal_engine * e, cwal_error const * src, cwal_error * dest ){ - int rc = 0; - if(!src) src = &e->err; - else if(!dest) dest = &e->err; - if(src == dest) return CWAL_RC_MISUSE; - cwal_error_reset( dest ); - dest->line = src->line; - dest->col = src->col; - dest->code = src->code; - if(src->msg.used){ - rc = cwal_buffer_append(e, &dest->msg, src->msg.mem, - src->msg.used); - } - if(!rc && src->script.used){ - rc = cwal_buffer_append(e, &dest->script, - src->script.mem, src->script.used); - } - return rc; -} - -void cwal_error_reset( cwal_error * err ){ - cwal_buffer_reset(&err->msg); - cwal_buffer_reset(&err->script); - err->code = err->line = err->col = 0; -} -void cwal_engine_error_reset( cwal_engine * e ){ - cwal_error_reset(&e->err); -} - - -void cwal_error_move( cwal_error * lower, cwal_error * higher ){ - cwal_error const err = *lower; - *lower = *higher; - lower->line = lower->col = lower->code = 0; - lower->msg.used = lower->script.used = 0U; - *higher = err; -} - -void cwal_error_clear( cwal_engine * e, cwal_error * err ){ - if(!err) err = &e->err; - cwal_buffer_clear(e, &err->msg); - cwal_buffer_clear(e, &err->script); - *err = cwal_error_empty; -} - -int cwal_error_get( cwal_error const * err, char const ** msg, cwal_size_t * msgLen ){ - if(err->code){ - if(msg) *msg = err->msg.used ? (char const *)err->msg.mem : 0; - if(msgLen) *msgLen = err->msg.used; - } - return err->code; -} -int cwal_engine_error_get( cwal_engine const * e, char const ** msg, cwal_size_t * msgLen ){ - return cwal_error_get(&e->err, msg, msgLen); -} -static int cwal_error_add_script_props( cwal_engine * e, - cwal_value * ex, - char const * scriptName, - int line, int col){ - int rc = 0; - if(scriptName && *scriptName){ - cwal_value * snv = cwal_new_string_value(e, - scriptName, - cwal_strlen(scriptName)); - cwal_value_ref(snv); - rc = snv - ? cwal_prop_set(ex, "script", 6, snv) - : CWAL_RC_OOM; - cwal_value_unref(snv); - } - if(!rc && line>0){ - cwal_value * v = cwal_new_integer(e, line); - if(!v) rc = CWAL_RC_OOM; - else{ - cwal_value_ref(v); - rc = cwal_prop_set(ex, "line", 4, v); - cwal_value_unref(v); - v = 0; - } - if(!rc){ - v = cwal_new_integer(e, col); - if(!v) rc = CWAL_RC_OOM; - else{ - cwal_value_ref(v); - rc = cwal_prop_set(ex, "column", 6, v); - cwal_value_unref(v); - v = 0; - } - } - } - return rc; -} - - -cwal_value * cwal_error_exception( cwal_engine * e, - cwal_error * err, - char const * scriptName, - int line, int col ){ - int rc = 0; - cwal_value * msg; - cwal_value * ex; - if(!err) err = &e->err; - if(line<=0){ - line = err->line; - col = err->col; - } - if(err->msg.used){ - msg = cwal_string_value(cwal_buffer_to_zstring(e, &err->msg)); - }else{ - msg = cwal_string_value(cwal_new_stringf(e, "Error #%d (%s).", - err->code, - cwal_rc_cstr(err->code))); - } - if(msg) cwal_value_ref(msg); - ex = msg - ? cwal_new_exception_value(e, err->code, msg) - : 0; - if(msg){ - cwal_value_unref(msg); - msg = 0; - } - if(!ex){ - rc = CWAL_RC_OOM; - }else{ - cwal_value_ref(ex); - assert(cwal_value_is_exception(ex)); - if(!scriptName && err->script.used) scriptName = (char const *)err->script.mem; - rc = cwal_error_add_script_props(e, ex, scriptName, line, col); - if(rc) { - cwal_value_unref(ex); - ex = 0; - }else{ - cwal_value_unhand(ex); - } - } - cwal_error_reset(err); - return ex; -} - -int cwal_error_throw( cwal_engine * e, cwal_error * err, - char const * script, - int line, int col ){ - cwal_value * const ex = cwal_error_exception(e, err, script, line, col); - if(!ex) return CWAL_RC_OOM; - else{ - int rc; - /* cwal_dump_val(ex, "exception"); */ - rc = cwal_exception_set(e, ex); - assert(CWAL_RC_EXCEPTION==rc); - assert(1==cwal_value_refcount(ex)); - return rc; - } -} - -cwal_error * cwal_engine_errstate( cwal_engine * e ){ - return &e->err; -} -#if 0 -cwal_error const * cwal_engine_errstate_c( cwal_engine const * e ){ - return &e->err; -} -#endif - -cwal_kvp const * cwal_obase_kvp_iter_init( cwal_value * const v, - cwal_obase_kvp_iter * const oks ){ - cwal_obase * const base = cwal_value_obase(v); - assert(CWAL_V_IS_VISITING(v) - || CWAL_V_IS_VISITING_LIST(v) - || CWAL_V_IS_RESCOPING(v)); - oks->v = v; -#if CWAL_OBASE_ISA_HASH - oks->base = base; - oks->li = &base->hprops.list; - oks->ndx = 0; - oks->current = NULL; - return cwal_obase_kvp_iter_next(oks); -#else - return oks->current = base->kvp; -#endif -} - -cwal_kvp const * cwal_obase_kvp_iter_next( cwal_obase_kvp_iter * const oks ){ - assert(CWAL_V_IS_VISITING(oks->v) - || CWAL_V_IS_VISITING_LIST(oks->v) - || CWAL_V_IS_RESCOPING(oks->v)); -#if CWAL_OBASE_ISA_HASH - //assert(CWAL_V_IS_VISITING(CWAL_VALPART(oks->base))); - assert(oks->base->hprops.list.isVisiting); - if(oks->current && oks->current->right){ - return oks->current = oks->current->right; - }else if(oks->ndx>=oks->base->hprops.hashSize){ - return NULL; - } - for(cwal_midsize_t i = oks->ndx; i < oks->base->hprops.hashSize; ++i){ - if(oks->li->list[i]){ - oks->current = (cwal_kvp const *)oks->li->list[i]; - oks->ndx = i+1; - return oks->current; - } - } - oks->ndx = oks->base->hprops.hashSize; - return NULL; -#else - return oks->current ? (oks->current = oks->current->right) : NULL; -#endif -} - -#undef MARKER -#undef cwal_string_empty_m -#undef cwal_obase_empty_m -#undef cwal_array_empty_m -#undef cwal_kvp_empty_m -#undef cwal_object_empty_m -#undef cwal_value_vtab_empty_m -#undef cwal_value_empty_m -#undef CWAL_VVPCAST -#undef CWAL_VALPART -#undef CWAL_INT -#undef CWAL_DBL -#undef CWAL_BOOL -#undef CWAL_STR -#undef CWAL_OBASE -#undef CWAL_VOBASE -#undef CWAL_OBJ -#undef CWAL_ARRAY -#undef CWAL_HASH -#undef CWAL_V2NATIVE -#undef dump_val -#undef CWAL_MEM_IS_BUILTIN -#undef V_SEEMS_OK -#undef CWAL_VENGINE -#undef SETUP_ARRAY_ARGS -#undef CWAL_STRLEN -#undef CWAL_STRLEN_MASK -#undef CWAL_STR_ISX -#undef CWAL_STR_ISZ -#undef CWAL_STR_ISXZ -#undef CWAL_STR_XMASK -#undef CWAL_STR_ZMASK -#undef CWAL_STR_ASCII_MASK -#undef CWAL_STR_ISASCII -#undef CWAL_KVP_TRY_SORTING -#undef TRY_CLONE_SHARING -#undef COMPARE_TYPE_IDS -#undef CWAL_V_IS_VISITING -#undef CWAL_V_IS_VISITING_LIST -#undef METRICS_REQ_INCR -#undef CWAL_V_IS_VACUUM_SAFE -#undef CWAL_ALLOC_DO_PAD -#undef CWAL_MEMSZ_PAD -#undef CWAL_UNIQUE_VAL -#undef MEMSZ_PTR_FROM_MEM -#undef E_IS_DEAD - -#undef CWAL_RCFLAG_MAXRC -#undef CWAL_RCFLAGS_BITS -#undef CWAL_RCFLAGS_BITS_MASK -#undef CWAL_REFCOUNT -#undef CWAL_REFCOUNT_SHIFTED -#undef CWAL_RCFLAGS -#undef CWAL_RCADJ -#undef CWAL_RCDECR -#undef CWAL_RCINCR -#undef CWAL_RCFLAG_ON -#undef CWAL_RCFLAG_ON -#undef CWAL_RCFLAG_OFF -#undef CWAL_RCFLAG_HAS -#undef CWAL_V_IS_IN_CLEANUP -#undef CWAL_V_IS_RESCOPING -#undef CWAL_VVPCAST_NONULL -#undef CWAL_DBL_NONULL -#undef CWAL_INT_NONULL -#undef CWAL_STRLEN_TOO_LONG -#undef CWAL_BUILTIN_INT_VAL -#undef CWAL_BUILTIN_INT_FIRST -#undef CWAL_BUILTIN_INT_LAST -#undef CWAL_BUILTIN_INT_COUNT -#undef CWAL_V_GOES_IN_HEADOBJ -#undef CWAL_OB_LOCK -#undef CWAL_OB_IS_LOCKED -#undef CWAL_OB_UNLOCK - - -#if defined(__cplusplus) -} //extern "C" LOL - the C++-style comments do not upset C89 here. -#endif -/* end of file cwal.c */ -/* start of file cwal_format.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -/** - This file contains the impl for cwal_buffer_format() and friends. -*/ -#include /* memcpy() */ -#include /* strtol() */ -#include - -#if 1 -/* Only for debuggering */ -#include -#define MARKER(pfexp) \ - do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ - printf pfexp; \ - } while(0) -#endif - - -/** - Internal type IDs for cwal_format_info instances. - - Maintenance reminder: these MUST be aligned with - the CWAL_FORMAT_INFO array. -*/ -enum cwal_format_t { -CWAL_FMT_UNKNOWN = 0, -CWAL_FMT_BOOL, -CWAL_FMT_NULL, -CWAL_FMT_UNDEF, -CWAL_FMT_VOIDP, -CWAL_FMT_INT_DEC, -CWAL_FMT_INT_HEXLC, -CWAL_FMT_INT_HEXUC, -CWAL_FMT_INT_OCTAL, -CWAL_FMT_DOUBLE, -CWAL_FMT_STRING, -CWAL_FMT_STRING_SQL, -CWAL_FMT_CHAR, -CWAL_FMT_TYPENAME, -CWAL_FMT_BLOB, -CWAL_FMT_JSON, -CWAL_FMT_URLENCODE, -CWAL_FMT_URLDECODE, -CWAL_FMT_MAX -}; -typedef enum cwal_format_t cwal_format_t; - -enum cwal_format_flags { -CWAL_FMT_F_NONE = 0, -CWAL_FMT_F_SIGNED = 0x01, -CWAL_FMT_F_ALT_FORM = 0x02, -CWAL_FMT_F_SQL_QUOTES_DOUBLE = 0x01 -}; -typedef enum cwal_format_flags cwal_format_flags; - -typedef struct cwal_format_info cwal_format_info; -/** - Callback for concrete cwal_buffer_format() handlers. fi holds the - formatting details, v the value. Implementations must interpret v - for formatting purposes, following the settings in fi (insofar as - possible for the given type). On error the handler must return - non-0 and may set fi->errMsg to a string describing the problem. - Handlers are allowed to silently ignore small misuses like invalid - width/precision values. -*/ -typedef int (*cwal_format_part_f)( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -/** - Holds information related to the handling of a format specifier in - cwal_buffer_format(). -*/ -struct cwal_format_info { - /** The format type for which this handler is intended. */ - cwal_format_t type; - /** Type-specific flags, from cwal_format_flags. */ - int flags; - /** - Precision part of the format string. - */ - int precision; - /** - Width part of the format string. - */ - int width; - /** - Ordinal position in the args processing, 1-based. - */ - int position; - /** - Can be set to propagate an error message in certai - contexts. - */ - char const * errMsg; - /** - Padding char. TODO: change to int and output it as a UTF8 - char. TODO: split into left/right padding. Currently we - hard-code space as right-side padding. - */ - char pad; - /** - Callback handler for this formatter. - */ - cwal_format_part_f f; -}; - -#define cwalFormatNoPrecision ((int)0x7FFFFFFF) - -static int cwal_format_part_int( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_double( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_string( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_sql( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_char( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_blob( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_json( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_urlenc( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); -static int cwal_format_part_urldec( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ); - -static const cwal_format_info CWAL_FORMAT_INFO[CWAL_FMT_MAX+1] = { -/* - Maintenance reminder: these MUST be in the same order the entries - are defined in cwal_format_t. -*/ -/* {type,flags,precision,width,position,errMsg,pad,f} */ -{CWAL_FMT_UNKNOWN, 0, 0, 0, 0,NULL,' ', NULL}, -#define FMT(T,CB) {CWAL_FMT_##T, 0, 0, 0, 0,NULL,' ', cwal_format_part_ ## CB} -FMT(BOOL,basics), -FMT(NULL,basics), -FMT(UNDEF,basics), -FMT(VOIDP,basics), -FMT(INT_DEC,int), -FMT(INT_HEXLC,int), -FMT(INT_HEXUC,int), -FMT(INT_OCTAL,int), -FMT(DOUBLE,double), -FMT(STRING,string), -FMT(STRING_SQL,sql), -FMT(CHAR,char), -FMT(TYPENAME,basics), -FMT(BLOB,blob), -FMT(JSON,json), -FMT(URLENCODE,urlenc), -FMT(URLDECODE,urldec), -#undef FMT -{CWAL_FMT_MAX, 0, 0, 0, 0,NULL,' ', NULL}, -}; - -static const cwal_format_info cwal_format_info_empty = { -CWAL_FMT_UNKNOWN, -0/*flags*/, -cwalFormatNoPrecision/*precision*/, -0/*width*/, -0/*position*/, -NULL/*errMsg*/, -' '/*pad*/, -NULL/*f*/ -}; - -int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt, - cwal_format_info * fi, cwal_value const * v ){ - switch(fi->type){ - case CWAL_FMT_BOOL:{ - char const b = cwal_value_get_bool(v); - return cwal_buffer_append( e, tgt, b ? "true" : "false", b ? 4 : 5); - } - case CWAL_FMT_UNDEF: - return cwal_buffer_append( e, tgt, "undefined", 9); - case CWAL_FMT_VOIDP: - return cwal_buffer_printf( e, tgt, "%s@%p", - cwal_value_type_name(v), - (void const*)v); - case CWAL_FMT_TYPENAME: - return cwal_buffer_printf(e, tgt, "%s", cwal_value_type_name(v)); - case CWAL_FMT_NULL: - default: - /* This can happen for %N when the value is-not NULL. */ - return cwal_buffer_append( e, tgt, "null", 4); - } -} - - -int cwal_format_part_int( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - enum { BufSize = CWAL_INT_T_BITS * 2 }; - char fmtBuf[BufSize+1] = {'%',0}; - char * b = fmtBuf + 1; - int rc; - cwal_int_t const i = cwal_value_get_integer(v); - char const * intFmt = 0; - if((i>0) && (CWAL_FMT_F_SIGNED & fi->flags)){ - *(b++) = '+'; - } - if(0 != fi->width){ - if('0'==fi->pad) *(b++) = '0'; - b += sprintf(b, "%d", fi->width); - } - if(fi->precision && cwalFormatNoPrecision!=fi->precision){ - b += sprintf(b, ".%d", fi->precision); - } - switch(fi->type){ - case CWAL_FMT_INT_DEC: - intFmt = CWAL_INT_T_PFMT; - break; - case CWAL_FMT_INT_HEXLC: - intFmt = CWAL_INT_T_PFMTx; - break; - case CWAL_FMT_INT_HEXUC: - intFmt = CWAL_INT_T_PFMTX; - break; - case CWAL_FMT_INT_OCTAL: - intFmt = CWAL_INT_T_PFMTo; - break; - default: - assert(!"CANNOT HAPPEN"); - return CWAL_RC_RANGE; - } - assert(intFmt); - { - cwal_size_t const fmtLen = cwal_strlen(intFmt); - assert(b + fmtLen <= (fmtBuf + BufSize)); - memcpy(b, intFmt, (size_t)fmtLen); - b += fmtLen; - *b = 0; - } - rc = cwal_buffer_printf(e, tgt, fmtBuf, i ); - return rc; -} - -int cwal_format_part_double( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - - enum { BufSize = 120 }; - static unsigned int fmtLen = sizeof(CWAL_DOUBLE_T_PFMT)-1; - char fmtBuf[BufSize+1] = {'%',0}; - cwal_size_t fmtBufLen = BufSize; - char * b = fmtBuf + 1; - int rc = 0; - cwal_double_t const d = cwal_value_get_double(v); - if((d>0) && (CWAL_FMT_F_SIGNED & fi->flags)){ - *(b++) = '+'; - } - if(fi->width){ - if('0'==fi->pad){ - *(b++) = '0'; - --fmtBufLen; - } - cwal_int_to_cstr( fi->width, b, &fmtBufLen ); - b += fmtBufLen; - } - if(fi->precision && cwalFormatNoPrecision != fi->precision){ - fmtBufLen = BufSize - (b - fmtBuf); - *(b++) = '.'; - cwal_int_to_cstr( fi->precision, b, &fmtBufLen); - b += fmtBufLen; - } - assert(b < (fmtBuf+BufSize+fmtLen)); - memcpy(b, CWAL_DOUBLE_T_PFMT, fmtLen); - b += fmtLen; - *b = 0; - if(!rc){ - rc = cwal_buffer_printf(e, tgt, fmtBuf, d ); - if(!rc && tgt->used && - (!fi->precision || cwalFormatNoPrecision == fi->precision)){ - /* Trim trailing zeroes when no precision is specified... */ - b = (char *)(tgt->mem + tgt->used - 1); - for( ; ('0'==*b) && tgt->used; --b, --tgt->used){} - if('.'==*b) ++tgt->used /* leave one trailing 0 if we just - stripped the last one. */; - } - } - return rc; -} - -int cwal_format_part_string( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - int rc = 0; - cwal_size_t slen = 0; - cwal_size_t ulen; - char const * cstr = cwal_value_get_cstr(v, &slen); - int width = fi->width; - if(!cstr){ -#if 0 - cwal_type_id const tid = cwal_value_type_id(v); - switch(tid){ - /* Reconsider these and whether we need to support the - width/precision values in these cases. */ - case CWAL_TYPE_BUFFER: - /* completely empty buffer */ - return 0; - case CWAL_TYPE_HASH: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_NATIVE: - return cwal_buffer_printf(e, tgt, "%s@%p", - cwal_value_type_name(v), - (void const*)v); - case CWAL_TYPE_BOOL: - return cwal_buffer_printf(e, tgt, "%s", - cwal_value_get_bool(v) - ? "true" : "false"); - case CWAL_TYPE_INTEGER: - return cwal_buffer_printf(e, tgt, "%"CWAL_INT_T_PFMT, - (cwal_int_t)cwal_value_get_integer(v)); - case CWAL_TYPE_DOUBLE: - return cwal_buffer_printf(e, tgt, "%"CWAL_DOUBLE_T_PFMT, - (cwal_int_t)cwal_value_get_double(v)); - default: - cstr = "(nil)"; - ulen = slen = 5; - } -#else - cstr = "(nil)"; - ulen = slen = 5; -#endif - } - else{ - ulen = cwal_strlen_utf8( cstr, slen ); - } - if((cwalFormatNoPrecision != fi->precision) - && (fi->precision>0) && (fi->precision<(int)ulen)){ - int i = 0; - unsigned char const * ustr = (unsigned char const *)cstr; - unsigned char const * uend = (unsigned char const *)cstr + slen; - unsigned char const * upos = ustr; - for( ; i < fi->precision; ++i, ustr = upos ){ - cwal_utf8_read_char( ustr, uend, &upos ); - assert(upos > ustr); - } - ulen = (cwal_size_t)fi->precision; - slen = upos - (unsigned char const *)cstr; - /* slen = (cwal_size_t)fi->precision; */ - } - if(width>0 && ((int)ulen < width)){ /* right alignment */ - int i = (int)ulen - width; - for( ; !rc && (i < 0); ++i){ - rc = cwal_buffer_append(e, tgt, &fi->pad, 1); - } - } - if(!rc){ - rc = cwal_buffer_append(e, tgt, cstr, slen); - if(!rc && (width<0)){ /* right padding */ - int i = (int)ulen - -width; - for( ; !rc && (i < 0); ++i){ - rc = cwal_buffer_append(e, tgt, &fi->pad, 1); - } - } - } - return rc; -} - -int cwal_format_part_sql( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - int rc = 0; - cwal_size_t slen = 0; - unsigned int i, n; - cwal_size_t j; - int ch, isnull; - int needQuote; - char const q = ((CWAL_FMT_F_SQL_QUOTES_DOUBLE & fi->flags)?'"':'\''); /* Quote character */ - char const * escarg = cwal_value_get_cstr(v, &slen); - char * bufpt = NULL; - cwal_size_t const oldUsed = e->buffer.used; - isnull = escarg==0; - if( isnull ) { - if(CWAL_FMT_F_ALT_FORM & fi->flags){ - escarg = "NULL"; - slen = 4; - }else{ - escarg = "(NULL)"; - slen = 6; - } - } - for(i=n=0; (iflags); - n += i + 1 + needQuote*2; - /* FIXME: use a static buffer here instead of malloc()! Shame on you! */ - rc = cwal_buffer_reserve( e, &e->buffer, oldUsed + n ); - if(rc) return rc; - bufpt = (char *)e->buffer.mem + oldUsed; - if( ! bufpt ) return CWAL_RC_OOM; - j = 0; - if( needQuote ) bufpt[j++] = q; - for(i=0; (ch=escarg[i])!=0; i++){ - bufpt[j++] = ch; - if( ch==q ) bufpt[j++] = ch; - } - if( needQuote ) bufpt[j++] = q; - bufpt[j] = 0; - rc = cwal_buffer_append(e, tgt, bufpt, j); - e->buffer.used = oldUsed; - e->buffer.mem[e->buffer.used] = 0; - return 0; -} - - -int cwal_format_part_blob( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - int rc = 0; - cwal_size_t slen = 0; - char const * cstr = cwal_value_get_cstr(v, &slen); - int width = fi->width; - if(!cstr) return 0; - else if((cwalFormatNoPrecision != fi->precision) - && (fi->precision>0) && (fi->precision<(int)slen)){ - slen = (cwal_size_t)fi->precision; - } - if(width>0 && ((int)slen < width)){ /* right alignment */ - int i = (int)slen - width; - for( ; !rc && (i < 0); ++i){ - rc = cwal_buffer_append(e, tgt, &fi->pad, 1); - } - } - if(!rc){ - rc = cwal_buffer_reserve(e, tgt, tgt->used + (slen*2) + 1); - if(!rc){ - char const * hchar = "0123456789ABCDEF"; - cwal_size_t i; - char hex[2] = {0,0}; - for( i = 0; !rc && (i < slen); ++i ){ - hex[0] = hchar[(cstr[i] & 0xF0)>>4]; - hex[1] = hchar[cstr[i] & 0x0F]; - rc = cwal_buffer_append(e, tgt, hex, 2); - } - if(!rc && (width<0)){ /* right padding */ - int i = (int)slen - -width; - for( ; !rc && (i < 0); ++i){ - rc = cwal_buffer_append(e, tgt, &fi->pad, 1); - } - } - } - } - return rc; -} - -int cwal_format_part_json( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - cwal_json_output_opt outOpt = cwal_json_output_opt_empty; - outOpt.indent = fi->width; - return cwal_json_output_buffer(e, (cwal_value *)/*EVIL!*/v, - tgt, &outOpt); -} - - -int cwal_format_part_char( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - unsigned char intBuf[10] = {0,0,0,0,0,0,0,0,0,0}; - int rc = 0; - cwal_size_t slen = 0; - char const * cstr = cwal_value_get_cstr(v, &slen); - cwal_int_t i; - if(!cstr){ - i = cwal_value_get_integer(v); - if(i<0){ - fi->errMsg = "Invalid (negative) character value."; - return CWAL_RC_RANGE; - } - slen = (cwal_size_t)cwal_utf8_char_to_cstr( (unsigned int)i, intBuf, - sizeof(intBuf)/sizeof(intBuf[0])); - assert(slen && slen<5); - if(slen>4){ - fi->errMsg = "Invalid UTF character value."; - return CWAL_RC_RANGE; - } - cstr = (char const *)intBuf; - } - if(!slen){ - fi->errMsg = "Invalid (empty) character."; - return CWAL_RC_RANGE; - }else{ - unsigned char const * begin = (unsigned char const *)cstr; - unsigned char const * eof = begin + slen; - unsigned char const * tail = begin; - int width = !fi->width ? 0 : ((fi->width<0) ? -fi->width : fi->width); - cwal_size_t chLen; - cwal_int_t n; - cwal_utf8_read_char( begin, eof, &tail ); - if(tail == begin){ - fi->errMsg = "Could not decode UTF character."; - return CWAL_RC_RANGE; - } - chLen = tail - begin; - n = (cwalFormatNoPrecision == fi->precision) - ? 1 - : ((fi->precision>=0) ? fi->precision : 0) - ; - /* MARKER(("n=%d, width=%d, precision=%d, \n",(int)n, fi->width, fi->precision)); */ - if((fi->width>0) && (width > n)){ /* left-pad */ - for( ; !rc && (width > n); --width){ - rc = cwal_buffer_append( e, tgt, &fi->pad, 1 ); - } - } - for( i = 0; !rc && (i < n); ++i ){ - rc = cwal_buffer_append( e, tgt, begin, chLen ); - } - if(!rc && (fi->width<0) && (width > n)){ /* right-pad */ - for( ; !rc && (width > n); --width){ - rc = cwal_buffer_append( e, tgt, &fi->pad, 1 ); - } - } - return rc; - } -} - -static int cwal_httpurl_needs_escape( int c ) -{ - /* - Definition of "safe" and "unsafe" chars - was taken from: - - http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4029/ - */ - return ( (c >= 32 && c <=47) - || ( c>=58 && c<=64) - || ( c>=91 && c<=96) - || ( c>=123 && c<=126) - || ( c<32 || c>=127) - ); -} - -int cwal_format_part_urlenc( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - cwal_size_t slen = 0; - char const * str = cwal_value_get_cstr(v, &slen); - char ch; - int rc = 0; - cwal_size_t j = 0; - char * bufpt = NULL; - cwal_size_t const oldUsed = e->buffer.used; - static char const * hex = "0123456789ABCDEF"; - if(!str){ - fi->errMsg = "URL encoding requires a String value."; - return CWAL_RC_TYPE; - } - rc = cwal_buffer_reserve(e, &e->buffer, oldUsed + slen * 3 ); - if(rc) return rc; - bufpt = (char *)(e->buffer.mem + oldUsed); - ch = *str; - for( ; !rc && ch; ch = *(++str) ){ - if(!cwal_httpurl_needs_escape(ch) ){ - bufpt[j++] = ch; - }else{ - bufpt[j++] = '%'; - bufpt[j++] = hex[((ch>>4)&0xf)]; - bufpt[j++] = hex[(ch&0xf)]; - } - } - assert( (void *)bufpt == (void*)e->buffer.mem ); - rc = cwal_buffer_append(e, tgt, bufpt, j); - e->buffer.used = oldUsed; - e->buffer.mem[e->buffer.used] = 0; - return 0; -} - -/* - cwal_hexchar_to_int(): - - For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal - number. For any other character it returns -1. -*/ -static int cwal_hexchar_to_int( int ch ) -{ - if( (ch>='0' && ch<='9') ) return ch-'0'; - else if( (ch>='a' && ch<='f') ) return ch-'a'+10; - else if( (ch>='A' && ch<='F') ) return ch-'A'+10; - else return -1; -} - -static int cwal_is_xdigit( int ch ){ - return ('a'<=ch && 'f'>=ch) - ? 1 - : (('A'<=ch && 'F'>=ch) - ? 1 - : ('0'<=ch && '9'>=ch ? 1 : 0)); -} - -int cwal_format_part_urldec( cwal_engine * e, - cwal_buffer * tgt, - cwal_format_info * fi, - cwal_value const * v ){ - - cwal_size_t slen = 0; - char const * str = cwal_value_get_cstr(v, &slen); - int rc = 0; - char ch = 0, ch2 = 0; - char xbuf[4]; - int decoded; - if(!str){ - fi->errMsg = "URL decoding requires a String value."; - return CWAL_RC_TYPE; - } - else if(!slen) return 0; - else if(slen<3){ - /* can't be urlencoded */ - return cwal_buffer_append(e, tgt, str, slen); - } - ch = *str; - while( !rc && ch ){ - if( ch == '%' ){ - ch = *(++str); - ch2 = *(++str); - if( cwal_is_xdigit((int)ch) && - cwal_is_xdigit((int)ch2) ){ - decoded = (cwal_hexchar_to_int( ch ) * 16) - + cwal_hexchar_to_int( ch2 ); - xbuf[0] = (char)decoded; - xbuf[1] = 0; - rc = cwal_buffer_append(e, tgt, xbuf, 1); - ch = *(++str); - continue; - } - else{ - xbuf[0] = '%'; - xbuf[1] = ch; - xbuf[2] = ch2; - xbuf[3] = 0; - rc = cwal_buffer_append(e, tgt, xbuf, 3); - ch = *(++str); - continue; - } - } - else if( ch == '+' ){ - xbuf[0] = ' '; - xbuf[1] = 0; - rc = cwal_buffer_append(e, tgt, xbuf, 1); - ch = *(++str); - continue; - } - xbuf[0] = ch; - xbuf[1] = 0; - rc = cwal_buffer_append(e, tgt, xbuf, 1); - ch = *(++str); - } - return rc; -} - -/** - Parse the range [b,*e) as a java.lang.String.format()-style - format string: - - %N$[flags][width][.precision][type] -*/ -static int cwal_format_parse_info( char const * b, char const ** e, - cwal_format_info * fi, - int * index ){ - char const * p = b; - char const * x; - char isMinus = 0; - *fi = cwal_format_info_empty; - for( ; (p < *e) && ('$' != *p); ++p ){ - if(('0'>*p) && ('9'<*p)){ - fi->errMsg = "Invalid argument index character after %."; - return CWAL_RC_RANGE; - } - } - /* MARKER(("fmt= b=[%s] p=[%s] e=[%c]\n",b, p, **e)); */ - if((p==b) || (p==*e)) { - fi->errMsg = "Invalid format token."; - return CWAL_RC_RANGE; - } - else if('$'!=*p) { - /* MARKER(("fmt=[%s] [%s]\n",b, p)); */ - fi->errMsg = "Missing '$' after %N."; - return CWAL_RC_RANGE; - } - *index = (int)strtol( b, (char **)&p, 10 ); - if(*index < 1){ - fi->errMsg = "Argument index must be greater than 0."; - return CWAL_RC_RANGE; - } - *index = *index - 1 /* make it zero-based */; - ++p /* skip '$' */; - /* MARKER(("p=[%s]\n",p)); */ - if(*e == p){ - no_type: - fi->errMsg = "No type specifier given in format token."; - return CWAL_RC_TYPE; - } - /* Check for flag. */ - switch(*p){ - case '+': - fi->flags |= CWAL_FMT_F_SIGNED; - ++p; - break; - default: break; - } - /* Read [[-]width] ... */ - if('-'==*p){ - isMinus = 1; - ++p; - } - x = p; - if('0' == *x){ /* Make it 0-padded */ - fi->pad = '0'; - ++x; - ++p; - } - for( ; (x<*e) && ('1'<=*x) && ('9'>=*x); ++x ){} - /* MARKER(("x=[%s]\n",x)); */ - if(x==*e){ - fi->errMsg = "Unexpected end of width specifier."; - return CWAL_RC_RANGE; - } - else if(x!=p){ - fi->width = (int)strtol( p, (char **)&p, 10 ); - if(p==*e) goto no_type; - if(isMinus) fi->width = -fi->width; - /* MARKER(("p=[%s], width=%d\n",p, fi->width)); */ - } - /* Read [.precision] ... */ - /* MARKER(("p=[%s]\n",p)); */ - if('.'==*p){ - ++p; - x = p; - for( ; (x < *e) && ('0'<=*x) && ('9'>=*x); ++x ){} - if(x==*e){ - fi->errMsg = "Unexpected end of precision specifier."; - return CWAL_RC_RANGE; - } - else if(x==p){ - fi->errMsg = "Missing digits after '.'."; - return CWAL_RC_RANGE; - } - else{ - fi->precision = (int)strtol( p, (char**)&p, 10 ); - /* MARKER(("p=[%s], precision=%d\n",p, fi->precision)); */ - if(p==*e) goto no_type; - } - } - switch( *p ){ - case 'b': fi->type = CWAL_FMT_BOOL; - ++p; - break; - case 'B': fi->type = CWAL_FMT_BLOB; - ++p; - break; - case 'c': fi->type = CWAL_FMT_CHAR; - ++p; - break; - case 'd': fi->type = CWAL_FMT_INT_DEC; - ++p; - break; - case 'f': fi->type = CWAL_FMT_DOUBLE; - ++p; - break; - case 'J': fi->type = CWAL_FMT_JSON; - ++p; - break; - case 'N': fi->type = CWAL_FMT_NULL; - ++p; - break; - case 'o': fi->type = CWAL_FMT_INT_OCTAL; - ++p; - break; - case 'p': fi->type = CWAL_FMT_VOIDP; - ++p; - break; - case 'q': fi->type = CWAL_FMT_STRING_SQL; - ++p; - break; - case 'Q': fi->type = CWAL_FMT_STRING_SQL; - fi->flags |= CWAL_FMT_F_ALT_FORM; - ++p; - break; - case 's': fi->type = CWAL_FMT_STRING; - ++p; - break; - case 'y': fi->type = CWAL_FMT_TYPENAME; - ++p; - break; - case 'U': fi->type = CWAL_FMT_UNDEF; - ++p; - break; - case 'x': fi->type = CWAL_FMT_INT_HEXLC; - ++p; - break; - case 'X': fi->type = CWAL_FMT_INT_HEXUC; - ++p; - break; - case 'r': fi->type = CWAL_FMT_URLENCODE; - ++p; - break; - case 'R': fi->type = CWAL_FMT_URLDECODE; - ++p; - break; - default: - fi->errMsg = "Unknown format type specifier."; - return CWAL_RC_RANGE; - } - switch(fi->type){ - case CWAL_FMT_URLENCODE: - case CWAL_FMT_URLDECODE: - if(fi->width){ - fi->errMsg = "Conversion does not support width."; - return CWAL_RC_MISUSE; - } - CWAL_SWITCH_FALL_THROUGH; - case CWAL_FMT_INT_DEC: - case CWAL_FMT_INT_HEXLC: - case CWAL_FMT_INT_HEXUC: - if(fi->precision && cwalFormatNoPrecision != fi->precision){ - fi->errMsg = "Conversion does not support precision."; - return CWAL_RC_MISUSE; - } - break; - case CWAL_FMT_CHAR: - if(fi->precision<0){ - fi->errMsg = "Character precision (repetition) may not be negative."; - return CWAL_RC_MISUSE; - } - break; - default: - break; - } - *e = p; - fi->f = CWAL_FORMAT_INFO[fi->type].f; - return 0; -} - -int cwal_buffer_format( cwal_engine * e, cwal_buffer * tgt, - char const * fmt, cwal_size_t fmtLen, - uint16_t argc, cwal_value * const * const argv){ - int rc = 0; - char const * fpos = fmt; - char const * fend = fpos + fmtLen; - char const * tstart = fmt; - cwal_format_info fi = cwal_format_info_empty; - int index; - int gotFmt = 0; - cwal_size_t oldUsed; - int ordPos = 0; - if(!e || !tgt || !fmt ) return CWAL_RC_MISUSE; - else if(!fmtLen) return 0; - oldUsed = tgt->used; - rc = cwal_buffer_reserve(e, tgt, oldUsed + fmtLen); - if(rc) return rc; - for( ; !rc && (fpos < fend); ){ - index = -1; - fi = cwal_format_info_empty; - switch( *fpos ){ - case '%':{ - ++fpos; - ++gotFmt; - if(fpos==fend){ - fi.errMsg = "Unexpected end of input after '%'."; - rc = CWAL_RC_RANGE; - goto end; - } - else if('%'==*fpos){ - rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart); - tstart = ++fpos; - continue; - } - else{ - char const * pend = fend; - fi.position = ++ordPos; - rc = cwal_format_parse_info( fpos, &pend, &fi, &index ); - tstart = fpos = pend; - if(!rc){ - assert(index>=0); - if(index>=(int)argc){ - fi.errMsg = "Argument index out of range."; - rc = CWAL_RC_RANGE; - goto end; - } - else if(!argv[index]){ - fi.f = CWAL_FORMAT_INFO[CWAL_FMT_NULL].f; - } - if(!fi.f){ - fi.errMsg = - "Missing formatting function for " - "type specifier."; - rc = CWAL_RC_ERROR; - goto end; - } - rc = fi.f( e, tgt, &fi, argv[index] ); - if(!rc) continue; - } - break; - } - }/* case '%' */ - default: - ++fpos; - break; - }/*switch(*fpos)*/ - if(!rc){ - rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart); - tstart = fpos; - } - else break; - } - end: - if(rc) switch(rc){ - case CWAL_RC_OOM: - /* Pass through errors which might be introduced indirectly - via client code if we expand this support to include custom - formatters or callbacks. Note that if other errors pass - through here we'll end up "overwriting" them as a formatting - error. - */ - case CWAL_RC_ASSERT: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_EXCEPTION: - break; - default:{ - int errPos = (int)(fpos - fmt); - int pedanticRc; - tgt->used = oldUsed; - pedanticRc = cwal_buffer_printf( e, tgt, - "Formatting failed with " - "code %d (%s) at format string " - "byte offset %d.", - rc, cwal_rc_cstr(rc), errPos - 1); - if(!pedanticRc && fi.errMsg){ - cwal_buffer_printf(e, tgt, " Format token #%d%s%s", gotFmt, - fi.errMsg ? ": " : ".", - fi.errMsg ? fi.errMsg : ""); - } - } - } - return rc; -} - -#undef MARKER -#undef cwalFormatNoPrecision -/* end of file cwal_format.c */ -/* start of file JSON_parser/JSON_parser.h */ -/* See JSON_parser.c for copyright information and licensing. */ - -#ifndef JSON_PARSER_H -#define JSON_PARSER_H - -/* JSON_parser.h */ - - -#include - -/* Windows DLL stuff */ -#ifdef JSON_PARSER_DLL -# ifdef _MSC_VER -# ifdef JSON_PARSER_DLL_EXPORTS -# define JSON_PARSER_DLL_API __declspec(dllexport) -# else -# define JSON_PARSER_DLL_API __declspec(dllimport) -# endif -# else -# define JSON_PARSER_DLL_API -# endif -#else -# define JSON_PARSER_DLL_API -#endif - -/* Determine the integer type use to parse non-floating point numbers */ -#ifdef _WIN32 -typedef __int64 JSON_int_t; -#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%I64d" -#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%I64d" -#elif (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1) -typedef long long JSON_int_t; -#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld" -#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld" -#else -typedef long JSON_int_t; -#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld" -#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld" -#endif - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum -{ - JSON_E_NONE = 0, - JSON_E_INVALID_CHAR, - JSON_E_INVALID_KEYWORD, - JSON_E_INVALID_ESCAPE_SEQUENCE, - JSON_E_INVALID_UNICODE_SEQUENCE, - JSON_E_INVALID_NUMBER, - JSON_E_NESTING_DEPTH_REACHED, - JSON_E_UNBALANCED_COLLECTION, - JSON_E_EXPECTED_KEY, - JSON_E_EXPECTED_COLON, - JSON_E_OUT_OF_MEMORY -} JSON_error; - -typedef enum -{ - JSON_T_NONE = 0, - JSON_T_ARRAY_BEGIN, - JSON_T_ARRAY_END, - JSON_T_OBJECT_BEGIN, - JSON_T_OBJECT_END, - JSON_T_INTEGER, - JSON_T_FLOAT, - JSON_T_NULL, - JSON_T_TRUE, - JSON_T_FALSE, - JSON_T_STRING, - JSON_T_KEY, - JSON_T_MAX -} JSON_type; - -typedef struct JSON_value_struct { - union { - JSON_int_t integer_value; - - double float_value; - - struct { - const char* value; - size_t length; - } str; - } vu; -} JSON_value; - -typedef struct JSON_parser_struct* JSON_parser; - -/*! \brief JSON parser callback - - \param ctx The pointer passed to new_JSON_parser. - \param type An element of JSON_type but not JSON_T_NONE. - \param value A representation of the parsed value. This parameter is NULL for - JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END, - JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned - as zero-terminated C strings. - - \return Non-zero if parsing should continue, else zero. -*/ -typedef int (*JSON_parser_callback)(void* ctx, int type, const JSON_value* value); - - -/** - A typedef for allocator functions semantically compatible with malloc(). -*/ -typedef void* (*JSON_malloc_t)(size_t n); -/** - A typedef for deallocator functions semantically compatible with free(). -*/ -typedef void (*JSON_free_t)(void* mem); - -/*! \brief The structure used to configure a JSON parser object -*/ -typedef struct { - /** Pointer to a callback, called when the parser has something to tell - the user. This parameter may be NULL. In this case the input is - merely checked for validity. - */ - JSON_parser_callback callback; - /** - Callback context - client-specified data to pass to the - callback function. This parameter may be NULL. - */ - void* callback_ctx; - /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting. - If negative, the parser can parse arbitrary levels of JSON, otherwise - the depth is the limit. - */ - int depth; - /** - To allow C style comments in JSON, set to non-zero. - */ - int allow_comments; - /** - To decode floating point numbers manually set this parameter to - non-zero. - */ - int handle_floats_manually; - /** - The memory allocation routine, which must be semantically - compatible with malloc(3). If set to NULL, malloc(3) is used. - - If this is set to a non-NULL value then the 'free' member MUST be - set to the proper deallocation counterpart for this function. - Failure to do so results in undefined behaviour at deallocation - time. - */ - JSON_malloc_t malloc; - /** - The memory deallocation routine, which must be semantically - compatible with free(3). If set to NULL, free(3) is used. - - If this is set to a non-NULL value then the 'alloc' member MUST be - set to the proper allocation counterpart for this function. - Failure to do so results in undefined behaviour at deallocation - time. - */ - JSON_free_t free; -} JSON_config; - -/*! \brief Initializes the JSON parser configuration structure to default values. - - The default configuration is - - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c) - - no parsing, just checking for JSON syntax - - no comments - - Uses realloc() for memory de/allocation. - - \param config. Used to configure the parser. -*/ -JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config); - -/*! \brief Create a JSON parser object - - \param config. Used to configure the parser. Set to NULL to use - the default configuration. See init_JSON_config. Its contents are - copied by this function, so it need not outlive the returned - object. - - \return The parser object, which is owned by the caller and must eventually - be freed by calling delete_JSON_parser(). -*/ -JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config); - -/*! \brief Destroy a previously created JSON parser object. */ -JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc); - -/*! \brief Parse a character. - - \return Non-zero, if all characters passed to this function are part of are valid JSON. -*/ -JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char); - -/*! \brief Finalize parsing. - - Call this method once after all input characters have been consumed. - - \return Non-zero, if all parsed characters are valid JSON, zero otherwise. -*/ -JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc); - -/*! \brief Determine if a given string is valid JSON white space - - \return Non-zero if the string is valid, zero otherwise. -*/ -JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s); - -/*! \brief Gets the last error that occurred during the use of JSON_parser. - - \return A value from the JSON_error enum. -*/ -JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc); - -/*! \brief Re-sets the parser to prepare it for another parse run. - - \return True (non-zero) on success, 0 on error (e.g. !jc). -*/ -JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc); - - -#ifdef __cplusplus -} -#endif - - -#endif /* JSON_PARSER_H */ -/* end of file JSON_parser/JSON_parser.h */ -/* start of file cwal_json.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -#include -#include /* for a single sprintf() need :/ */ -#include /* toupper(), tolower() */ - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - - -#if defined(__cplusplus) -extern "C" { -#endif - -const cwal_json_output_opt cwal_json_output_opt_empty = - cwal_json_output_opt_empty_m; -const cwal_json_parse_info cwal_json_parse_info_empty = - cwal_json_parse_info_empty_m; - - -/** - Returns true if v is of a type which has a direct JSON representation, - else false. -*/ -static char cwal_json_can_output( cwal_value const * v ); - -/** - Escapes the first len bytes of the given string as JSON and sends - it to the given output function (which will be called often - once - for each logical character). The output is also surrounded by - double-quotes. - - A NULL str will be escaped as an empty string, though we should - arguably export it as "null" (without quotes). We do this because - in JavaScript (typeof null === "object"), and by outputing null - here we would effectively change the data type from string to - object. -*/ -static int cwal_str_to_json( char const * str, unsigned int len, - char escapeFwdSlash, - cwal_output_f f, void * state ) -{ - if( NULL == f ) return CWAL_RC_MISUSE; - else if( !str || !*str || (0 == len) ) - { /* special case for 0-length strings. */ - return f( state, "\"\"", 2 ); - } - else - { - unsigned char const * pos = (unsigned char const *)str; - unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL); - unsigned char const * next = NULL; - int ch; - unsigned char clen = 0; - char escChar[3] = {'\\',0,0}; - enum { - UBLen = 13 * 2 - /* gcc 7.3 is misdiagnosing ubuf (below) as being "between 3 - and 5" bytes in one sprintf() call, so we have to inflate - UBLen beyond what we really need. */ - }; - char ubuf[UBLen]; - int rc = 0; - rc = f(state, "\"", 1 ); - for( ; (pos < end) && (0 == rc); pos += clen ) - { - ch = cwal_utf8_read_char(pos, end, &next); - if( 0 == ch ) break; - assert( next > pos ); - clen = next - pos; - assert( clen ); - if( 1 == clen ) - { /* ASCII */ - assert( *pos == ch ); - escChar[1] = 0; - switch(ch) - { - case '\t': escChar[1] = 't'; break; - case '\r': escChar[1] = 'r'; break; - case '\n': escChar[1] = 'n'; break; - case '\f': escChar[1] = 'f'; break; - case '\b': escChar[1] = 'b'; break; - case '/': - /* - Regarding escaping of forward-slashes. See the main exchange below... - - -------------- - From: Douglas Crockford - To: Stephan Beal - Subject: Re: Is escaping of forward slashes required? - - It is allowed, not required. It is allowed so that JSON can be safely - embedded in HTML, which can freak out when seeing strings containing - " Hello, Jsonites, - > - > i'm a bit confused on a small grammatic detail of JSON: - > - > if i'm reading the grammar chart on http://www.json.org/ correctly, - > forward slashes (/) are supposed to be escaped in JSON. However, the - > JSON class provided with my browsers (Chrome and FF, both of which i - > assume are fairly standards/RFC-compliant) do not escape such characters. - > - > Is backslash-escaping forward slashes required? If so, what is the - > justification for it? (i ask because i find it unnecessary and hard to - > look at.) - -------------- - */ - if( escapeFwdSlash ) escChar[1] = '/'; - break; - case '\\': escChar[1] = '\\'; break; - case '"': escChar[1] = '"'; break; - default: break; - } - if( escChar[1]) - { - rc = f(state, escChar, 2); - } - else - { - rc = f(state, (char const *)pos, clen); - } - continue; - } - else - { /* UTF: transform it to \uXXXX */ - memset(ubuf,0,UBLen); - if(ch <= 0xFFFF){ - rc = sprintf(ubuf, "\\u%04x",ch); - if( rc != 6 ) - { - rc = CWAL_RC_RANGE; - break; - } - rc = f( state, ubuf, 6 ); - }else{ /* encode as a UTF16 surrugate pair */ - /* http://unicodebook.readthedocs.org/en/latest/unicode_encodings.html#surrogates */ - ch -= 0x10000; - rc = sprintf(ubuf, "\\u%04x\\u%04x", - (0xd800 | (ch>>10)), - (0xdc00 | (ch & 0x3ff))); - if( rc != 12 ) - { - rc = CWAL_RC_RANGE; - break; - } - rc = f( state, ubuf, 12 ); - } - continue; - } - } - if( 0 == rc ) rc = f(state, "\"", 1 ); - return rc; - } -} - - -static int cwal_json_output_null( cwal_output_f f, void * state ){ - return f(state, "null", 4); -} - -static int cwal_json_output_bool( cwal_value const * src, cwal_output_f f, void * state ) -{ - char const v = cwal_value_get_bool(src); - return f(state, v ? "true" : "false", v ? 4 : 5); -} - -static int cwal_json_output_integer( cwal_value const * src, cwal_output_f f, void * state ) -{ - if( !f ) return CWAL_RC_MISUSE; - else if( !cwal_value_is_integer(src) ) return CWAL_RC_TYPE; - else { - char b[100]; - cwal_size_t bLen = sizeof(b)/sizeof(b[0]); - int rc; - memset( b, 0, bLen ); - rc = cwal_int_to_cstr( cwal_value_get_integer(src), b, &bLen ); - return rc ? rc : f( state, b, bLen ); - } -} - -static int cwal_json_output_double( cwal_value const * src, cwal_output_f f, void * state ) -{ - if( !f ) return CWAL_RC_MISUSE; - else if( !cwal_value_is_double(src) ) return CWAL_RC_TYPE; - else - { - enum { BufLen = 128 /* this must be relatively large or huge - doubles can cause us to overrun here, - resulting in stack-smashing errors. - */}; - char b[BufLen]; - cwal_size_t bLen = BufLen; - int rc; - memset( b, 0, BufLen ); - rc = cwal_double_to_cstr( cwal_value_get_double(src), b, &bLen ); - if( rc ) return rc; - else if(0) { - /* Strip trailing zeroes before passing it on... */ - char * pos = b + bLen - 1; - for( ; ('0' == *pos) && bLen && (*(pos-1) != '.'); - --pos, --bLen ){ - *pos = 0; - } - assert(bLen && *pos); - return f( state, b, bLen ); - } - else{ - return f( state, b, bLen ); - } - return 0; - } -} - -static int cwal_json_output_string( cwal_value const * src, char escapeFwdSlash, cwal_output_f f, void * state ) -{ - char const * cstr; - cwal_size_t strLen = 0; - if( !f ) return CWAL_RC_MISUSE; - else if(!cwal_value_is_string(src) && !cwal_value_is_buffer(src)){ - return CWAL_RC_TYPE; - }else{ - cstr = cwal_value_get_cstr(src,&strLen); - return cstr - ? cwal_str_to_json(cstr, strLen, escapeFwdSlash, f, state) - : /*only applies to buffers:*/ - cwal_json_output_null( f, state ); - } -} - - -/** - Outputs indention spacing to f(). - - blanks: (0)=no indentation, (-N)=-N TABs per/level, (+N)=n spaces/level - - depth is the current depth of the output tree, and determines how much - indentation to generate. - - If blanks is 0 this is a no-op. Returns non-0 on error, and the - error code will always come from f(). -*/ -static int cwal_json_output_indent( cwal_output_f f, void * state, - cwal_json_output_opt const * opt, - unsigned int depth ) -{ - if(!opt->indent && (!opt->indentString.str || !opt->indentString.len)){ - return 0; - } - else if(opt->indentString.str){ - int i; - int rc = f(state, "\n", 1 ); - assert(opt->indentString.len); - for( i = 0; (i < (int)depth) && (0 == rc); ++i ){ - rc = f(state, opt->indentString.str, - opt->indentString.len); - } - return rc; - }else{ - int blanks = opt->indent; - int i; - int x; - char const ch = (blanks<0) ? '\t' : ' '; - int rc = f(state, "\n", 1 ); - if(blanks<0) blanks = -blanks; - for( i = 0; (i < (int)depth) && (0 == rc); ++i ){ - for( x = 0; (x < blanks) && (0 == rc); ++x ){ - rc = f(state, &ch, 1); - } - } - return rc; - } -} - -static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ); -static int cwal_json_output_tuple( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ); -#if 0 -static int cwal_json_output_object( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ); -#endif - -/** - Outputs a JSON Object from the base->kvp list. -*/ -static int cwal_json_output_obase( cwal_value * base, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ); - - -static int cwal_json_cycle_string( cwal_value * cycled, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt ){ - enum {BufSize = 128}; - char buf[BufSize]; - cwal_size_t nLen = 0; - char const * tname = cwal_value_type_name2(cycled, &nLen); - int slen; - assert(tname); - assert(nLen); - slen = sprintf(buf, "", (int)(nLen>60U ? 60U : nLen), tname, (void const *)cycled); - assert(slen>0); - return cwal_str_to_json( buf, (unsigned)slen, fmt->escapeForwardSlashes, f, state); -} - -/** - Main cwal_json_output() implementation. Dispatches to a different impl depending - on src->vtab->typeID. - - Returns 0 on success. -*/ -static int cwal_json_output_impl( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ) -{ - int rc = 0; - cwal_type_id const tid = cwal_value_type_id( src ); - switch( tid ){ - case CWAL_TYPE_UNDEF: -#if 0 - rc = f( state, "undefined", 9); - break; - /* We should arguably elide this values - that's what JS's - standard impl does. */ -#endif - CWAL_SWITCH_FALL_THROUGH /* transform it to null */; - case CWAL_TYPE_NULL: - rc = cwal_json_output_null(f, state); - break; - case CWAL_TYPE_BOOL: - rc = cwal_json_output_bool(src, f, state); - break; - case CWAL_TYPE_INTEGER: - rc = cwal_json_output_integer(src, f, state); - break; - case CWAL_TYPE_DOUBLE: - rc = cwal_json_output_double(src, f, state); - break; - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_STRING: - rc = cwal_json_output_string(src, fmt->escapeForwardSlashes,f, state); - break; - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_TUPLE: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_OBJECT:{ - int opaque; - rc = cwal_visit_acyclic_begin(src, &opaque); - if(rc){ - if(fmt->cyclesAsStrings){ - rc = cwal_json_cycle_string(src, f, state, fmt); - } - break; - } - switch(tid){ - case CWAL_TYPE_ARRAY: - rc = cwal_json_output_array( src, f, state, fmt, level ); - break; - case CWAL_TYPE_TUPLE: - rc = cwal_json_output_tuple( src, f, state, fmt, level ); - break; - case CWAL_TYPE_FUNCTION: - if(!fmt->functionsAsObjects){ - rc = CWAL_RC_TYPE; - break; - } - CWAL_SWITCH_FALL_THROUGH; - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_OBJECT: - rc = cwal_json_output_obase( src, f, state, fmt, level ); - break; - default: - assert(!"impossible!"); - break; - } - cwal_visit_acyclic_end(src, opaque); - break; - } - default: - rc = CWAL_RC_TYPE; - break; - } - return rc; -} - - - -static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ) -{ - if( !src || !f || !fmt ) return CWAL_RC_MISUSE; - else if( ! cwal_value_is_array(src) ) return CWAL_RC_TYPE; - else if( level > fmt->maxDepth ) return CWAL_RC_RANGE; - else - { - int rc; - unsigned int i; - cwal_value * v; - char doIndent = (fmt->indent || fmt->indentString.len) ? 1 : 0; - cwal_list const * li; - cwal_array const * ar = cwal_value_get_array(src); - int opaque = 0; - li = &ar->list; - assert( NULL != li ); - if( 0 == li->count ) - { - rc = f(state, "[]", 2 ); - goto end; - } - else if( (1 == li->count) && !fmt->indentSingleMemberValues ) doIndent = 0; - cwal_visit_list_begin( src, &opaque ); - rc = f(state, "[", 1); - ++level; - if( doIndent ){ - rc = cwal_json_output_indent( f, state, fmt, level ); - } - for( i = 0; (i < li->count) && (0 == rc); ++i ){ - v = (cwal_value *) li->list[i]; - rc = (v && cwal_json_can_output(v)) - ? cwal_json_output_impl( v, f, state, fmt, level ) - : cwal_json_output_null( f, state ); - if( 0 == rc ){ - if(i+1 < li->count){ - rc = f(state, ",", 1); - if( 0 == rc ){ - rc = doIndent - ? cwal_json_output_indent( f, state, fmt, level ) - : (fmt->addSpaceAfterComma - ? f( state, " ", 1 ) - : 0); - } - } - } - } - if(!rc){ - if( doIndent ){ - rc = cwal_json_output_indent( f, state, fmt, --level ); - } - if(!rc) rc = f(state, "]", 1); - } - cwal_visit_list_end(src, opaque); - end: - return rc; - } -} - -int cwal_json_output_tuple( cwal_value * src, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ) -{ - if( !src || !f || !fmt ) return CWAL_RC_MISUSE; - else if( ! cwal_value_is_tuple(src) ) return CWAL_RC_TYPE; - else if( level > fmt->maxDepth ) return CWAL_RC_RANGE; - else - { - int rc; - unsigned int i; - cwal_value * v; - char doIndent = (fmt->indent || fmt->indentString.len) ? 1 : 0; - cwal_tuple const * tp = cwal_value_get_tuple(src); - cwal_size_t const n = cwal_tuple_length(tp); - int opaque = 0; - if( 0 == n ){ - rc = f(state, "[]", 2 ); - goto end; - } - else if( (1 == n) && !fmt->indentSingleMemberValues ) doIndent = 0; - cwal_visit_list_begin( src, &opaque ); - rc = f(state, "[", 1); - ++level; - if( doIndent ){ - rc = cwal_json_output_indent( f, state, fmt, level ); - } - for( i = 0; (i < n) && (0 == rc); ++i ){ - if(i>0){ - rc = f(state, ",", 1); - if( 0 == rc ){ - rc = doIndent - ? cwal_json_output_indent( f, state, fmt, level ) - : (fmt->addSpaceAfterComma - ? f( state, " ", 1 ) - : 0); - } - if(rc) break; - } - v = (cwal_value *) cwal_tuple_get(tp, i); - rc = (v && cwal_json_can_output(v)) - ? cwal_json_output_impl( v, f, state, fmt, level ) - : cwal_json_output_null( f, state ); - } - if(!rc){ - if( doIndent ){ - rc = cwal_json_output_indent( f, state, fmt, --level ); - } - if(!rc) rc = f(state, "]", 1); - } - cwal_visit_list_end( src, opaque ); - end: - return rc; - } -} - -char cwal_json_can_output( cwal_value const * v ){ - switch(cwal_value_type_id(v)){ - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_STRING: - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_BOOL: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_NULL: - case CWAL_TYPE_TUPLE: - return 1; - default: - return 0; - } -} - -int cwal_json_output_obase( cwal_value * self, cwal_output_f f, void * state, - cwal_json_output_opt const * fmt, unsigned int level ){ - if( !self || !f || !fmt ) return CWAL_RC_MISUSE; - else if( level > fmt->maxDepth ) return CWAL_RC_RANGE; - else{ - int rc = 0; - unsigned int i; - cwal_kvp const * kvp; - cwal_string const * sKey; - cwal_value * val; - char doIndent = (fmt->indent || fmt->indentString.len) ? 1 : 0; - cwal_obase * base = cwal_value_obase(self); - int outputCount = 0 /* keep track of where we need a comma */; - int opaque; - cwal_obase_kvp_iter iter; - if(!base) return CWAL_RC_MISUSE; - assert( (NULL != fmt)); - cwal_visit_props_begin(self, &opaque); - kvp = cwal_obase_kvp_iter_init(self, &iter); - if( 0 == kvp ){ - cwal_visit_props_end(self, opaque); - return f(state, "{}", 2 ); - }else if( cwal_props_count(self)<2 - && !fmt->indentSingleMemberValues ){ - doIndent = 0; - } - rc = f(state, "{", 1); - ++level; - if( !rc && doIndent ){ - rc = cwal_json_output_indent( f, state, fmt, level ); - } - for( i = 0; (0==rc) && kvp; - ++i, kvp = cwal_obase_kvp_iter_next(&iter) ){ - char const * cKey; - cwal_value * key; - if(CWAL_VAR_F_HIDDEN & kvp->flags) continue; - key = cwal_kvp_key( kvp ); - sKey = cwal_value_get_string(key); - if(!sKey){ - /*assert(sKey && "FIXME: cannot handle non-string keys.");*/ - switch(cwal_value_type_id(kvp->key)){ - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE: - break; - default: continue; - } - } - val = cwal_kvp_value(kvp); - if(!cwal_json_can_output(val)) continue; - else if(outputCount){ - /* Output comma between us and our left-side neighbor */ - rc = f(state, ",", 1); - if( 0 == rc ){ - rc = doIndent - ? cwal_json_output_indent( f, state, fmt, level ) - : (fmt->addSpaceAfterComma - ? f( state, " ", 1 ) - : 0); - } - if(rc) break; - } - - if(sKey){ - cKey = cwal_string_cstr(sKey); - rc = cwal_str_to_json(cKey, - cwal_string_length_bytes(sKey), - fmt->escapeForwardSlashes, f, state); - }else{ - rc = f(state, "\"", 1); - if(!rc) rc = cwal_json_output_impl(key, f, state, 0, level); - if(!rc) rc = f(state, "\"", 1); - } - if(0 == rc) rc = fmt->addSpaceAfterColon - ? f(state, ": ", 2 ) - : f(state, ":", 1 ) - ; - if(0 == rc) { - rc = ( val ) - ? cwal_json_output_impl( val, f, state, fmt, level ) - : cwal_json_output_null( f, state ); - ++outputCount; - } - } - --level; - if( doIndent && (0 == rc) ){ - rc = cwal_json_output_indent( f, state, fmt, level ); - } - cwal_visit_props_end(self, opaque); - return rc ? rc : f(state, "}", 1); - } -} - - -int cwal_json_output( cwal_value * src, cwal_output_f f, - void * state, cwal_json_output_opt const * fmt ) -{ - int rc; - if(! fmt ) fmt = &cwal_json_output_opt_empty; - rc = cwal_json_output_impl(src, f, state, fmt, 0 ); - if( (0 == rc) && fmt->addNewline ) - { - rc = f(state, "\n", 1); - } - return rc; -} - -int cwal_json_output_FILE( cwal_value * src, FILE * dest, - cwal_json_output_opt const * fmt ){ - static cwal_json_output_opt sopt = cwal_json_output_opt_empty_m; - int rc = 0; - if(!sopt.addNewline){ - sopt.addNewline = 1; - } - if(!fmt) fmt = &sopt; - rc = cwal_json_output( src, cwal_output_f_FILE, dest, fmt ); - if( 0 == rc ) fflush( dest ); - return rc; -} - -int cwal_json_output_filename( cwal_value * src, - char const * fn, - cwal_json_output_opt const * fmt ) -{ - if( !src || !fn || !*fn ) return CWAL_RC_MISUSE; - else { - FILE * f = (*fn && !fn[1] && ('-'==*fn)) - ? stdout - : fopen( fn, "wb" ); - if( !f ) return CWAL_RC_IO; - else { - int const rc = cwal_json_output_FILE( src, f, fmt ); - if(stdout != f) fclose(f); - return rc; - } - } -} - -int cwal_json_output_buffer( cwal_engine * e, cwal_value * src, - cwal_buffer * dest, - cwal_json_output_opt const * fmt ){ - - static cwal_json_output_opt const outOpt = cwal_json_output_opt_empty_m; - cwal_output_buffer_state job = cwal_output_buffer_state_empty; - if(!e || !src || !dest) return CWAL_RC_MISUSE; - else if(!fmt) fmt = &outOpt; - job.e = e; - job.b = dest; - return cwal_json_output( src, cwal_output_f_buffer, &job, fmt ); -} - - -int cwal_json_output_engine( cwal_engine * e, cwal_value * src, - cwal_json_output_opt const * fmt ){ - return (src && e && e->vtab && e->vtab->outputer.output) - ? cwal_json_output( src, e->vtab->outputer.output, e->vtab->outputer.state.data, fmt ) - : CWAL_RC_MISUSE; -} - -#if CWAL_ENABLE_JSON_PARSER -struct cwal_json_parser{ - JSON_parser p; - cwal_engine * e; - cwal_value * root; - cwal_value * node; - cwal_string * ckey; - int errNo; - char const * errMsg; - cwal_list stack; -}; -typedef struct cwal_json_parser cwal_json_parser; -static const cwal_json_parser cwal_json_parser_empty = { -NULL/*e*/, -NULL/*p*/, -NULL/*root*/, -NULL/*node*/, -NULL/*ckey*/, -0/*errNo*/, -NULL/*errMsg*/, -cwal_list_empty_m/*stack*/ -}; -#endif -/* CWAL_ENABLE_JSON_PARSER */ - -#if CWAL_ENABLE_JSON_PARSER -/** - Converts a JSON_error code to one of the cwal_rc values. -*/ -static int cwal_json_err_to_rc( JSON_error jrc ){ - switch(jrc){ - case JSON_E_NONE: return 0; - case JSON_E_INVALID_CHAR: return CWAL_RC_JSON_INVALID_CHAR; - case JSON_E_INVALID_KEYWORD: return CWAL_RC_JSON_INVALID_KEYWORD; - case JSON_E_INVALID_ESCAPE_SEQUENCE: return CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE; - case JSON_E_INVALID_UNICODE_SEQUENCE: return CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE; - case JSON_E_INVALID_NUMBER: return CWAL_RC_JSON_INVALID_NUMBER; - case JSON_E_NESTING_DEPTH_REACHED: return CWAL_RC_JSON_NESTING_DEPTH_REACHED; - case JSON_E_UNBALANCED_COLLECTION: return CWAL_RC_JSON_UNBALANCED_COLLECTION; - case JSON_E_EXPECTED_KEY: return CWAL_RC_JSON_EXPECTED_KEY; - case JSON_E_EXPECTED_COLON: return CWAL_RC_JSON_EXPECTED_COLON; - case JSON_E_OUT_OF_MEMORY: return CWAL_RC_OOM; - default: - return CWAL_RC_ERROR; - } -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - -#if CWAL_ENABLE_JSON_PARSER -/** @internal - - Cleans up all contents of p but does not free p. - - To properly take over ownership of the parser's root node on a - successful parse: - - - Copy p->root's pointer and set p->root to NULL. - - Eventually free up p->root with cwal_value_free(). - - If you do not set p->root to NULL, p->root will be freed along with - any other items inserted into it (or under it) during the parsing - process. -*/ -static void cwal_json_parser_clean( cwal_json_parser * p ){ - if(p->p) delete_JSON_parser(p->p); - if(p->ckey){ - cwal_value * ckeyV = cwal_string_value(p->ckey); - assert(cwal_value_is_builtin(ckeyV) || cwal_value_refcount(ckeyV)); - cwal_value_unref(ckeyV); - } - cwal_list_reserve(p->e, &p->stack, 0); - if(p->root) cwal_value_unref( p->root ); - *p = cwal_json_parser_empty; -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - - -#if CWAL_ENABLE_JSON_PARSER -/** @internal - - If p->node is-a Object then value is inserted into the object - using p->key. In any other case cwal_rc.InternalError is returned. - - Returns cwal_rc.AllocError if an allocation fails. - - Returns 0 on success. On error, parsing must be ceased immediately. - - This function always takes and removes a ref to val, regardless of - success or failure. Thus it will, on error, clean up val if there - is no other reference to it. (This simplifies error handling in - the core parser.) -*/ -static int cwal_json_parser_set_key( cwal_json_parser * p, cwal_value * val ){ - int rc; - assert( p && val ); - cwal_value_ref(val); - if( p->ckey && cwal_value_is_object(p->node) ){ - cwal_value * ckeyV = cwal_string_value(p->ckey); - assert(cwal_value_is_builtin(ckeyV) || cwal_value_refcount(ckeyV)); - rc = cwal_prop_set_v( p->node, ckeyV, val ); - cwal_value_unref(ckeyV); - p->ckey = NULL /* required to avoid mis-cleanup */; - } - else{ - rc = p->errNo = CWAL_RC_ERROR; - } - cwal_value_unref(val); - return rc; -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - -#if CWAL_ENABLE_JSON_PARSER -/** @internal - - Pushes val into the current object/array parent node, depending on - the internal state of the parser. - - This function always takes and removes a ref to val, regardless of - success or failure. Thus it will, on error, clean up val if there - is no other reference to it. (This simplifies error handling in - the core parser.) - - Returns 0 on success. On error, parsing must be ceased immediately. -*/ -static int cwal_json_parser_push_value( cwal_json_parser * p, cwal_value * val ){ - int rc; - cwal_value_ref(val); - if( p->ckey ){ - /* we're in Object mode */ - assert( cwal_value_is_object( p->node ) ); - rc = cwal_json_parser_set_key( p, val ); - } - else if( cwal_value_is_array( p->node ) ){ - /* we're in Array mode */ - cwal_array * ar = cwal_value_get_array( p->node ); - rc = cwal_array_append( ar, val ); - } - else{ /* Wha??? */ - assert( !"Internal error in cwal_json_parser code" ); - rc = p->errNo = CWAL_RC_ERROR; - } - cwal_value_unref(val); - return rc; -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - -#if CWAL_ENABLE_JSON_PARSER -/** - Callback for JSON_parser API. Reminder: it returns 0 (meaning false) - on error! -*/ -static int cwal_json_parse_callback( void * cx, int type, - JSON_value const * value ){ - cwal_json_parser * p = (cwal_json_parser *)cx; - int rc = 0; - cwal_value * v = NULL; -#define CHECKV if( !v ) { rc = CWAL_RC_OOM; break; } else - switch(type) { - case JSON_T_ARRAY_BEGIN: - case JSON_T_OBJECT_BEGIN: { - cwal_value * obja = (JSON_T_ARRAY_BEGIN == type) - ? cwal_new_array_value(p->e) - : cwal_new_object_value(p->e); - if( ! obja ){ - p->errNo = CWAL_RC_OOM; - break; - } - if( ! p->root ){ - cwal_value_ref(p->root); - p->root = p->node = obja; - rc = cwal_list_append( p->e, &p->stack, obja ); - if( rc ){ - /* work around a (potential) corner case in the cleanup code. */ - p->root = p->node = NULL; - cwal_value_unref(obja); - } - } - else{ - cwal_value_ref(obja); - rc = cwal_json_parser_push_value( p, obja ); - if(!rc){ - rc = cwal_list_append( p->e, &p->stack, obja ); - if( !rc ){ - p->node = obja; - } - } - cwal_value_unref(obja); - } - break; - } - case JSON_T_ARRAY_END: - case JSON_T_OBJECT_END: { - if(!p->stack.count){ - rc = CWAL_RC_RANGE; - break; - } - /* Reminder: do not use cwal_array_pop_back( &p->stack ) - because that will clean up the object, and we don't want - that. We just want to forget this reference - to it. The object is either the root or was pushed into - an object/array in the parse tree (and is owned by that - object/array). - */ - --p->stack.count; - assert( p->node == p->stack.list[p->stack.count] ); - p->stack.list[p->stack.count] = NULL; - if( p->stack.count ){ - p->node = (cwal_value *)p->stack.list[p->stack.count-1]; - } - else p->node = p->root; - break; - } - case JSON_T_INTEGER: { - v = cwal_new_integer(p->e, value->vu.integer_value); - CHECKV { - rc = cwal_json_parser_push_value( p, v ); - break; - } - } - case JSON_T_FLOAT: { - v = cwal_new_double(p->e, value->vu.float_value); - CHECKV { - rc = cwal_json_parser_push_value( p, v ); - break; - } - } - case JSON_T_NULL: { - rc = cwal_json_parser_push_value( p, cwal_value_null() ); - break; - } - case JSON_T_TRUE: { - rc = cwal_json_parser_push_value( p, cwal_value_true() ); - break; - } - case JSON_T_FALSE: { - rc = cwal_json_parser_push_value( p, cwal_value_false() ); - break; - } - case JSON_T_KEY: { - assert(!p->ckey); - p->ckey = cwal_new_string( p->e, - value->vu.str.value, - (cwal_size_t)value->vu.str.length ); - if( ! p->ckey ){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(cwal_string_value(p->ckey)); - break; - } - case JSON_T_STRING: { - v = cwal_new_string_value( p->e, - value->vu.str.value, - (cwal_size_t)value->vu.str.length ); - CHECKV { - rc = cwal_json_parser_push_value( p, v ); - break; - } - } - default: - assert(0); - rc = CWAL_RC_ERROR; - break; - } -#undef CHECKV - return ((p->errNo = rc)) ? 0 : 1; -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - -int cwal_json_parse( cwal_engine * e, cwal_input_f src, - void * state, cwal_value ** tgt, - cwal_json_parse_info * pInfo){ -#if CWAL_ENABLE_JSON_PARSER - unsigned char ch[2] = {0,0}; - int rc = 0; - cwal_size_t len = 1; - cwal_json_parse_info info = pInfo ? *pInfo : cwal_json_parse_info_empty; - cwal_json_parser p = cwal_json_parser_empty; - JSON_config jopt; - if( !e || !tgt || !src ) return CWAL_RC_MISUSE; - memset( &jopt, 0, sizeof(JSON_config) ); - init_JSON_config( &jopt ); - jopt.allow_comments = 0; - jopt.depth = 30; - jopt.callback_ctx = &p; - jopt.handle_floats_manually = 0; - jopt.callback = cwal_json_parse_callback; - p.p = new_JSON_parser(&jopt); - if( !p.p ) return CWAL_RC_OOM; - p.e = e; - do - { /* FIXME: buffer the input in multi-kb chunks. */ - len = 1; - ch[0] = 0; - rc = src( state, ch, &len ); - if( 0 != rc ) break; - else if( !len /* EOF */ ) break; - ++info.length; - if('\n' == ch[0]){ - ++info.line; - info.col = 0; - } - if( ! JSON_parser_char(p.p, ch[0]) ){ - rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) ); - if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR; - info.errorCode = rc; - break; - } - if( '\n' != ch[0]) ++info.col; - } while(1); - if(pInfo) *pInfo = info; - if( 0 != rc ){ - cwal_json_parser_clean(&p); - return rc; - } - if( ! JSON_parser_done(p.p) ){ - rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) ); - cwal_json_parser_clean(&p); - if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR; - } - else{ - cwal_value * root = p.root; - p.root = NULL; - cwal_json_parser_clean(&p); - if( root ) *tgt = root; - else{ /* this can happen on empty input. */ - rc = CWAL_RC_ERROR; - } - } - return rc; -#else - return CWAL_RC_UNSUPPORTED; -#endif - /* CWAL_ENABLE_JSON_PARSER */ -} - - -int cwal_json_parse_FILE( cwal_engine * e, FILE * src, cwal_value ** tgt, - cwal_json_parse_info * pInfo ){ - return cwal_json_parse( e, cwal_input_f_FILE, src, tgt, pInfo ); -} - -int cwal_json_parse_filename( cwal_engine * e, char const * src, - cwal_value ** tgt, - cwal_json_parse_info * pInfo ){ -#if CWAL_ENABLE_JSON_PARSER - if( !src || !tgt ) return CWAL_RC_MISUSE; - else{ - FILE * f = (*src && !src[1] && ('-'==*src)) - ? stdin - : fopen(src, "rb"); - if( !f ) return CWAL_RC_IO; - else{ - int const rc = cwal_json_parse_FILE( e, f, tgt, pInfo ); - if(stdin != f) fclose(f); - return rc; - } - } -#else - return CWAL_RC_UNSUPPORTED; -#endif - /* CWAL_ENABLE_JSON_PARSER */ -} - -#if CWAL_ENABLE_JSON_PARSER -/** Internal type to hold state for a JSON input string. - */ -typedef struct cwal_input_StringSource_{ - /** Start of input string. */ - char const * str; - /** Current iteration position. Must initially be == str. */ - char const * pos; - /** Logical EOF, one-past-the-end of str. */ - char const * end; -} cwal_input_StringSource_t; - -/** - A cwal_input_f() implementation which requires the state argument - to be a properly populated (cwal_input_StringSource_t*). -*/ -static int cwal_input_StringSource( void * state, void * dest, cwal_size_t * n ){ - if( !state || !n || !dest ) return CWAL_RC_MISUSE; - else if( !*n ) return 0 /* ignore this */; - else{ - cwal_size_t i; - cwal_input_StringSource_t * ss = (cwal_input_StringSource_t*) state; - unsigned char * tgt = (unsigned char *)dest; - for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt ) - { - *tgt = *ss->pos; - } - *n = i; - return 0; - } -} -#endif -/* CWAL_ENABLE_JSON_PARSER */ - - -int cwal_json_parse_cstr( cwal_engine * e, char const * src, - cwal_size_t len, cwal_value ** tgt, - cwal_json_parse_info * pInfo ){ -#if CWAL_ENABLE_JSON_PARSER - if( ! tgt || !src ) return CWAL_RC_MISUSE; - else if( !*src || (len<2/*2==len of {} and []*/) ) return CWAL_RC_RANGE; - else{ - cwal_input_StringSource_t ss; - ss.str = ss.pos = src; - ss.end = src + len; - return cwal_json_parse( e, cwal_input_StringSource, &ss, tgt, pInfo ); - } -#else - return CWAL_RC_UNSUPPORTED; -#endif - /* CWAL_ENABLE_JSON_PARSER */ -} - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif -#undef MARKER -/* end of file cwal_json.c */ -/* start of file cwal_printf.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -/************************************************************************ -The printf-like implementation in this file is based on the one found -in the sqlite3 distribution is in the Public Domain. - -This copy was forked for use with the clob API in Feb 2008 by Stephan -Beal (http://wanderinghorse.net/home/stephan/) and modified to send -its output to arbitrary targets via a callback mechanism. Also -refactored the %X specifier handlers a bit to make adding/removing -specific handlers easier. - -All code in this file is released into the Public Domain. - -The printf implementation (cwal_printfv()) is pretty easy to extend -(e.g. adding or removing %-specifiers for cwal_printfv()) if you're -willing to poke around a bit and see how the specifiers are declared -and dispatched. For an example, grep for 'etSTRING' and follow it -through the process of declaration to implementation. - -See below for several WHPRINTF_OMIT_xxx macros which can be set to -remove certain features/extensions. -************************************************************************/ - -#include -#include /* FILE */ -#include /* strlen() */ -#include /* free/malloc() */ -#include -#include -#include - -typedef long double LONGDOUBLE_TYPE; - -#if !defined(CWAL_SWITCH_FALL_THROUGH) -#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7) -/* - gcc v7+ treats implicit 'switch' fallthrough as a warning - (i.e. error because we always build with -Wall -Werror -Wextra - -pedantic). Because now it's apparently considered modern to warn - for using perfectly valid features of the language. Holy cow, guys, - what the hell were you thinking!?!?!? - - Similarly braindead, clang #defines __GNUC__. -*/ -# define CWAL_SWITCH_FALL_THROUGH __attribute__ ((fallthrough)) -#else -# define CWAL_SWITCH_FALL_THROUGH -#endif -#endif - - -/* - If WHPRINTF_OMIT_FLOATING_POINT is defined to a true value, then - floating point conversions are disabled. -*/ -#ifndef WHPRINTF_OMIT_FLOATING_POINT -# define WHPRINTF_OMIT_FLOATING_POINT 0 -#endif - -/* - If WHPRINTF_OMIT_SIZE is defined to a true value, then the %n - specifier is disabled. This must be disabled as of 2021-07-09, as - the %n semantics no longer match the appendf() semantics. -*/ -#define WHPRINTF_OMIT_SIZE 1 - -/* - If WHPRINTF_OMIT_SQL is defined to a true value, then - the %q and %Q specifiers are disabled. -*/ -#ifndef WHPRINTF_OMIT_SQL -# define WHPRINTF_OMIT_SQL 0 -#endif - -/* - If WHPRINTF_OMIT_HTML is defined to a true value then the %h (HTML - escape), %t (URL escape), and %T (URL unescape) specifiers are - disabled. -*/ -#ifndef WHPRINTF_OMIT_HTML -# define WHPRINTF_OMIT_HTML 0 -#endif - -/** - If true, the %j (JSON string) format is enabled. -*/ -#define WHPRINTF_ENABLE_JSON 1 - -/** - If WHPRINTF_OMIT_DYNSTRING is defined to a true value then the - %z (dynamically-allocated string) specifier is disabled. -*/ -#ifndef WHPRINTF_OMIT_DYNSTRING -# define WHPRINTF_OMIT_DYNSTRING 1 -#endif - -/* - Most C compilers handle variable-sized arrays, so we enable - that by default. Some (e.g. tcc) do not, so we provide a way - to disable it: set WHPRINTF_HAVE_VARARRAY to 0 - - One approach would be to look at: - - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) - - but some compilers support variable-sized arrays even when not - explicitly running in c99 mode. -*/ -#if !defined(WHPRINTF_HAVE_VARARRAY) -# if defined(__TINYC__) -# define WHPRINTF_HAVE_VARARRAY 0 -# else -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define WHPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */ -# else -# define WHPRINTF_HAVE_VARARRAY 0 -# endif -# endif -#endif - -/** - WHPRINTF_CHARARRAY is a helper to allocate variable-sized arrays. - This exists mainly so this code can compile with the tcc compiler. -*/ -#if WHPRINTF_HAVE_VARARRAY -# define WHPRINTF_CHARARRAY(V,N) char V[N+1]; memset(V,0,N+1); -# define WHPRINTF_CHARARRAY_FREE(V) -#else -# define WHPRINTF_CHARARRAY(V,N) char * V = (char *)malloc(N); memset(V,0,N); -# define WHPRINTF_CHARARRAY_FREE(V) free(V) -#endif - -/* - Conversion types fall into various categories as defined by the - following enumeration. -*/ -enum PrintfCategory { -etRADIX = 1, /* Integer types. %d, %x, %o, and so forth */ -etFLOAT = 2, /* Floating point. %f */ -etEXP = 3, /* Exponentional notation. %e and %E */ -etGENERIC = 4, /* Floating or exponential, depending on exponent. %g */ -etSIZE = 5, /* Return number of characters processed so far. %n */ -etSTRING = 6, /* Strings. %s */ -#if !WHPRINTF_OMIT_DYNSTRING -etDYNSTRING = 7, /* Dynamically allocated strings. %z */ -#endif -etPERCENT = 8, /* Percent symbol. %% */ -etCHARX = 9, /* Characters. %c */ -/* The rest are extensions, not normally found in printf() */ -etCHARLIT = 10, /* Literal characters. %' */ -#if !WHPRINTF_OMIT_SQL -etSQLESCAPE = 11, /* Strings with '\'' doubled. %q */ -etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '', - NULL pointers replaced by SQL NULL. %Q */ -etSQLESCAPE3 = 16, /* %w -> Strings with '\"' doubled */ -#endif /* !WHPRINTF_OMIT_SQL */ -etPOINTER = 15, /* The %p conversion */ -etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ -#if ! WHPRINTF_OMIT_HTML -etHTML = 18, /* %h -> basic HTML escaping. */ -etURLENCODE = 19, /* %t -> URL encoding. */ -etURLDECODE = 20, /* %T -> URL decoding. */ -#endif -#if WHPRINTF_ENABLE_JSON -etJSONSTR = 21, -#endif -etPLACEHOLDER = 100 -}; - -/* - An "etByte" is an 8-bit unsigned value. -*/ -typedef unsigned char etByte; - -/* - Each builtin conversion character (ex: the 'd' in "%d") is described - by an instance of the following structure -*/ -typedef struct et_info { /* Information about each format field */ - char fmttype; /* The format field code letter */ - etByte base; /* The base for radix conversion */ - etByte flags; /* One or more of FLAG_ constants below */ - etByte type; /* Conversion paradigm */ - etByte charset; /* Offset into aDigits[] of the digits string */ - etByte prefix; /* Offset into aPrefix[] of the prefix string */ -} et_info; - -/* - Allowed values for et_info.flags -*/ -enum et_info_flags { FLAG_SIGNED = 1, /* True if the value to convert is signed */ - FLAG_EXTENDED = 2, /* True if for internal/extended use only. */ - FLAG_STRING = 4 /* Allow infinity precision */ -}; - -/* - Historically, the following table was searched linearly, so the most - common conversions were kept at the front. - - Change 2008 Oct 31 by Stephan Beal: we reserve an array or ordered - entries for all chars in the range [32..126]. Format character - checks can now be done in constant time by addressing that array - directly. This takes more static memory, but reduces the time and - per-call overhead costs of cwal_printfv(). -*/ -static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; -static const char aPrefix[] = "-x0\000X0"; -static const et_info fmtinfo[] = { -/** - If WHPRINTF_FMTINFO_FIXED is 1 then we use the original - implementation: a linear list of entries. Search time is linear. If - WHPRINTF_FMTINFO_FIXED is 0 then we use a fixed-size array which - we index directly using the format char as the key. -*/ -/* - These entries MUST stay in ASCII order, sorted - on their fmttype member! -*/ -{' '/*32*/, 0, 0, 0, 0, 0 }, -{'!'/*33*/, 0, 0, 0, 0, 0 }, -{'"'/*34*/, 0, 0, 0, 0, 0 }, -{'#'/*35*/, 0, 0, 0, 0, 0 }, -{'$'/*36*/, 0, 0, 0, 0, 0 }, -{'%'/*37*/, 0, 0, etPERCENT, 0, 0 }, -{'&'/*38*/, 0, 0, 0, 0, 0 }, -{'\''/*39*/, 0, 0, 0, 0, 0 }, -{'('/*40*/, 0, 0, 0, 0, 0 }, -{')'/*41*/, 0, 0, 0, 0, 0 }, -{'*'/*42*/, 0, 0, 0, 0, 0 }, -{'+'/*43*/, 0, 0, 0, 0, 0 }, -{','/*44*/, 0, 0, 0, 0, 0 }, -{'-'/*45*/, 0, 0, 0, 0, 0 }, -{'.'/*46*/, 0, 0, 0, 0, 0 }, -{'/'/*47*/, 0, 0, 0, 0, 0 } -/* reminder to self: i'd like to port in fossil's path-sanitization - here (%/), which replaces \\ with /, but it requires allocating - and cwal_printf() has no mechanism to pass in a cwal_engine - instance, which means that it can neither use the correct - allocator nor propagate allocation failure properly back to cwal. - - Or... it _could_ be reimplemented to stream output in multiple - calls, so allocation wouldn't strictly need to allocate. Hmmm. -*/, -{'0'/*48*/, 0, 0, 0, 0, 0 }, -{'1'/*49*/, 0, 0, 0, 0, 0 }, -{'2'/*50*/, 0, 0, 0, 0, 0 }, -{'3'/*51*/, 0, 0, 0, 0, 0 }, -{'4'/*52*/, 0, 0, 0, 0, 0 }, -{'5'/*53*/, 0, 0, 0, 0, 0 }, -{'6'/*54*/, 0, 0, 0, 0, 0 }, -{'7'/*55*/, 0, 0, 0, 0, 0 }, -{'8'/*56*/, 0, 0, 0, 0, 0 }, -{'9'/*57*/, 0, 0, 0, 0, 0 }, -{':'/*58*/, 0, 0, 0, 0, 0 }, -{';'/*59*/, 0, 0, 0, 0, 0 }, -{'<'/*60*/, 0, 0, 0, 0, 0 }, -{'='/*61*/, 0, 0, 0, 0, 0 }, -{'>'/*62*/, 0, 0, 0, 0, 0 }, -{'?'/*63*/, 0, 0, 0, 0, 0 }, -{'@'/*64*/, 0, 0, 0, 0, 0 }, -{'A'/*65*/, 0, 0, 0, 0, 0 }, -{'B'/*66*/, 0, 0, 0, 0, 0 }, -{'C'/*67*/, 0, 0, 0, 0, 0 }, -{'D'/*68*/, 0, 0, 0, 0, 0 }, -{'E'/*69*/, 0, FLAG_SIGNED, etEXP, 14, 0 }, -{'F'/*70*/, 0, 0, 0, 0, 0 }, -{'G'/*71*/, 0, FLAG_SIGNED, etGENERIC, 14, 0 }, -{'H'/*72*/, 0, 0, 0, 0, 0 }, -{'I'/*73*/, 0, 0, 0, 0, 0 }, -{'J'/*74*/, 0, 0, 0, 0, 0 }, -{'K'/*75*/, 0, 0, 0, 0, 0 }, -{'L'/*76*/, 0, 0, 0, 0, 0 }, -{'M'/*77*/, 0, 0, 0, 0, 0 }, -{'N'/*78*/, 0, 0, 0, 0, 0 }, -{'O'/*79*/, 0, 0, 0, 0, 0 }, -{'P'/*80*/, 0, 0, 0, 0, 0 }, -#if WHPRINTF_OMIT_SQL -{'Q'/*81*/, 0, 0, 0, 0, 0 }, -#else -{'Q'/*81*/, 0, FLAG_STRING, etSQLESCAPE2, 0, 0 }, -#endif -{'R'/*82*/, 0, 0, 0, 0, 0 }, -{'S'/*83*/, 0, 0, 0, 0, 0 }, -{'T'/*84*/, 0, FLAG_STRING, etURLDECODE, 0, 0 }, -{'U'/*85*/, 0, 0, 0, 0, 0 }, -{'V'/*86*/, 0, 0, 0, 0, 0 }, -{'W'/*87*/, 0, 0, 0, 0, 0 }, -{'X'/*88*/, 16, 0, etRADIX, 0, 4 }, -{'Y'/*89*/, 0, 0, 0, 0, 0 }, -{'Z'/*90*/, 0, 0, 0, 0, 0 }, -{'['/*91*/, 0, 0, 0, 0, 0 }, -{'\\'/*92*/, 0, 0, 0, 0, 0 }, -{']'/*93*/, 0, 0, 0, 0, 0 }, -{'^'/*94*/, 0, 0, 0, 0, 0 }, -{'_'/*95*/, 0, 0, 0, 0, 0 }, -{'`'/*96*/, 0, 0, 0, 0, 0 }, -{'a'/*97*/, 0, 0, 0, 0, 0 }, -{'b'/*98*/, 0, 0, 0, 0, 0 }, -{'c'/*99*/, 0, 0, etCHARX, 0, 0 }, -{'d'/*100*/, 10, FLAG_SIGNED, etRADIX, 0, 0 }, -{'e'/*101*/, 0, FLAG_SIGNED, etEXP, 30, 0 }, -{'f'/*102*/, 0, FLAG_SIGNED, etFLOAT, 0, 0}, -{'g'/*103*/, 0, FLAG_SIGNED, etGENERIC, 30, 0 }, -{'h'/*104*/, 0, FLAG_STRING, etHTML, 0, 0 }, -{'i'/*105*/, 10, FLAG_SIGNED, etRADIX, 0, 0}, -#if WHPRINTF_ENABLE_JSON -{'j'/*106*/, 0, 0, etJSONSTR, 0, 0 }, -#else -{'j'/*106*/, 0, 0, 0, 0, 0 }, -#endif -{'k'/*107*/, 0, 0, 0, 0, 0 }, -{'l'/*108*/, 0, 0, 0, 0, 0 }, -{'m'/*109*/, 0, 0, 0, 0, 0 }, -{'n'/*110*/, 0, 0, etSIZE, 0, 0 }, -{'o'/*111*/, 8, 0, etRADIX, 0, 2 }, -{'p'/*112*/, 16, 0, etPOINTER, 16, 1 }, -#if WHPRINTF_OMIT_SQL -{'q'/*113*/, 0, 0, 0, 0, 0 }, -#else -{'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, -#endif -{'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0}, -{'s'/*115*/, 0, FLAG_STRING, etSTRING, 0, 0 }, -{'t'/*116*/, 0, FLAG_STRING, etURLENCODE, 0, 0 }, -{'u'/*117*/, 10, 0, etRADIX, 0, 0 }, -{'v'/*118*/, 0, 0, 0, 0, 0 }, -#if WHPRINTF_OMIT_SQL -{'w'/*119*/, 0, 0, 0, 0, 0 }, -#else -{'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, -#endif -{'x'/*120*/, 16, 0, etRADIX, 16, 1 }, -{'y'/*121*/, 0, 0, 0, 0, 0 }, -#if !WHPRINTF_OMIT_DYNSTRING -{'z'/*122*/, 0, FLAG_STRING, etDYNSTRING, 0, 0}, -#else -{'z'/*122*/, 0, 0, 0, 0, 0}, -#endif -{'{'/*123*/, 0, 0, 0, 0, 0 }, -{'|'/*124*/, 0, 0, 0, 0, 0 }, -{'}'/*125*/, 0, 0, 0, 0, 0 }, -{'~'/*126*/, 0, 0, 0, 0, 0 }, -}; -#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) - -#if ! WHPRINTF_OMIT_FLOATING_POINT -/* - "*val" is a double such that 0.1 <= *val < 10.0 - Return the ascii code for the leading digit of *val, then - multiply "*val" by 10.0 to renormalize. - ** - Example: - input: *val = 3.14159 - output: *val = 1.4159 function return = '3' - ** - The counter *cnt is incremented each time. After counter exceeds - 16 (the number of significant digits in a 64-bit float) '0' is - always returned. -*/ -static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ - int digit; - LONGDOUBLE_TYPE d; - if( (*cnt)++ >= 16 ) return '0'; - digit = (int)*val; - d = digit; - digit += '0'; - *val = (*val - d)*10.0; - return digit; -} -#endif /* !WHPRINTF_OMIT_FLOATING_POINT */ - -/* - On machines with a small(?) stack size, you can redefine the - WHPRINTF_BUF_SIZE to be less than 350. But beware - for smaller - values some %f conversions may go into an infinite loop. -*/ -#ifndef WHPRINTF_BUF_SIZE -# define WHPRINTF_BUF_SIZE 350 /* Size of the output buffer for numeric conversions */ -#endif - -/** - cwal_printf_spec_handler is an almost-generic interface for farming - work out of cwal_printfv()'s code into external functions. It - doesn't actually save much (if any) overall code, but it makes the - cwal_printfv() code more manageable. - - REQUIREMENTS of implementations: - - - Expects an implementation-specific vargp pointer. - cwal_printfv() passes a pointer to the converted value of - an entry from the format va_list. If it passes a type - other than the expected one, undefined results. - - - If it calls pf then it must return the return value - from that function. - - - If it calls pf it must do: pf( pfArg, D, N ), where D is - the data to export and N is the number of bytes to export. - It may call pf() an arbitrary number of times - - - If pf() returns non-0, it must return that code. - - - On success, must return 0. On error, non-0. - - SIGNIFICANT(?) LIMITATIONS: - - - Has no way of iterating over the format string, so handling - precisions and such here can't work. -*/ -typedef int (*cwal_printf_spec_handler)( cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * vargp ); - - -#if !WHPRINTF_OMIT_DYNSTRING -/** - cwal_printf_spec_handler for etDYNSTRING types. It assumes that varg - is a non-const (char *). It behaves identically to spec_string() and - then calls free() on that (char *). -*/ -static int spech_dynstring( cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * varg ) -{ - char const * ch = (char const *) varg; - int const ret = ch ? pf( pfArg, ch, pfLen ) : 0; - free( (char *) varg ); - return ret; -} -#endif - -#if !WHPRINTF_OMIT_HTML -static int spech_string_to_html( cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * varg ) -{ - char const * ch = (char const *) varg; - unsigned int i; - int rc = 0; - if( ! ch ) return 0; - for( i = 0; rc==0 && (i= 32 && c <=47) - || ( c>=58 && c<=64) - || ( c>=91 && c<=96) - || ( c>=123 && c<=126) - || ( c<32 || c>=127) - ); -} - -/** - The handler for the etURLENCODE specifier. - - It expects varg to be a string value, which it will preceed to - encode using an URL encoding algothrim (certain characters are - converted to %XX, where XX is their hex value) and passes the - encoded string to pf(). It returns the total length of the output - string. -*/ -static int spech_urlencode( cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * varg ) -{ - char const * str = (char const *) varg; - int rc = 0; - char ch = 0; - char const * hex = "0123456789ABCDEF"; -#define xbufsz 10 - char xbuf[xbufsz]; - int slen = 0; - if( ! str ) return 0; - memset( xbuf, 0, xbufsz ); - ch = *str; -#define xbufsz 10 - slen = 0; - for( ; ch && rc==0; ch = *(++str) ){ - if( ! httpurl_needs_escape( ch ) ){ - rc = pf( pfArg, str, 1 ); - continue; - } - else { - xbuf[0] = '%'; - xbuf[1] = hex[((ch>>4)&0xf)]; - xbuf[2] = hex[(ch&0xf)]; - xbuf[3] = 0; - slen = 3; - rc = pf( pfArg, xbuf, slen ); - } - } -#undef xbufsz - if(pfLen){/*avoid unused param warning*/} - return rc; -} - -/* - hexchar_to_int(): - - For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal - number. For any other character it returns -1. -*/ -static int hexchar_to_int( int ch ){ - if( (ch>='a' && ch<='f') ) return ch-'a'+10; - else if( (ch>='A' && ch<='F') ) return ch-'A'+10; - else if( (ch>='0' && ch<='9') ) return ch-'0'; - return -1; -} - -/** - The handler for the etURLDECODE specifier. - - It expects varg to be a ([const] char *), possibly encoded - with URL encoding. It decodes the string using a URL decode - algorithm and passes the decoded string to - pf(). It returns the total length of the output string. - If the input string contains malformed %XX codes then this - function will return prematurely. -*/ -static int spech_urldecode( cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * varg ){ - char const * str = (char const *) varg; - int rc = 0; - int ch = 0; - int ch2 = 0; - char xbuf[4]; - int decoded; - if( ! str ) return 0; - ch = *str; - while( ch && !rc ){ - if( ch == '%' ){ - ch = *(++str); - ch2 = *(++str); - if( isxdigit(ch) && - isxdigit(ch2) ){ - decoded = (hexchar_to_int( ch ) * 16) - + hexchar_to_int( ch2 ); - xbuf[0] = (char)decoded; - xbuf[1] = 0; - rc = pf( pfArg, xbuf, 1 ); - ch = *(++str); - continue; - }else{ - xbuf[0] = '%'; - xbuf[1] = ch; - xbuf[2] = ch2; - xbuf[3] = 0; - rc = pf( pfArg, xbuf, 3 ); - ch = *(++str); - continue; - } - }else if( ch == '+' ){ - xbuf[0] = ' '; - xbuf[1] = 0; - rc = pf( pfArg, xbuf, 1 ); - ch = *(++str); - continue; - } - xbuf[0] = ch; - xbuf[1] = 0; - rc = pf( pfArg, xbuf, 1 ); - ch = *(++str); - } - if(pfLen){/*avoid unused param warning*/} - return rc; -} - -#endif /* !WHPRINTF_OMIT_HTML */ - - -#if !WHPRINTF_OMIT_SQL -/** - Quotes the (char *) varg as an SQL string 'should' - be quoted. The exact type of the conversion - is specified by xtype, which must be one of - etSQLESCAPE, etSQLESCAPE2, or etSQLESCAPE3. - - Search this file for those constants to find - the associated documentation. -*/ -static int spech_sqlstring_main( int xtype, - cwal_printf_appender_f pf, - void * pfArg, - unsigned int pfLen, - void * varg ){ - unsigned int i, n; - int j, ch, isnull; - int needQuote, rc = 0; - char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ - char const * escarg = (char const *) varg; - char * bufpt = NULL; - char buffer[1024 * 2] = {0}; - isnull = escarg==0; - if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); - for(i=n=0; (i=zTerm){ - *pzNext = zTerm; - c = 0; - }else{ - c = (unsigned int)*(zIn++); - if( c>=0xc0 ){ - c = appendf_utfTrans1[c-0xc0]; - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ) - c = (c<<6) + (0x3f & *(zIn++)); - if( c<0x80 - || (c&0xFFFFF800)==0xD800 - || (c&0xFFFFFFFE)==0xFFFE ) c = 0xFFFD; - } - *pzNext = zIn; - } - return c; -} - -static int appendf_utf8_char_to_cstr(unsigned int c, unsigned char *output, - unsigned char length){ - /* Stolen from the internet, adapted from several variations which - all _seem_ to have derived from librdf. */ - unsigned char size=0; - - /* check for illegal code positions: - * U+D800 to U+DFFF (UTF-16 surrogates) - * U+FFFE and U+FFFF - */ - if((c > 0xD7FF && c < 0xE000) - || c == 0xFFFE || c == 0xFFFF) return -1; - - /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */ - if(c > 0x10ffff) return -1; - - if (c < 0x00000080) size = 1; - else if (c < 0x00000800) size = 2; - else if (c < 0x00010000) size = 3; - else size = 4; - if(!output) return (int)size; - else if(size > length) return -1; - else switch(size) { - case 0: - assert(!"can't happen anymore"); - output[0] = 0; - return 0; - case 4: - output[3] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0x10000; - CWAL_SWITCH_FALL_THROUGH; - case 3: - output[2] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0x800; - CWAL_SWITCH_FALL_THROUGH; - case 2: - output[1] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0xc0; - CWAL_SWITCH_FALL_THROUGH; - case 1: - output[0] = (unsigned char)c; - CWAL_SWITCH_FALL_THROUGH; - default: - return (int)size; - } -} - -struct SpechJson { - char const * z; - bool addQuotes; - bool escapeSmallUtf8; -}; - -/** - cwal_printf_spec_handler for etJSONSTR. It assumes that varg is a - SpechJson struct instance. -*/ -static int spech_json( cwal_printf_appender_f pf, void * pfArg, - unsigned int pfLen, void * varg ) -{ - struct SpechJson const * state = (struct SpechJson *)varg; - int pfRc = 0; - const unsigned char *z = (const unsigned char *)state->z; - const unsigned char *zEnd = z + pfLen; - const unsigned char * zNext = 0; - unsigned int c; - unsigned char c1; - -#define out(X,N) pfRc=pf(pfArg, (char const *)(X), N); \ - if(pfRc) return pfRc -#define outc c1 = (unsigned char)c; out(&c1,1) - if(!z){ - out("null",4); - return pfRc; - } - if(state->addQuotes){ - out("\"", 1); - } - for( ; (z < zEnd) && (c=appendf_utf8_read_char(z, zEnd, &zNext)); - z = zNext ){ - if( c=='\\' || c=='"' ){ - out("\\", 1); - outc; - }else if( c<' ' ){ - out("\\",1); - if( c=='\n' ){ - out("n",1); - }else if( c=='\r' ){ - out("r",1); - }else{ - unsigned char ubuf[5] = {'u',0,0,0,0}; - int i; - for(i = 4; i>0; --i){ - ubuf[i] = "0123456789abcdef"[c&0xf]; - c >>= 4; - } - out(ubuf,5); - } - }else if(c<128){ - outc; - }/* At this point we know that c is part of a multi-byte - character. We're assuming legal UTF8 input, which means - emitting a surrogate pair if the value is > 0xffff. */ - else if(c<0xFFFF){ - unsigned char ubuf[12]; - if(state->escapeSmallUtf8){ - /* Output char in \u#### form. */ - snprintf((char *)ubuf, sizeof(ubuf), "\\u%04x", c) - /* gcc incorrectly misdiagnoses the output length here, - thus our buffer and "n" value is bigger than - necessary */; - out(ubuf, 6); - }else{ - /* Output character literal. */ - int const n = appendf_utf8_char_to_cstr(c, ubuf, 4); - if(n<0){ - out("?",1); - }else{ - assert(n>0); - out(ubuf, n); - } - } - }else{ - /* Surrogate pair. */ - unsigned char ubuf[24]; - c -= 0x10000; - snprintf((char *)ubuf, sizeof(ubuf), "\\u%04x\\u%04x", - (0xd800 | (c>>10)), - (0xdc00 | (c & 0x3ff)) - /* gcc incorrectly misdiagnoses the output length here, - thus our buffer and "n" value is bigger than - necessary */); - out(ubuf, 12); - } - } - if(state->addQuotes){ - out("\"",1); - } - return pfRc; -#undef out -#undef outc -} -#endif /* WHPRINTF_ENABLE_JSON */ - - -/* - The root printf program. All variations call this core. It - implements most of the common printf behaviours plus (optionally) - some extended ones. - - INPUTS: - - pfAppend : The is a cwal_printf_appender_f function which is responsible - for accumulating the output. If pfAppend returns a negative integer - then processing stops immediately. - - pfAppendArg : is ignored by this function but passed as the first - argument to pfAppend. pfAppend will presumably use it as a data - store for accumulating its string. - - fmt : This is the format string, as in the usual printf(). - - ap : This is a pointer to a list of arguments. Same as in - vprintf() and friends. - - OUTPUTS: - - The return value is the total number of characters sent to the - function "func". Returns -1 on a error. - - Note that the order in which automatic variables are declared below - seems to make a big difference in determining how fast this beast - will run. - - Much of this code dates back to the early 1980's, supposedly. - - Known change history (most historic info has been lost): - - 10 Feb 2008 by Stephan Beal: refactored to remove the 'useExtended' - flag (which is now always on). Added the cwal_printf_appender_f typedef to - make this function generic enough to drop into other source trees - without much work. - - 31 Oct 2008 by Stephan Beal: refactored the et_info lookup to be - constant-time instead of linear. - - 2021-07 by Stephan Beal: changed semantics to return 0 on success, - non-0 on error, as the conventional semantics are pretty useless in - practice and we need to be able to notify clients on OOM conditions. -*/ -int cwal_printfv(cwal_printf_appender_f pfAppend, /* Accumulate results here */ - void * pfAppendArg, /* Passed as first arg to pfAppend. */ - const char *fmt, /* Format string */ - va_list ap /* arguments */ - ){ - /** - HISTORIC NOTE (author and year unknown, as is whether this still applies): - - Note that the order in which automatic variables are declared below - seems to make a big difference in determining how fast this beast - will run. - */ - int rc = 0; /* accumulated output count */ - int pfrc = 0; /* result from calling pfAppend */ - int c; /* Next character in the format string */ - char *bufpt = 0; /* Pointer to the conversion buffer */ - int precision; /* Precision of the current field */ - int length; /* Length of the field */ - int idx; /* A general purpose loop counter */ - int width; /* Width of the current field */ - etByte flag_leftjustify; /* True if "-" flag is present */ - etByte flag_plussign; /* True if "+" flag is present */ - etByte flag_blanksign; /* True if " " flag is present */ - etByte flag_alternateform; /* True if "#" flag is present */ - etByte flag_altform2; /* True if "!" flag is present */ - etByte flag_zeropad; /* True if field width constant starts with zero */ - etByte flag_long; /* True if "l" flag is present */ - etByte flag_longlong; /* True if the "ll" flag is present */ - etByte done; /* Loop termination flag */ - uint64_t longvalue; /* Value for integer types */ - LONGDOUBLE_TYPE realvalue; /* Value for real types */ - const et_info *infop = 0; /* Pointer to the appropriate info structure */ - char buf[WHPRINTF_BUF_SIZE]; /* Conversion buffer */ - char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ - etByte xtype = 0; /* Conversion paradigm */ - char * zExtra = 0; /* Extra memory used for etTCLESCAPE conversions */ -#if ! WHPRINTF_OMIT_FLOATING_POINT - int exp, e2; /* exponent of real numbers */ - double rounder; /* Used for rounding floating point values */ - etByte flag_dp; /* True if decimal point should be shown */ - etByte flag_rtz; /* True if trailing zeros should be removed */ - etByte flag_exp; /* True to force display of the exponent */ - int nsd; /* Number of significant digits returned */ -#endif - - - /* WHPRINTF_RETURN, WHPRINTF_CHECKERR, and WHPRINTF_SPACES - are internal helpers. - */ -#define WHPRINTF_RETURN if( zExtra ) free(zExtra); return pfrc ? pfrc : rc; -#if WHPRINTF_HAVE_VARARRAY - /* - This impl possibly mallocs - */ -#define WHPRINTF_CHECKERR if( pfrc ) { WHPRINTF_RETURN; } (void)0 -#define WHPRINTF_SPACES(N) \ - if(1){ \ - WHPRINTF_CHARARRAY(zSpaces,N); \ - memset( zSpaces,' ',N); \ - pfrc = pfAppend(pfAppendArg, zSpaces, N); \ - WHPRINTF_CHARARRAY_FREE(zSpaces); \ - WHPRINTF_CHECKERR; \ - }(void)0 -#else - /* But this one is subject to potential wrong output - on "unusual" inputs. - - FIXME: turn this into a loop on N, so we can do away with the - limit. - - reminder to self: libfossil's fork uses a similar, arguably - better, approach, which falls back to allocating. - */ -#define WHPRINTF_CHECKERR if( pfrc!=0 ) { WHPRINTF_RETURN; } -#define WHPRINTF_SPACES(N) \ - if(1){ \ - enum { BufSz = 128 }; \ - char zSpaces[BufSz]; \ - unsigned int n = ((N)>=BufSz) ? BufSz : (int)N; \ - memset( zSpaces,' ',n); \ - pfrc = pfAppend(pfAppendArg, zSpaces, n); \ - WHPRINTF_CHECKERR; \ - }(void)0 -#endif - - length = 0; - bufpt = 0; - for(; (c=(*fmt))!=0; ++fmt){ - if( c!='%' ){ - int amt; - bufpt = (char *)fmt; - amt = 1; - while( (c=(*++fmt))!='%' && c!=0 ) amt++; - pfrc = pfAppend( pfAppendArg, bufpt, amt); - WHPRINTF_CHECKERR; - if( c==0 ) break; - } - if( (c=(*++fmt))==0 ){ - pfrc = pfAppend( pfAppendArg, "%", 1); - WHPRINTF_CHECKERR; - break; - } - /* Find out what flags are present */ - flag_leftjustify = flag_plussign = flag_blanksign = - flag_alternateform = flag_altform2 = flag_zeropad = 0; - done = 0; - do{ - switch( c ){ - case '-': flag_leftjustify = 1; break; - case '+': flag_plussign = 1; break; - case ' ': flag_blanksign = 1; break; - case '#': flag_alternateform = 1; break; - case '!': flag_altform2 = 1; break; - case '0': flag_zeropad = 1; break; - default: done = 1; break; - } - }while( !done && (c=(*++fmt))!=0 ); - /* Get the field width */ - width = 0; - if( c=='*' ){ - width = va_arg(ap,int); - if( width<0 ){ - flag_leftjustify = 1; - width = width >= -2147483647 ? -width : 0; - } - c = *++fmt; - }else{ - unsigned wx = 0; - while( c>='0' && c<='9' ){ - wx = wx * 10 + c - '0'; - width = width*10 + c - '0'; - c = *++fmt; - } - width = wx & 0x7fffffff; - } - if( width > WHPRINTF_BUF_SIZE-10 ){ - width = WHPRINTF_BUF_SIZE-10; - } - /* Get the precision */ - if( c=='.' ){ - precision = 0; - c = *++fmt; - if( c=='*' ){ - precision = va_arg(ap,int); - if( precision<0 ) precision = -precision; - c = *++fmt; - if( precision<0 ){ - precision = precision >= -2147483647 ? -precision : -1; - } - - }else{ - unsigned px = 0; - while( c>='0' && c<='9' ){ - px = px*10 + c - '0'; - c = *++fmt; - } - precision = px & 0x7fffffff; - } - }else{ - precision = -1; - } - /* Get the conversion type modifier */ - if( c=='l' ){ - flag_long = 1; - c = *++fmt; - if( c=='l' ){ - flag_longlong = 1; - c = *++fmt; - }else{ - flag_longlong = 0; - } - }else{ - flag_long = flag_longlong = 0; - } - /* Fetch the info entry for the field */ - infop = 0; -#define FMTNDX(N) (N - fmtinfo[0].fmttype) -#define FMTINFO(N) (fmtinfo[ FMTNDX(N) ]) - infop = ((c>=(fmtinfo[0].fmttype)) && (cfmttype,infop->type);*/ - if( infop ) xtype = infop->type; -#undef FMTINFO -#undef FMTNDX - zExtra = 0; - if( (!infop) || (!infop->type) ){ - WHPRINTF_RETURN; - } - - /* Limit the precision to prevent overflowing buf[] during conversion */ - if( precision>WHPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){ - precision = WHPRINTF_BUF_SIZE-40; - } - - /* - At this point, variables are initialized as follows: - ** - flag_alternateform TRUE if a '#' is present. - flag_altform2 TRUE if a '!' is present. - flag_plussign TRUE if a '+' is present. - flag_leftjustify TRUE if a '-' is present or if the - field width was negative. - flag_zeropad TRUE if the width began with 0. - flag_long TRUE if the letter 'l' (ell) prefixed - the conversion character. - flag_longlong TRUE if the letter 'll' (ell ell) prefixed - the conversion character. - flag_blanksign TRUE if a ' ' is present. - width The specified field width. This is - always non-negative. Zero is the default. - precision The specified precision. The default - is -1. - xtype The class of the conversion. - infop Pointer to the appropriate info struct. - */ - switch( xtype ){ - case etPOINTER: - flag_longlong = sizeof(char*)==sizeof(int64_t); - flag_long = sizeof(char*)==sizeof(long int); - CWAL_SWITCH_FALL_THROUGH; - case etORDINAL: - case etRADIX: - if( infop->flags & FLAG_SIGNED ){ - int64_t v; - if( flag_longlong ) v = va_arg(ap,int64_t); - else if( flag_long ) v = va_arg(ap,long int); - else v = va_arg(ap,int); - if( v<0 ){ - longvalue = -v; - prefix = '-'; - }else{ - longvalue = v; - if( flag_plussign ) prefix = '+'; - else if( flag_blanksign ) prefix = ' '; - else prefix = 0; - } - }else{ - if( flag_longlong ) longvalue = va_arg(ap,uint64_t); - else if( flag_long ) longvalue = va_arg(ap,unsigned long int); - else longvalue = va_arg(ap,unsigned int); - prefix = 0; - } - if( longvalue==0 ) flag_alternateform = 0; - if( flag_zeropad && precision=4 || (longvalue/10)%10==1 ){ - x = 0; - } - buf[WHPRINTF_BUF_SIZE-3] = zOrd[x*2]; - buf[WHPRINTF_BUF_SIZE-2] = zOrd[x*2+1]; - bufpt -= 2; - } - { - const char *cset; - int base; - cset = &aDigits[infop->charset]; - base = infop->base; - do{ /* Convert to ascii */ - *(--bufpt) = cset[longvalue%base]; - longvalue = longvalue/base; - }while( longvalue>0 ); - } - length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt; - for(idx=precision-length; idx>0; idx--){ - *(--bufpt) = '0'; /* Zero pad */ - } - if( prefix ) *(--bufpt) = prefix; /* Add sign */ - if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ - const char *pre; - char x; - pre = &aPrefix[infop->prefix]; - if( *bufpt!=pre[0] ){ - for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; - } - } - length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt; - break; - case etFLOAT: - case etEXP: - case etGENERIC: - realvalue = va_arg(ap,double); -#if ! WHPRINTF_OMIT_FLOATING_POINT - if( precision<0 ) precision = 6; /* Set default precision */ - if( precision>WHPRINTF_BUF_SIZE/2-10 ) precision = WHPRINTF_BUF_SIZE/2-10; - if( realvalue<0.0 ){ - realvalue = -realvalue; - prefix = '-'; - }else{ - if( flag_plussign ) prefix = '+'; - else if( flag_blanksign ) prefix = ' '; - else prefix = 0; - } - if( xtype==etGENERIC && precision>0 ) precision--; -#if 0 - /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ - for(idx=precision & 0xfff, rounder=0.4999; idx>0; idx--, rounder*=0.1); -#else - /* It makes more sense to use 0.5 */ - for(idx=precision & 0xfff, rounder=0.5; idx>0; idx--, rounder*=0.1){} -#endif - if( xtype==etFLOAT ) realvalue += rounder; - /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ - exp = 0; -#if 1 - if( (realvalue)!=(realvalue) ){ - /* from sqlite3: #define sqlite3_isnan(X) ((X)!=(X)) */ - /* This weird array thing is to avoid constness violations - when assinging, e.g. "NaN" to bufpt. - */ - static char NaN[4] = {'N','a','N','\0'}; - bufpt = NaN; - length = 3; - break; - } -#endif - if( realvalue>0.0 ){ - while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; } - while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } - while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } - while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } - while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } - if( exp>350 || exp<-350 ){ - if( prefix=='-' ){ - static char Inf[5] = {'-','I','n','f','\0'}; - bufpt = Inf; - }else if( prefix=='+' ){ - static char Inf[5] = {'+','I','n','f','\0'}; - bufpt = Inf; - }else{ - static char Inf[4] = {'I','n','f','\0'}; - bufpt = Inf; - } - length = strlen(bufpt); - break; - } - } - bufpt = buf; - /* - If the field type is etGENERIC, then convert to either etEXP - or etFLOAT, as appropriate. - */ - flag_exp = xtype==etEXP; - if( xtype!=etFLOAT ){ - realvalue += rounder; - if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } - } - if( xtype==etGENERIC ){ - flag_rtz = !flag_alternateform; - if( exp<-4 || exp>precision ){ - xtype = etEXP; - }else{ - precision = precision - exp; - xtype = etFLOAT; - } - }else{ - flag_rtz = 0; - } - if( xtype==etEXP ){ - e2 = 0; - }else{ - e2 = exp; - } - nsd = 0; - flag_dp = (precision>0) | flag_alternateform | flag_altform2; - /* The sign in front of the number */ - if( prefix ){ - *(bufpt++) = prefix; - } - /* Digits prior to the decimal point */ - if( e2<0 ){ - *(bufpt++) = '0'; - }else{ - for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); - } - } - /* The decimal point */ - if( flag_dp ){ - *(bufpt++) = '.'; - } - /* "0" digits after the decimal point but before the first - significant digit of the number */ - for(e2++; e2<0 && precision>0; precision--, e2++){ - *(bufpt++) = '0'; - } - /* Significant digits after the decimal point */ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); - } - /* Remove trailing zeros and the "." if no digits follow the "." */ - if( flag_rtz && flag_dp ){ - while( bufpt[-1]=='0' ) *(--bufpt) = 0; - /* assert( bufpt>buf ); */ - if( bufpt[-1]=='.' ){ - if( flag_altform2 ){ - *(bufpt++) = '0'; - }else{ - *(--bufpt) = 0; - } - } - } - /* Add the "eNNN" suffix */ - if( flag_exp || (xtype==etEXP && exp) ){ - *(bufpt++) = aDigits[infop->charset]; - if( exp<0 ){ - *(bufpt++) = '-'; exp = -exp; - }else{ - *(bufpt++) = '+'; - } - if( exp>=100 ){ - *(bufpt++) = (exp/100)+'0'; /* 100's digit */ - exp %= 100; - } - *(bufpt++) = exp/10+'0'; /* 10's digit */ - *(bufpt++) = exp%10+'0'; /* 1's digit */ - } - *bufpt = 0; - - /* The converted number is in buf[] and zero terminated. Output it. - Note that the number is in the usual order, not reversed as with - integer conversions. */ - length = bufpt-buf; - bufpt = buf; - - /* Special case: Add leading zeros if the flag_zeropad flag is - set and we are not left justified */ - if( flag_zeropad && !flag_leftjustify && length < width){ - int i; - int nPad = width - length; - for(i=width; i>=nPad; i--){ - bufpt[i] = bufpt[i-nPad]; - } - i = prefix!=0; - while( nPad-- ) bufpt[i++] = '0'; - length = width; - } -#endif /* !WHPRINTF_OMIT_FLOATING_POINT */ - break; -#if !WHPRINTF_OMIT_SIZE -#error "etSIZE (%n) cannot work with the 2021+ semantics." - case etSIZE: - *(va_arg(ap,int*)) = outCount; - length = width = 0; - break; -#endif - case etPERCENT: - buf[0] = '%'; - bufpt = buf; - length = 1; - break; - case etCHARLIT: - case etCHARX: - c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); - if( precision>=0 ){ - for(idx=1; idx=0 && precision0 ){ - WHPRINTF_SPACES(nspace); - } - } - if( length>0 ){ - pfrc = pfAppend( pfAppendArg, bufpt, length); - WHPRINTF_CHECKERR; - } - if( flag_leftjustify ){ - int nspace; - nspace = width-length; - if( nspace>0 ){ - WHPRINTF_SPACES(nspace); - } - } - if( zExtra ){ - free(zExtra); - zExtra = 0; - } - }/* End for loop over the format string */ - WHPRINTF_RETURN; -} /* End of function */ - - -#undef WHPRINTF_SPACES -#undef WHPRINTF_CHECKERR -#undef WHPRINTF_RETURN -#undef WHPRINTF_OMIT_FLOATING_POINT -#undef WHPRINTF_OMIT_SIZE -#undef WHPRINTF_OMIT_SQL -#undef WHPRINTF_BUF_SIZE -#undef WHPRINTF_OMIT_HTML - -int cwal_printf(cwal_printf_appender_f pfAppend, - void * pfAppendArg, - const char *fmt, - ... ){ - va_list vargs; - int ret; - va_start( vargs, fmt ); - ret = cwal_printfv( pfAppend, pfAppendArg, fmt, vargs ); - va_end(vargs); - return ret; -} - - -int cwal_printf_FILE_appender( void * a, char const * s, unsigned int n ){ - FILE * fp = (FILE *)a; - if( ! fp ) return -1; - else{ - const unsigned long ret = (unsigned long)fwrite( s, sizeof(char), n, fp ); - return (ret == n) ? 0 : CWAL_RC_IO; - } -} - -int cwal_printfv_FILE( FILE * fp, char const * fmt, va_list vargs ){ - return cwal_printfv( cwal_printf_FILE_appender, fp, fmt, vargs ); -} - -int cwal_printf_FILE( FILE * fp, char const * fmt, ... ){ - va_list vargs; - int ret; - va_start( vargs, fmt ); - ret = cwal_printfv( cwal_printf_FILE_appender, fp, fmt, vargs ); - va_end(vargs); - return ret; -} -/* end of file cwal_printf.c */ -/* start of file cwal_utf.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -#include -#include /* for a single sprintf() need :/ */ - -#if 1 -#include -#define MARKER(pfexp) \ - do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ - printf pfexp; \ - } while(0) -#else -#define MARKER(exp) if(0) printf -#endif - -/** - Parts of the UTF code was originally taken from sqlite3's - public-domain source code (https://sqlite.org), modified only - slightly for use here. This code generates some "possible data - loss" warnings on MSC, but if this code is good enough for sqlite3 - then it's damned well good enough for me, so we disable that - warning for MSC builds. -*/ -#ifdef _MSC_VER -# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ -# define PUSHED_MSC_WARNING -# pragma warning( push ) -# pragma warning(disable:4244) /* complaining about data loss due - to integer precision in the - sqlite3 utf decoding routines */ -# endif -#endif - - -/* -** This lookup table is used to help decode the first byte of -** a multi-byte UTF8 character. -** -** Taken from sqlite3: -** https://www.sqlite.org/src/artifact?ln=48-61&name=810fbfebe12359f1 -*/ -static const unsigned char cwal_utfTrans1[] = { -0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, -0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, -0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, -0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, -0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 -}; - -unsigned int cwal_utf8_read_char( - const unsigned char *zIn, /* First byte of UTF-8 character */ - const unsigned char *zTerm, /* Pretend this byte is 0x00 */ - const unsigned char **pzNext /* Write first byte past UTF-8 char here */ - ){ - /* - Adapted from sqlite3: - https://www.sqlite.org/src/artifact?ln=155-165&name=810fbfebe12359f1 - */ - unsigned c; - if(zIn>=zTerm){ - *pzNext = zTerm; - c = 0; - }else{ - c = (unsigned int)*(zIn++); - if( c>=0xc0 ){ - c = cwal_utfTrans1[c-0xc0]; - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ) - c = (c<<6) + (0x3f & *(zIn++)); - if( c<0x80 - || (c&0xFFFFF800)==0xD800 - || (c&0xFFFFFFFE)==0xFFFE ) c = 0xFFFD; - } - *pzNext = zIn; - } - return c; -} - -unsigned int cwal_utf8_read_char1(const unsigned char **pz){ - /* taken from sqlite3's utf.c:sqlite3Utf8Read(): - - https://www.sqlite.org/src/artifact?ln=166-185&name=810fbfebe12359f1 - */ - unsigned int c; - c = *((*pz)++); - if( c>=0xc0 ){ - c = cwal_utfTrans1[c-0xc0]; - while( (*(*pz) & 0xc0)==0x80 ){ - c = (c<<6) + (0x3f & *((*pz)++)); - } - if( c<0x80 - || (c&0xFFFFF800)==0xD800 - || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } - } - return c; - -} - -cwal_midsize_t cwal_strlen_utf8( char const * str, cwal_midsize_t len ){ - if( !str || !len ) return 0; - else{ - char unsigned const * x = (char unsigned const *)str; - char unsigned const * end = x + len; - cwal_size_t rc = 0; - /* profiling shows that cwal_utf8_read_char() is, by leaps and - bounds, the most oft-called func in the whole s2 constellation. - We need a faster multi-byte char skipping routine. - */ -#if 1 - /* Derived from: - http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html - */ - for( ; x < end; ++x, ++rc ){ - switch(0xF0 & *x) { - case 0xF0: /* length 4 */ - x += 3; - break; - case 0xE0: /* length 3 */ - x+= 2; - break; - case 0xC0: /* length 2 */ - x += 1; - break; - default: - break; - } - } -#else - for( ; (pos < end) && cwal_utf8_read_char(pos, end, &pos); - ++rc ) - {} -#endif - return rc; - } -} - -#if 0 -/** - Given THE FIRST BYTE of a UTF-8 character, this function returns - that character's length in bytes. It "should" return -1 for invalid - characters, but currently doesn't. Callers should treat it as if it - does/could, though, just in case it is fixed to do so at some point. -*/ -static int cwal_utf8_char_length( unsigned char c ){ - switch(0xF0 & c) { - case 0xF0: return 4; - case 0xE0: return 3; - case 0xC0: return 2; - default: return 1; - /* See also: https://stackoverflow.com/questions/4884656/utf-8-encoding-size */ - } -#endif - -#if 0 - /* fundamentally broken interface */ - int cwal_utf8_char_next( char const * pos, char const * end, - unsigned int * unicode){ - int const len = (pos && posend)){ - /* arguable: if(unicode) *unicode = 0xFFFD; */ - return 0; - } - assert(len>=1 && len<=4); - if(unicode){ - unsigned int u = 0; - unsigned char const * pos_ = (unsigned char const *)pos; - unsigned char const * p = pos_ + len - 1; - unsigned int shift = 0; - for( ; p >= pos_; --p ){ - u += ((0xFF & (int)*p) << shift); - shift += 8; - } - *unicode = u; - } - return len; - } -#endif - - int cwal_utf8_char_to_cstr(unsigned int c, unsigned char *output, cwal_size_t length){ - /* - Stolen from the internet, adapted from several variations which - all _seem_ to have derived from librdf. - */ - cwal_size_t size=0; - - /* check for illegal code positions: - * U+D800 to U+DFFF (UTF-16 surrogates) - * U+FFFE and U+FFFF - */ - if((c > 0xD7FF && c < 0xE000) - || c == 0xFFFE || c == 0xFFFF) return -1; - - /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */ - if(c > 0x10ffff) return -1; - - /*if(!c) size = 0; - else*/ - if (c < 0x00000080) size = 1; - else if (c < 0x00000800) size = 2; - else if (c < 0x00010000) size = 3; - else size = 4; - if(!output) return (int)size; - else if(size > length) return -1; - else switch(size) { - case 0: - assert(!"can't happen anymore"); - output[0] = 0; - return 0; - case 4: - output[3] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0x10000; - CWAL_SWITCH_FALL_THROUGH; - case 3: - output[2] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0x800; - CWAL_SWITCH_FALL_THROUGH; - case 2: - output[1] = 0x80 | (c & 0x3F); - c = c >> 6; - c |= 0xc0; - CWAL_SWITCH_FALL_THROUGH; - case 1: - output[0] = (unsigned char)c; - CWAL_SWITCH_FALL_THROUGH; - default: - return (int)size; - } - } - - int cwal_utf8_char_at( unsigned char const * begin, - unsigned char const * end, - cwal_size_t index, - unsigned int * unicode ){ - unsigned char const * pos = (unsigned char const *)begin; - unsigned char const * next = (unsigned char const *)begin; - cwal_size_t n = 0; - unsigned int codepoint = 0; - if(begin>=end || index>=(cwal_size_t)(end - begin)){ - return CWAL_RC_RANGE; - } - for( ; pos=end){ - return CWAL_RC_RANGE; - }else{ - if(unicode) *unicode = codepoint; - return 0; - } - } - - - int cwal_string_case_fold( cwal_engine * e, - cwal_string const * str, - cwal_value **rv, - bool doUpper ){ - cwal_midsize_t len = 0; - char const * cs = cwal_string_cstr2(str, &len); - if(!e) return CWAL_RC_MISUSE; - else if(!len){ - *rv = cwal_string_value(str); - return 0; - }else{ -#if 0 - /* TODO?... */ - if(cwal_string_is_ascii(str)){ - /* todo?: maybe add slightly faster impl for ASCII - strings... */ - }else{ - return cwal_utf8_case_fold(e, cs, len, rv, doUpper ); - } -#else - return cwal_utf8_case_fold(e, cs, len, rv, doUpper ); -#endif - } - } - -#if 0 - static int cwal_utf8_char_len( int ch ){ - if(ch<=0x7F) return 1; - else if(ch<=0x7FF) return 2; - else if(ch<=0xffff) return 3; - else return 4; - } -#endif - - int cwal_utf8_case_fold_to_buffer( cwal_engine * e, char const * cstr, - cwal_midsize_t len, - cwal_buffer *buf, - bool doUpper ){ - int rc = 0; - unsigned char const * cs = (unsigned char const *)cstr; - unsigned char const * csEnd = cs+len; - int ch; - int clen; - unsigned char cbuf[5] = {0,0,0,0,0}; - if(!e || !cstr || !buf || - (cs>=buf->mem && csmem+buf->capacity - /* source/dest memory may not overlap */) - ) return CWAL_RC_MISUSE; - else if(!len){ - return 0; - } - rc = cwal_buffer_reserve( e, buf, buf->used + len + 1 /*NUL byte*/) - /** - Sidebar: there are actually UTF-8 characters in cwal's - library for which the tolower and toupper values have - different byte lengths. Thus this buffer reservation is a - conservative guess, and not a guaranty that we won't need to - allocate again. - - Just a day or so after writing ^^^^^ that, i randomly came - across a tweet about that very topic: - - https://twitter.com/mikko/status/1059521508765298691 - - See also: - - https://github.com/minimaxir/big-list-of-naughty-strings - */; - if(rc) return rc; - for( ; cs < csEnd; ){ - unsigned char const * pos = cs; - ch = cwal_utf8_read_char( cs, csEnd, &cs ); - if(!(cs-pos)){ - assert(!"can't happen with the current code."); - rc = CWAL_RC_RANGE; - break; - } - ch = doUpper - ? cwal_utf8_char_toupper(ch) - : cwal_utf8_char_tolower(ch); - clen = cwal_utf8_char_to_cstr( (unsigned int)ch, cbuf, 4 ); - if(clen>0){ - rc = cwal_buffer_append( e, buf, cbuf, (cwal_size_t)clen ); - if(rc) break; - }else{ - rc = CWAL_RC_RANGE; - break; - } - } - if(!rc){ - assert(0==buf->mem[buf->used] && "performed by/via cwal_buffer_append()"); - } - return rc; - } - - int cwal_utf8_case_fold( cwal_engine * e, char const * cstr, - cwal_midsize_t len, - cwal_value **rv, - bool doUpper ){ - cwal_buffer buf = cwal_buffer_empty; - int rc = 0; - if(!e || !cstr || !rv) return CWAL_RC_MISUSE; - else if(!len){ - *rv = cwal_new_string_value(e, "", 0) /* does not allocate */; - return 0; - } - rc = cwal_utf8_case_fold_to_buffer(e, cstr, len, &buf, doUpper); - if(!rc){ - assert(buf.mem); - assert(0==buf.mem[buf.used]); - *rv = cwal_string_value(cwal_buffer_to_zstring(e, &buf)); - if(!*rv) rc = CWAL_RC_OOM; - else{ - assert(!buf.mem && "taken over by z-string"); - } - } - cwal_buffer_reserve(e, &buf, 0); - return rc; - } - - cwal_int_t cwal_utf8_indexof( char const * haystack, cwal_size_t hayLen, - cwal_int_t offset, - char const * _needle, cwal_size_t nLen, - char returnAsByteOffset ){ - if(!haystack || !hayLen || !_needle || !nLen) return -1; - else if(nLen > hayLen) return -1; - else if(!offset && nLen == hayLen){ - /* micro-optimization... */ - return memcmp(haystack, _needle, (size_t)nLen) - ? -1 : 0; - } - else{ - unsigned char const * hay = - (unsigned char const *)haystack; - unsigned char const * eof = hay + hayLen; - unsigned char const * origin = hay; - unsigned char const * needle = (unsigned char const *)_needle; - cwal_int_t i = 0; - if(offset<0){ - if(hayLen < ((cwal_size_t)-offset)){ - offset = 0; - }else{ - offset = hayLen + offset; - } - assert(offset >= 0); - } - while(1){ - unsigned char const * start = hay; - if((start==eof) || ((cwal_int_t)nLen > (eof-start))) break; - cwal_utf8_read_char( start, eof, &hay ); - if(i eof) return -2; - else if(0==memcmp(start, needle, nLen)){ - return returnAsByteOffset - ? ((cwal_int_t)(start - origin)) - : i; - } - else ++i; - } - return -1; - } - } - - - int cwal_utf8_char_tolower( int ch ){ - /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt - dated April 1, 2019. */ - switch(ch){ - case 0x0041: return 0x0061; - case 0x0042: return 0x0062; - case 0x0043: return 0x0063; - case 0x0044: return 0x0064; - case 0x0045: return 0x0065; - case 0x0046: return 0x0066; - case 0x0047: return 0x0067; - case 0x0048: return 0x0068; - case 0x0049: return 0x0069; - case 0x004A: return 0x006A; - case 0x004B: return 0x006B; - case 0x004C: return 0x006C; - case 0x004D: return 0x006D; - case 0x004E: return 0x006E; - case 0x004F: return 0x006F; - case 0x0050: return 0x0070; - case 0x0051: return 0x0071; - case 0x0052: return 0x0072; - case 0x0053: return 0x0073; - case 0x0054: return 0x0074; - case 0x0055: return 0x0075; - case 0x0056: return 0x0076; - case 0x0057: return 0x0077; - case 0x0058: return 0x0078; - case 0x0059: return 0x0079; - case 0x005A: return 0x007A; - case 0x00C0: return 0x00E0; - case 0x00C1: return 0x00E1; - case 0x00C2: return 0x00E2; - case 0x00C3: return 0x00E3; - case 0x00C4: return 0x00E4; - case 0x00C5: return 0x00E5; - case 0x00C6: return 0x00E6; - case 0x00C7: return 0x00E7; - case 0x00C8: return 0x00E8; - case 0x00C9: return 0x00E9; - case 0x00CA: return 0x00EA; - case 0x00CB: return 0x00EB; - case 0x00CC: return 0x00EC; - case 0x00CD: return 0x00ED; - case 0x00CE: return 0x00EE; - case 0x00CF: return 0x00EF; - case 0x00D0: return 0x00F0; - case 0x00D1: return 0x00F1; - case 0x00D2: return 0x00F2; - case 0x00D3: return 0x00F3; - case 0x00D4: return 0x00F4; - case 0x00D5: return 0x00F5; - case 0x00D6: return 0x00F6; - case 0x00D8: return 0x00F8; - case 0x00D9: return 0x00F9; - case 0x00DA: return 0x00FA; - case 0x00DB: return 0x00FB; - case 0x00DC: return 0x00FC; - case 0x00DD: return 0x00FD; - case 0x00DE: return 0x00FE; - case 0x0100: return 0x0101; - case 0x0102: return 0x0103; - case 0x0104: return 0x0105; - case 0x0106: return 0x0107; - case 0x0108: return 0x0109; - case 0x010A: return 0x010B; - case 0x010C: return 0x010D; - case 0x010E: return 0x010F; - case 0x0110: return 0x0111; - case 0x0112: return 0x0113; - case 0x0114: return 0x0115; - case 0x0116: return 0x0117; - case 0x0118: return 0x0119; - case 0x011A: return 0x011B; - case 0x011C: return 0x011D; - case 0x011E: return 0x011F; - case 0x0120: return 0x0121; - case 0x0122: return 0x0123; - case 0x0124: return 0x0125; - case 0x0126: return 0x0127; - case 0x0128: return 0x0129; - case 0x012A: return 0x012B; - case 0x012C: return 0x012D; - case 0x012E: return 0x012F; - case 0x0130: return 0x0069; - case 0x0132: return 0x0133; - case 0x0134: return 0x0135; - case 0x0136: return 0x0137; - case 0x0139: return 0x013A; - case 0x013B: return 0x013C; - case 0x013D: return 0x013E; - case 0x013F: return 0x0140; - case 0x0141: return 0x0142; - case 0x0143: return 0x0144; - case 0x0145: return 0x0146; - case 0x0147: return 0x0148; - case 0x014A: return 0x014B; - case 0x014C: return 0x014D; - case 0x014E: return 0x014F; - case 0x0150: return 0x0151; - case 0x0152: return 0x0153; - case 0x0154: return 0x0155; - case 0x0156: return 0x0157; - case 0x0158: return 0x0159; - case 0x015A: return 0x015B; - case 0x015C: return 0x015D; - case 0x015E: return 0x015F; - case 0x0160: return 0x0161; - case 0x0162: return 0x0163; - case 0x0164: return 0x0165; - case 0x0166: return 0x0167; - case 0x0168: return 0x0169; - case 0x016A: return 0x016B; - case 0x016C: return 0x016D; - case 0x016E: return 0x016F; - case 0x0170: return 0x0171; - case 0x0172: return 0x0173; - case 0x0174: return 0x0175; - case 0x0176: return 0x0177; - case 0x0178: return 0x00FF; - case 0x0179: return 0x017A; - case 0x017B: return 0x017C; - case 0x017D: return 0x017E; - case 0x0181: return 0x0253; - case 0x0182: return 0x0183; - case 0x0184: return 0x0185; - case 0x0186: return 0x0254; - case 0x0187: return 0x0188; - case 0x0189: return 0x0256; - case 0x018A: return 0x0257; - case 0x018B: return 0x018C; - case 0x018E: return 0x01DD; - case 0x018F: return 0x0259; - case 0x0190: return 0x025B; - case 0x0191: return 0x0192; - case 0x0193: return 0x0260; - case 0x0194: return 0x0263; - case 0x0196: return 0x0269; - case 0x0197: return 0x0268; - case 0x0198: return 0x0199; - case 0x019C: return 0x026F; - case 0x019D: return 0x0272; - case 0x019F: return 0x0275; - case 0x01A0: return 0x01A1; - case 0x01A2: return 0x01A3; - case 0x01A4: return 0x01A5; - case 0x01A6: return 0x0280; - case 0x01A7: return 0x01A8; - case 0x01A9: return 0x0283; - case 0x01AC: return 0x01AD; - case 0x01AE: return 0x0288; - case 0x01AF: return 0x01B0; - case 0x01B1: return 0x028A; - case 0x01B2: return 0x028B; - case 0x01B3: return 0x01B4; - case 0x01B5: return 0x01B6; - case 0x01B7: return 0x0292; - case 0x01B8: return 0x01B9; - case 0x01BC: return 0x01BD; - case 0x01C4: return 0x01C6; - case 0x01C5: return 0x01C6; - case 0x01C7: return 0x01C9; - case 0x01C8: return 0x01C9; - case 0x01CA: return 0x01CC; - case 0x01CB: return 0x01CC; - case 0x01CD: return 0x01CE; - case 0x01CF: return 0x01D0; - case 0x01D1: return 0x01D2; - case 0x01D3: return 0x01D4; - case 0x01D5: return 0x01D6; - case 0x01D7: return 0x01D8; - case 0x01D9: return 0x01DA; - case 0x01DB: return 0x01DC; - case 0x01DE: return 0x01DF; - case 0x01E0: return 0x01E1; - case 0x01E2: return 0x01E3; - case 0x01E4: return 0x01E5; - case 0x01E6: return 0x01E7; - case 0x01E8: return 0x01E9; - case 0x01EA: return 0x01EB; - case 0x01EC: return 0x01ED; - case 0x01EE: return 0x01EF; - case 0x01F1: return 0x01F3; - case 0x01F2: return 0x01F3; - case 0x01F4: return 0x01F5; - case 0x01F6: return 0x0195; - case 0x01F7: return 0x01BF; - case 0x01F8: return 0x01F9; - case 0x01FA: return 0x01FB; - case 0x01FC: return 0x01FD; - case 0x01FE: return 0x01FF; - case 0x0200: return 0x0201; - case 0x0202: return 0x0203; - case 0x0204: return 0x0205; - case 0x0206: return 0x0207; - case 0x0208: return 0x0209; - case 0x020A: return 0x020B; - case 0x020C: return 0x020D; - case 0x020E: return 0x020F; - case 0x0210: return 0x0211; - case 0x0212: return 0x0213; - case 0x0214: return 0x0215; - case 0x0216: return 0x0217; - case 0x0218: return 0x0219; - case 0x021A: return 0x021B; - case 0x021C: return 0x021D; - case 0x021E: return 0x021F; - case 0x0220: return 0x019E; - case 0x0222: return 0x0223; - case 0x0224: return 0x0225; - case 0x0226: return 0x0227; - case 0x0228: return 0x0229; - case 0x022A: return 0x022B; - case 0x022C: return 0x022D; - case 0x022E: return 0x022F; - case 0x0230: return 0x0231; - case 0x0232: return 0x0233; - case 0x023A: return 0x2C65; - case 0x023B: return 0x023C; - case 0x023D: return 0x019A; - case 0x023E: return 0x2C66; - case 0x0241: return 0x0242; - case 0x0243: return 0x0180; - case 0x0244: return 0x0289; - case 0x0245: return 0x028C; - case 0x0246: return 0x0247; - case 0x0248: return 0x0249; - case 0x024A: return 0x024B; - case 0x024C: return 0x024D; - case 0x024E: return 0x024F; - case 0x0370: return 0x0371; - case 0x0372: return 0x0373; - case 0x0376: return 0x0377; - case 0x037F: return 0x03F3; - case 0x0386: return 0x03AC; - case 0x0388: return 0x03AD; - case 0x0389: return 0x03AE; - case 0x038A: return 0x03AF; - case 0x038C: return 0x03CC; - case 0x038E: return 0x03CD; - case 0x038F: return 0x03CE; - case 0x0391: return 0x03B1; - case 0x0392: return 0x03B2; - case 0x0393: return 0x03B3; - case 0x0394: return 0x03B4; - case 0x0395: return 0x03B5; - case 0x0396: return 0x03B6; - case 0x0397: return 0x03B7; - case 0x0398: return 0x03B8; - case 0x0399: return 0x03B9; - case 0x039A: return 0x03BA; - case 0x039B: return 0x03BB; - case 0x039C: return 0x03BC; - case 0x039D: return 0x03BD; - case 0x039E: return 0x03BE; - case 0x039F: return 0x03BF; - case 0x03A0: return 0x03C0; - case 0x03A1: return 0x03C1; - case 0x03A3: return 0x03C3; - case 0x03A4: return 0x03C4; - case 0x03A5: return 0x03C5; - case 0x03A6: return 0x03C6; - case 0x03A7: return 0x03C7; - case 0x03A8: return 0x03C8; - case 0x03A9: return 0x03C9; - case 0x03AA: return 0x03CA; - case 0x03AB: return 0x03CB; - case 0x03CF: return 0x03D7; - case 0x03D8: return 0x03D9; - case 0x03DA: return 0x03DB; - case 0x03DC: return 0x03DD; - case 0x03DE: return 0x03DF; - case 0x03E0: return 0x03E1; - case 0x03E2: return 0x03E3; - case 0x03E4: return 0x03E5; - case 0x03E6: return 0x03E7; - case 0x03E8: return 0x03E9; - case 0x03EA: return 0x03EB; - case 0x03EC: return 0x03ED; - case 0x03EE: return 0x03EF; - case 0x03F4: return 0x03B8; - case 0x03F7: return 0x03F8; - case 0x03F9: return 0x03F2; - case 0x03FA: return 0x03FB; - case 0x03FD: return 0x037B; - case 0x03FE: return 0x037C; - case 0x03FF: return 0x037D; - case 0x0400: return 0x0450; - case 0x0401: return 0x0451; - case 0x0402: return 0x0452; - case 0x0403: return 0x0453; - case 0x0404: return 0x0454; - case 0x0405: return 0x0455; - case 0x0406: return 0x0456; - case 0x0407: return 0x0457; - case 0x0408: return 0x0458; - case 0x0409: return 0x0459; - case 0x040A: return 0x045A; - case 0x040B: return 0x045B; - case 0x040C: return 0x045C; - case 0x040D: return 0x045D; - case 0x040E: return 0x045E; - case 0x040F: return 0x045F; - case 0x0410: return 0x0430; - case 0x0411: return 0x0431; - case 0x0412: return 0x0432; - case 0x0413: return 0x0433; - case 0x0414: return 0x0434; - case 0x0415: return 0x0435; - case 0x0416: return 0x0436; - case 0x0417: return 0x0437; - case 0x0418: return 0x0438; - case 0x0419: return 0x0439; - case 0x041A: return 0x043A; - case 0x041B: return 0x043B; - case 0x041C: return 0x043C; - case 0x041D: return 0x043D; - case 0x041E: return 0x043E; - case 0x041F: return 0x043F; - case 0x0420: return 0x0440; - case 0x0421: return 0x0441; - case 0x0422: return 0x0442; - case 0x0423: return 0x0443; - case 0x0424: return 0x0444; - case 0x0425: return 0x0445; - case 0x0426: return 0x0446; - case 0x0427: return 0x0447; - case 0x0428: return 0x0448; - case 0x0429: return 0x0449; - case 0x042A: return 0x044A; - case 0x042B: return 0x044B; - case 0x042C: return 0x044C; - case 0x042D: return 0x044D; - case 0x042E: return 0x044E; - case 0x042F: return 0x044F; - case 0x0460: return 0x0461; - case 0x0462: return 0x0463; - case 0x0464: return 0x0465; - case 0x0466: return 0x0467; - case 0x0468: return 0x0469; - case 0x046A: return 0x046B; - case 0x046C: return 0x046D; - case 0x046E: return 0x046F; - case 0x0470: return 0x0471; - case 0x0472: return 0x0473; - case 0x0474: return 0x0475; - case 0x0476: return 0x0477; - case 0x0478: return 0x0479; - case 0x047A: return 0x047B; - case 0x047C: return 0x047D; - case 0x047E: return 0x047F; - case 0x0480: return 0x0481; - case 0x048A: return 0x048B; - case 0x048C: return 0x048D; - case 0x048E: return 0x048F; - case 0x0490: return 0x0491; - case 0x0492: return 0x0493; - case 0x0494: return 0x0495; - case 0x0496: return 0x0497; - case 0x0498: return 0x0499; - case 0x049A: return 0x049B; - case 0x049C: return 0x049D; - case 0x049E: return 0x049F; - case 0x04A0: return 0x04A1; - case 0x04A2: return 0x04A3; - case 0x04A4: return 0x04A5; - case 0x04A6: return 0x04A7; - case 0x04A8: return 0x04A9; - case 0x04AA: return 0x04AB; - case 0x04AC: return 0x04AD; - case 0x04AE: return 0x04AF; - case 0x04B0: return 0x04B1; - case 0x04B2: return 0x04B3; - case 0x04B4: return 0x04B5; - case 0x04B6: return 0x04B7; - case 0x04B8: return 0x04B9; - case 0x04BA: return 0x04BB; - case 0x04BC: return 0x04BD; - case 0x04BE: return 0x04BF; - case 0x04C0: return 0x04CF; - case 0x04C1: return 0x04C2; - case 0x04C3: return 0x04C4; - case 0x04C5: return 0x04C6; - case 0x04C7: return 0x04C8; - case 0x04C9: return 0x04CA; - case 0x04CB: return 0x04CC; - case 0x04CD: return 0x04CE; - case 0x04D0: return 0x04D1; - case 0x04D2: return 0x04D3; - case 0x04D4: return 0x04D5; - case 0x04D6: return 0x04D7; - case 0x04D8: return 0x04D9; - case 0x04DA: return 0x04DB; - case 0x04DC: return 0x04DD; - case 0x04DE: return 0x04DF; - case 0x04E0: return 0x04E1; - case 0x04E2: return 0x04E3; - case 0x04E4: return 0x04E5; - case 0x04E6: return 0x04E7; - case 0x04E8: return 0x04E9; - case 0x04EA: return 0x04EB; - case 0x04EC: return 0x04ED; - case 0x04EE: return 0x04EF; - case 0x04F0: return 0x04F1; - case 0x04F2: return 0x04F3; - case 0x04F4: return 0x04F5; - case 0x04F6: return 0x04F7; - case 0x04F8: return 0x04F9; - case 0x04FA: return 0x04FB; - case 0x04FC: return 0x04FD; - case 0x04FE: return 0x04FF; - case 0x0500: return 0x0501; - case 0x0502: return 0x0503; - case 0x0504: return 0x0505; - case 0x0506: return 0x0507; - case 0x0508: return 0x0509; - case 0x050A: return 0x050B; - case 0x050C: return 0x050D; - case 0x050E: return 0x050F; - case 0x0510: return 0x0511; - case 0x0512: return 0x0513; - case 0x0514: return 0x0515; - case 0x0516: return 0x0517; - case 0x0518: return 0x0519; - case 0x051A: return 0x051B; - case 0x051C: return 0x051D; - case 0x051E: return 0x051F; - case 0x0520: return 0x0521; - case 0x0522: return 0x0523; - case 0x0524: return 0x0525; - case 0x0526: return 0x0527; - case 0x0528: return 0x0529; - case 0x052A: return 0x052B; - case 0x052C: return 0x052D; - case 0x052E: return 0x052F; - case 0x0531: return 0x0561; - case 0x0532: return 0x0562; - case 0x0533: return 0x0563; - case 0x0534: return 0x0564; - case 0x0535: return 0x0565; - case 0x0536: return 0x0566; - case 0x0537: return 0x0567; - case 0x0538: return 0x0568; - case 0x0539: return 0x0569; - case 0x053A: return 0x056A; - case 0x053B: return 0x056B; - case 0x053C: return 0x056C; - case 0x053D: return 0x056D; - case 0x053E: return 0x056E; - case 0x053F: return 0x056F; - case 0x0540: return 0x0570; - case 0x0541: return 0x0571; - case 0x0542: return 0x0572; - case 0x0543: return 0x0573; - case 0x0544: return 0x0574; - case 0x0545: return 0x0575; - case 0x0546: return 0x0576; - case 0x0547: return 0x0577; - case 0x0548: return 0x0578; - case 0x0549: return 0x0579; - case 0x054A: return 0x057A; - case 0x054B: return 0x057B; - case 0x054C: return 0x057C; - case 0x054D: return 0x057D; - case 0x054E: return 0x057E; - case 0x054F: return 0x057F; - case 0x0550: return 0x0580; - case 0x0551: return 0x0581; - case 0x0552: return 0x0582; - case 0x0553: return 0x0583; - case 0x0554: return 0x0584; - case 0x0555: return 0x0585; - case 0x0556: return 0x0586; - case 0x10A0: return 0x2D00; - case 0x10A1: return 0x2D01; - case 0x10A2: return 0x2D02; - case 0x10A3: return 0x2D03; - case 0x10A4: return 0x2D04; - case 0x10A5: return 0x2D05; - case 0x10A6: return 0x2D06; - case 0x10A7: return 0x2D07; - case 0x10A8: return 0x2D08; - case 0x10A9: return 0x2D09; - case 0x10AA: return 0x2D0A; - case 0x10AB: return 0x2D0B; - case 0x10AC: return 0x2D0C; - case 0x10AD: return 0x2D0D; - case 0x10AE: return 0x2D0E; - case 0x10AF: return 0x2D0F; - case 0x10B0: return 0x2D10; - case 0x10B1: return 0x2D11; - case 0x10B2: return 0x2D12; - case 0x10B3: return 0x2D13; - case 0x10B4: return 0x2D14; - case 0x10B5: return 0x2D15; - case 0x10B6: return 0x2D16; - case 0x10B7: return 0x2D17; - case 0x10B8: return 0x2D18; - case 0x10B9: return 0x2D19; - case 0x10BA: return 0x2D1A; - case 0x10BB: return 0x2D1B; - case 0x10BC: return 0x2D1C; - case 0x10BD: return 0x2D1D; - case 0x10BE: return 0x2D1E; - case 0x10BF: return 0x2D1F; - case 0x10C0: return 0x2D20; - case 0x10C1: return 0x2D21; - case 0x10C2: return 0x2D22; - case 0x10C3: return 0x2D23; - case 0x10C4: return 0x2D24; - case 0x10C5: return 0x2D25; - case 0x10C7: return 0x2D27; - case 0x10CD: return 0x2D2D; - case 0x13A0: return 0xAB70; - case 0x13A1: return 0xAB71; - case 0x13A2: return 0xAB72; - case 0x13A3: return 0xAB73; - case 0x13A4: return 0xAB74; - case 0x13A5: return 0xAB75; - case 0x13A6: return 0xAB76; - case 0x13A7: return 0xAB77; - case 0x13A8: return 0xAB78; - case 0x13A9: return 0xAB79; - case 0x13AA: return 0xAB7A; - case 0x13AB: return 0xAB7B; - case 0x13AC: return 0xAB7C; - case 0x13AD: return 0xAB7D; - case 0x13AE: return 0xAB7E; - case 0x13AF: return 0xAB7F; - case 0x13B0: return 0xAB80; - case 0x13B1: return 0xAB81; - case 0x13B2: return 0xAB82; - case 0x13B3: return 0xAB83; - case 0x13B4: return 0xAB84; - case 0x13B5: return 0xAB85; - case 0x13B6: return 0xAB86; - case 0x13B7: return 0xAB87; - case 0x13B8: return 0xAB88; - case 0x13B9: return 0xAB89; - case 0x13BA: return 0xAB8A; - case 0x13BB: return 0xAB8B; - case 0x13BC: return 0xAB8C; - case 0x13BD: return 0xAB8D; - case 0x13BE: return 0xAB8E; - case 0x13BF: return 0xAB8F; - case 0x13C0: return 0xAB90; - case 0x13C1: return 0xAB91; - case 0x13C2: return 0xAB92; - case 0x13C3: return 0xAB93; - case 0x13C4: return 0xAB94; - case 0x13C5: return 0xAB95; - case 0x13C6: return 0xAB96; - case 0x13C7: return 0xAB97; - case 0x13C8: return 0xAB98; - case 0x13C9: return 0xAB99; - case 0x13CA: return 0xAB9A; - case 0x13CB: return 0xAB9B; - case 0x13CC: return 0xAB9C; - case 0x13CD: return 0xAB9D; - case 0x13CE: return 0xAB9E; - case 0x13CF: return 0xAB9F; - case 0x13D0: return 0xABA0; - case 0x13D1: return 0xABA1; - case 0x13D2: return 0xABA2; - case 0x13D3: return 0xABA3; - case 0x13D4: return 0xABA4; - case 0x13D5: return 0xABA5; - case 0x13D6: return 0xABA6; - case 0x13D7: return 0xABA7; - case 0x13D8: return 0xABA8; - case 0x13D9: return 0xABA9; - case 0x13DA: return 0xABAA; - case 0x13DB: return 0xABAB; - case 0x13DC: return 0xABAC; - case 0x13DD: return 0xABAD; - case 0x13DE: return 0xABAE; - case 0x13DF: return 0xABAF; - case 0x13E0: return 0xABB0; - case 0x13E1: return 0xABB1; - case 0x13E2: return 0xABB2; - case 0x13E3: return 0xABB3; - case 0x13E4: return 0xABB4; - case 0x13E5: return 0xABB5; - case 0x13E6: return 0xABB6; - case 0x13E7: return 0xABB7; - case 0x13E8: return 0xABB8; - case 0x13E9: return 0xABB9; - case 0x13EA: return 0xABBA; - case 0x13EB: return 0xABBB; - case 0x13EC: return 0xABBC; - case 0x13ED: return 0xABBD; - case 0x13EE: return 0xABBE; - case 0x13EF: return 0xABBF; - case 0x13F0: return 0x13F8; - case 0x13F1: return 0x13F9; - case 0x13F2: return 0x13FA; - case 0x13F3: return 0x13FB; - case 0x13F4: return 0x13FC; - case 0x13F5: return 0x13FD; - case 0x1C90: return 0x10D0; - case 0x1C91: return 0x10D1; - case 0x1C92: return 0x10D2; - case 0x1C93: return 0x10D3; - case 0x1C94: return 0x10D4; - case 0x1C95: return 0x10D5; - case 0x1C96: return 0x10D6; - case 0x1C97: return 0x10D7; - case 0x1C98: return 0x10D8; - case 0x1C99: return 0x10D9; - case 0x1C9A: return 0x10DA; - case 0x1C9B: return 0x10DB; - case 0x1C9C: return 0x10DC; - case 0x1C9D: return 0x10DD; - case 0x1C9E: return 0x10DE; - case 0x1C9F: return 0x10DF; - case 0x1CA0: return 0x10E0; - case 0x1CA1: return 0x10E1; - case 0x1CA2: return 0x10E2; - case 0x1CA3: return 0x10E3; - case 0x1CA4: return 0x10E4; - case 0x1CA5: return 0x10E5; - case 0x1CA6: return 0x10E6; - case 0x1CA7: return 0x10E7; - case 0x1CA8: return 0x10E8; - case 0x1CA9: return 0x10E9; - case 0x1CAA: return 0x10EA; - case 0x1CAB: return 0x10EB; - case 0x1CAC: return 0x10EC; - case 0x1CAD: return 0x10ED; - case 0x1CAE: return 0x10EE; - case 0x1CAF: return 0x10EF; - case 0x1CB0: return 0x10F0; - case 0x1CB1: return 0x10F1; - case 0x1CB2: return 0x10F2; - case 0x1CB3: return 0x10F3; - case 0x1CB4: return 0x10F4; - case 0x1CB5: return 0x10F5; - case 0x1CB6: return 0x10F6; - case 0x1CB7: return 0x10F7; - case 0x1CB8: return 0x10F8; - case 0x1CB9: return 0x10F9; - case 0x1CBA: return 0x10FA; - case 0x1CBD: return 0x10FD; - case 0x1CBE: return 0x10FE; - case 0x1CBF: return 0x10FF; - case 0x1E00: return 0x1E01; - case 0x1E02: return 0x1E03; - case 0x1E04: return 0x1E05; - case 0x1E06: return 0x1E07; - case 0x1E08: return 0x1E09; - case 0x1E0A: return 0x1E0B; - case 0x1E0C: return 0x1E0D; - case 0x1E0E: return 0x1E0F; - case 0x1E10: return 0x1E11; - case 0x1E12: return 0x1E13; - case 0x1E14: return 0x1E15; - case 0x1E16: return 0x1E17; - case 0x1E18: return 0x1E19; - case 0x1E1A: return 0x1E1B; - case 0x1E1C: return 0x1E1D; - case 0x1E1E: return 0x1E1F; - case 0x1E20: return 0x1E21; - case 0x1E22: return 0x1E23; - case 0x1E24: return 0x1E25; - case 0x1E26: return 0x1E27; - case 0x1E28: return 0x1E29; - case 0x1E2A: return 0x1E2B; - case 0x1E2C: return 0x1E2D; - case 0x1E2E: return 0x1E2F; - case 0x1E30: return 0x1E31; - case 0x1E32: return 0x1E33; - case 0x1E34: return 0x1E35; - case 0x1E36: return 0x1E37; - case 0x1E38: return 0x1E39; - case 0x1E3A: return 0x1E3B; - case 0x1E3C: return 0x1E3D; - case 0x1E3E: return 0x1E3F; - case 0x1E40: return 0x1E41; - case 0x1E42: return 0x1E43; - case 0x1E44: return 0x1E45; - case 0x1E46: return 0x1E47; - case 0x1E48: return 0x1E49; - case 0x1E4A: return 0x1E4B; - case 0x1E4C: return 0x1E4D; - case 0x1E4E: return 0x1E4F; - case 0x1E50: return 0x1E51; - case 0x1E52: return 0x1E53; - case 0x1E54: return 0x1E55; - case 0x1E56: return 0x1E57; - case 0x1E58: return 0x1E59; - case 0x1E5A: return 0x1E5B; - case 0x1E5C: return 0x1E5D; - case 0x1E5E: return 0x1E5F; - case 0x1E60: return 0x1E61; - case 0x1E62: return 0x1E63; - case 0x1E64: return 0x1E65; - case 0x1E66: return 0x1E67; - case 0x1E68: return 0x1E69; - case 0x1E6A: return 0x1E6B; - case 0x1E6C: return 0x1E6D; - case 0x1E6E: return 0x1E6F; - case 0x1E70: return 0x1E71; - case 0x1E72: return 0x1E73; - case 0x1E74: return 0x1E75; - case 0x1E76: return 0x1E77; - case 0x1E78: return 0x1E79; - case 0x1E7A: return 0x1E7B; - case 0x1E7C: return 0x1E7D; - case 0x1E7E: return 0x1E7F; - case 0x1E80: return 0x1E81; - case 0x1E82: return 0x1E83; - case 0x1E84: return 0x1E85; - case 0x1E86: return 0x1E87; - case 0x1E88: return 0x1E89; - case 0x1E8A: return 0x1E8B; - case 0x1E8C: return 0x1E8D; - case 0x1E8E: return 0x1E8F; - case 0x1E90: return 0x1E91; - case 0x1E92: return 0x1E93; - case 0x1E94: return 0x1E95; - case 0x1E9E: return 0x00DF; - case 0x1EA0: return 0x1EA1; - case 0x1EA2: return 0x1EA3; - case 0x1EA4: return 0x1EA5; - case 0x1EA6: return 0x1EA7; - case 0x1EA8: return 0x1EA9; - case 0x1EAA: return 0x1EAB; - case 0x1EAC: return 0x1EAD; - case 0x1EAE: return 0x1EAF; - case 0x1EB0: return 0x1EB1; - case 0x1EB2: return 0x1EB3; - case 0x1EB4: return 0x1EB5; - case 0x1EB6: return 0x1EB7; - case 0x1EB8: return 0x1EB9; - case 0x1EBA: return 0x1EBB; - case 0x1EBC: return 0x1EBD; - case 0x1EBE: return 0x1EBF; - case 0x1EC0: return 0x1EC1; - case 0x1EC2: return 0x1EC3; - case 0x1EC4: return 0x1EC5; - case 0x1EC6: return 0x1EC7; - case 0x1EC8: return 0x1EC9; - case 0x1ECA: return 0x1ECB; - case 0x1ECC: return 0x1ECD; - case 0x1ECE: return 0x1ECF; - case 0x1ED0: return 0x1ED1; - case 0x1ED2: return 0x1ED3; - case 0x1ED4: return 0x1ED5; - case 0x1ED6: return 0x1ED7; - case 0x1ED8: return 0x1ED9; - case 0x1EDA: return 0x1EDB; - case 0x1EDC: return 0x1EDD; - case 0x1EDE: return 0x1EDF; - case 0x1EE0: return 0x1EE1; - case 0x1EE2: return 0x1EE3; - case 0x1EE4: return 0x1EE5; - case 0x1EE6: return 0x1EE7; - case 0x1EE8: return 0x1EE9; - case 0x1EEA: return 0x1EEB; - case 0x1EEC: return 0x1EED; - case 0x1EEE: return 0x1EEF; - case 0x1EF0: return 0x1EF1; - case 0x1EF2: return 0x1EF3; - case 0x1EF4: return 0x1EF5; - case 0x1EF6: return 0x1EF7; - case 0x1EF8: return 0x1EF9; - case 0x1EFA: return 0x1EFB; - case 0x1EFC: return 0x1EFD; - case 0x1EFE: return 0x1EFF; - case 0x1F08: return 0x1F00; - case 0x1F09: return 0x1F01; - case 0x1F0A: return 0x1F02; - case 0x1F0B: return 0x1F03; - case 0x1F0C: return 0x1F04; - case 0x1F0D: return 0x1F05; - case 0x1F0E: return 0x1F06; - case 0x1F0F: return 0x1F07; - case 0x1F18: return 0x1F10; - case 0x1F19: return 0x1F11; - case 0x1F1A: return 0x1F12; - case 0x1F1B: return 0x1F13; - case 0x1F1C: return 0x1F14; - case 0x1F1D: return 0x1F15; - case 0x1F28: return 0x1F20; - case 0x1F29: return 0x1F21; - case 0x1F2A: return 0x1F22; - case 0x1F2B: return 0x1F23; - case 0x1F2C: return 0x1F24; - case 0x1F2D: return 0x1F25; - case 0x1F2E: return 0x1F26; - case 0x1F2F: return 0x1F27; - case 0x1F38: return 0x1F30; - case 0x1F39: return 0x1F31; - case 0x1F3A: return 0x1F32; - case 0x1F3B: return 0x1F33; - case 0x1F3C: return 0x1F34; - case 0x1F3D: return 0x1F35; - case 0x1F3E: return 0x1F36; - case 0x1F3F: return 0x1F37; - case 0x1F48: return 0x1F40; - case 0x1F49: return 0x1F41; - case 0x1F4A: return 0x1F42; - case 0x1F4B: return 0x1F43; - case 0x1F4C: return 0x1F44; - case 0x1F4D: return 0x1F45; - case 0x1F59: return 0x1F51; - case 0x1F5B: return 0x1F53; - case 0x1F5D: return 0x1F55; - case 0x1F5F: return 0x1F57; - case 0x1F68: return 0x1F60; - case 0x1F69: return 0x1F61; - case 0x1F6A: return 0x1F62; - case 0x1F6B: return 0x1F63; - case 0x1F6C: return 0x1F64; - case 0x1F6D: return 0x1F65; - case 0x1F6E: return 0x1F66; - case 0x1F6F: return 0x1F67; - case 0x1F88: return 0x1F80; - case 0x1F89: return 0x1F81; - case 0x1F8A: return 0x1F82; - case 0x1F8B: return 0x1F83; - case 0x1F8C: return 0x1F84; - case 0x1F8D: return 0x1F85; - case 0x1F8E: return 0x1F86; - case 0x1F8F: return 0x1F87; - case 0x1F98: return 0x1F90; - case 0x1F99: return 0x1F91; - case 0x1F9A: return 0x1F92; - case 0x1F9B: return 0x1F93; - case 0x1F9C: return 0x1F94; - case 0x1F9D: return 0x1F95; - case 0x1F9E: return 0x1F96; - case 0x1F9F: return 0x1F97; - case 0x1FA8: return 0x1FA0; - case 0x1FA9: return 0x1FA1; - case 0x1FAA: return 0x1FA2; - case 0x1FAB: return 0x1FA3; - case 0x1FAC: return 0x1FA4; - case 0x1FAD: return 0x1FA5; - case 0x1FAE: return 0x1FA6; - case 0x1FAF: return 0x1FA7; - case 0x1FB8: return 0x1FB0; - case 0x1FB9: return 0x1FB1; - case 0x1FBA: return 0x1F70; - case 0x1FBB: return 0x1F71; - case 0x1FBC: return 0x1FB3; - case 0x1FC8: return 0x1F72; - case 0x1FC9: return 0x1F73; - case 0x1FCA: return 0x1F74; - case 0x1FCB: return 0x1F75; - case 0x1FCC: return 0x1FC3; - case 0x1FD8: return 0x1FD0; - case 0x1FD9: return 0x1FD1; - case 0x1FDA: return 0x1F76; - case 0x1FDB: return 0x1F77; - case 0x1FE8: return 0x1FE0; - case 0x1FE9: return 0x1FE1; - case 0x1FEA: return 0x1F7A; - case 0x1FEB: return 0x1F7B; - case 0x1FEC: return 0x1FE5; - case 0x1FF8: return 0x1F78; - case 0x1FF9: return 0x1F79; - case 0x1FFA: return 0x1F7C; - case 0x1FFB: return 0x1F7D; - case 0x1FFC: return 0x1FF3; - case 0x2126: return 0x03C9; - case 0x212A: return 0x006B; - case 0x212B: return 0x00E5; - case 0x2132: return 0x214E; - case 0x2160: return 0x2170; - case 0x2161: return 0x2171; - case 0x2162: return 0x2172; - case 0x2163: return 0x2173; - case 0x2164: return 0x2174; - case 0x2165: return 0x2175; - case 0x2166: return 0x2176; - case 0x2167: return 0x2177; - case 0x2168: return 0x2178; - case 0x2169: return 0x2179; - case 0x216A: return 0x217A; - case 0x216B: return 0x217B; - case 0x216C: return 0x217C; - case 0x216D: return 0x217D; - case 0x216E: return 0x217E; - case 0x216F: return 0x217F; - case 0x2183: return 0x2184; - case 0x24B6: return 0x24D0; - case 0x24B7: return 0x24D1; - case 0x24B8: return 0x24D2; - case 0x24B9: return 0x24D3; - case 0x24BA: return 0x24D4; - case 0x24BB: return 0x24D5; - case 0x24BC: return 0x24D6; - case 0x24BD: return 0x24D7; - case 0x24BE: return 0x24D8; - case 0x24BF: return 0x24D9; - case 0x24C0: return 0x24DA; - case 0x24C1: return 0x24DB; - case 0x24C2: return 0x24DC; - case 0x24C3: return 0x24DD; - case 0x24C4: return 0x24DE; - case 0x24C5: return 0x24DF; - case 0x24C6: return 0x24E0; - case 0x24C7: return 0x24E1; - case 0x24C8: return 0x24E2; - case 0x24C9: return 0x24E3; - case 0x24CA: return 0x24E4; - case 0x24CB: return 0x24E5; - case 0x24CC: return 0x24E6; - case 0x24CD: return 0x24E7; - case 0x24CE: return 0x24E8; - case 0x24CF: return 0x24E9; - case 0x2C00: return 0x2C30; - case 0x2C01: return 0x2C31; - case 0x2C02: return 0x2C32; - case 0x2C03: return 0x2C33; - case 0x2C04: return 0x2C34; - case 0x2C05: return 0x2C35; - case 0x2C06: return 0x2C36; - case 0x2C07: return 0x2C37; - case 0x2C08: return 0x2C38; - case 0x2C09: return 0x2C39; - case 0x2C0A: return 0x2C3A; - case 0x2C0B: return 0x2C3B; - case 0x2C0C: return 0x2C3C; - case 0x2C0D: return 0x2C3D; - case 0x2C0E: return 0x2C3E; - case 0x2C0F: return 0x2C3F; - case 0x2C10: return 0x2C40; - case 0x2C11: return 0x2C41; - case 0x2C12: return 0x2C42; - case 0x2C13: return 0x2C43; - case 0x2C14: return 0x2C44; - case 0x2C15: return 0x2C45; - case 0x2C16: return 0x2C46; - case 0x2C17: return 0x2C47; - case 0x2C18: return 0x2C48; - case 0x2C19: return 0x2C49; - case 0x2C1A: return 0x2C4A; - case 0x2C1B: return 0x2C4B; - case 0x2C1C: return 0x2C4C; - case 0x2C1D: return 0x2C4D; - case 0x2C1E: return 0x2C4E; - case 0x2C1F: return 0x2C4F; - case 0x2C20: return 0x2C50; - case 0x2C21: return 0x2C51; - case 0x2C22: return 0x2C52; - case 0x2C23: return 0x2C53; - case 0x2C24: return 0x2C54; - case 0x2C25: return 0x2C55; - case 0x2C26: return 0x2C56; - case 0x2C27: return 0x2C57; - case 0x2C28: return 0x2C58; - case 0x2C29: return 0x2C59; - case 0x2C2A: return 0x2C5A; - case 0x2C2B: return 0x2C5B; - case 0x2C2C: return 0x2C5C; - case 0x2C2D: return 0x2C5D; - case 0x2C2E: return 0x2C5E; - case 0x2C60: return 0x2C61; - case 0x2C62: return 0x026B; - case 0x2C63: return 0x1D7D; - case 0x2C64: return 0x027D; - case 0x2C67: return 0x2C68; - case 0x2C69: return 0x2C6A; - case 0x2C6B: return 0x2C6C; - case 0x2C6D: return 0x0251; - case 0x2C6E: return 0x0271; - case 0x2C6F: return 0x0250; - case 0x2C70: return 0x0252; - case 0x2C72: return 0x2C73; - case 0x2C75: return 0x2C76; - case 0x2C7E: return 0x023F; - case 0x2C7F: return 0x0240; - case 0x2C80: return 0x2C81; - case 0x2C82: return 0x2C83; - case 0x2C84: return 0x2C85; - case 0x2C86: return 0x2C87; - case 0x2C88: return 0x2C89; - case 0x2C8A: return 0x2C8B; - case 0x2C8C: return 0x2C8D; - case 0x2C8E: return 0x2C8F; - case 0x2C90: return 0x2C91; - case 0x2C92: return 0x2C93; - case 0x2C94: return 0x2C95; - case 0x2C96: return 0x2C97; - case 0x2C98: return 0x2C99; - case 0x2C9A: return 0x2C9B; - case 0x2C9C: return 0x2C9D; - case 0x2C9E: return 0x2C9F; - case 0x2CA0: return 0x2CA1; - case 0x2CA2: return 0x2CA3; - case 0x2CA4: return 0x2CA5; - case 0x2CA6: return 0x2CA7; - case 0x2CA8: return 0x2CA9; - case 0x2CAA: return 0x2CAB; - case 0x2CAC: return 0x2CAD; - case 0x2CAE: return 0x2CAF; - case 0x2CB0: return 0x2CB1; - case 0x2CB2: return 0x2CB3; - case 0x2CB4: return 0x2CB5; - case 0x2CB6: return 0x2CB7; - case 0x2CB8: return 0x2CB9; - case 0x2CBA: return 0x2CBB; - case 0x2CBC: return 0x2CBD; - case 0x2CBE: return 0x2CBF; - case 0x2CC0: return 0x2CC1; - case 0x2CC2: return 0x2CC3; - case 0x2CC4: return 0x2CC5; - case 0x2CC6: return 0x2CC7; - case 0x2CC8: return 0x2CC9; - case 0x2CCA: return 0x2CCB; - case 0x2CCC: return 0x2CCD; - case 0x2CCE: return 0x2CCF; - case 0x2CD0: return 0x2CD1; - case 0x2CD2: return 0x2CD3; - case 0x2CD4: return 0x2CD5; - case 0x2CD6: return 0x2CD7; - case 0x2CD8: return 0x2CD9; - case 0x2CDA: return 0x2CDB; - case 0x2CDC: return 0x2CDD; - case 0x2CDE: return 0x2CDF; - case 0x2CE0: return 0x2CE1; - case 0x2CE2: return 0x2CE3; - case 0x2CEB: return 0x2CEC; - case 0x2CED: return 0x2CEE; - case 0x2CF2: return 0x2CF3; - case 0xA640: return 0xA641; - case 0xA642: return 0xA643; - case 0xA644: return 0xA645; - case 0xA646: return 0xA647; - case 0xA648: return 0xA649; - case 0xA64A: return 0xA64B; - case 0xA64C: return 0xA64D; - case 0xA64E: return 0xA64F; - case 0xA650: return 0xA651; - case 0xA652: return 0xA653; - case 0xA654: return 0xA655; - case 0xA656: return 0xA657; - case 0xA658: return 0xA659; - case 0xA65A: return 0xA65B; - case 0xA65C: return 0xA65D; - case 0xA65E: return 0xA65F; - case 0xA660: return 0xA661; - case 0xA662: return 0xA663; - case 0xA664: return 0xA665; - case 0xA666: return 0xA667; - case 0xA668: return 0xA669; - case 0xA66A: return 0xA66B; - case 0xA66C: return 0xA66D; - case 0xA680: return 0xA681; - case 0xA682: return 0xA683; - case 0xA684: return 0xA685; - case 0xA686: return 0xA687; - case 0xA688: return 0xA689; - case 0xA68A: return 0xA68B; - case 0xA68C: return 0xA68D; - case 0xA68E: return 0xA68F; - case 0xA690: return 0xA691; - case 0xA692: return 0xA693; - case 0xA694: return 0xA695; - case 0xA696: return 0xA697; - case 0xA698: return 0xA699; - case 0xA69A: return 0xA69B; - case 0xA722: return 0xA723; - case 0xA724: return 0xA725; - case 0xA726: return 0xA727; - case 0xA728: return 0xA729; - case 0xA72A: return 0xA72B; - case 0xA72C: return 0xA72D; - case 0xA72E: return 0xA72F; - case 0xA732: return 0xA733; - case 0xA734: return 0xA735; - case 0xA736: return 0xA737; - case 0xA738: return 0xA739; - case 0xA73A: return 0xA73B; - case 0xA73C: return 0xA73D; - case 0xA73E: return 0xA73F; - case 0xA740: return 0xA741; - case 0xA742: return 0xA743; - case 0xA744: return 0xA745; - case 0xA746: return 0xA747; - case 0xA748: return 0xA749; - case 0xA74A: return 0xA74B; - case 0xA74C: return 0xA74D; - case 0xA74E: return 0xA74F; - case 0xA750: return 0xA751; - case 0xA752: return 0xA753; - case 0xA754: return 0xA755; - case 0xA756: return 0xA757; - case 0xA758: return 0xA759; - case 0xA75A: return 0xA75B; - case 0xA75C: return 0xA75D; - case 0xA75E: return 0xA75F; - case 0xA760: return 0xA761; - case 0xA762: return 0xA763; - case 0xA764: return 0xA765; - case 0xA766: return 0xA767; - case 0xA768: return 0xA769; - case 0xA76A: return 0xA76B; - case 0xA76C: return 0xA76D; - case 0xA76E: return 0xA76F; - case 0xA779: return 0xA77A; - case 0xA77B: return 0xA77C; - case 0xA77D: return 0x1D79; - case 0xA77E: return 0xA77F; - case 0xA780: return 0xA781; - case 0xA782: return 0xA783; - case 0xA784: return 0xA785; - case 0xA786: return 0xA787; - case 0xA78B: return 0xA78C; - case 0xA78D: return 0x0265; - case 0xA790: return 0xA791; - case 0xA792: return 0xA793; - case 0xA796: return 0xA797; - case 0xA798: return 0xA799; - case 0xA79A: return 0xA79B; - case 0xA79C: return 0xA79D; - case 0xA79E: return 0xA79F; - case 0xA7A0: return 0xA7A1; - case 0xA7A2: return 0xA7A3; - case 0xA7A4: return 0xA7A5; - case 0xA7A6: return 0xA7A7; - case 0xA7A8: return 0xA7A9; - case 0xA7AA: return 0x0266; - case 0xA7AB: return 0x025C; - case 0xA7AC: return 0x0261; - case 0xA7AD: return 0x026C; - case 0xA7AE: return 0x026A; - case 0xA7B0: return 0x029E; - case 0xA7B1: return 0x0287; - case 0xA7B2: return 0x029D; - case 0xA7B3: return 0xAB53; - case 0xA7B4: return 0xA7B5; - case 0xA7B6: return 0xA7B7; - case 0xA7B8: return 0xA7B9; - case 0xA7BA: return 0xA7BB; - case 0xA7BC: return 0xA7BD; - case 0xA7BE: return 0xA7BF; - case 0xA7C2: return 0xA7C3; - case 0xA7C4: return 0xA794; - case 0xA7C5: return 0x0282; - case 0xA7C6: return 0x1D8E; - case 0xFF21: return 0xFF41; - case 0xFF22: return 0xFF42; - case 0xFF23: return 0xFF43; - case 0xFF24: return 0xFF44; - case 0xFF25: return 0xFF45; - case 0xFF26: return 0xFF46; - case 0xFF27: return 0xFF47; - case 0xFF28: return 0xFF48; - case 0xFF29: return 0xFF49; - case 0xFF2A: return 0xFF4A; - case 0xFF2B: return 0xFF4B; - case 0xFF2C: return 0xFF4C; - case 0xFF2D: return 0xFF4D; - case 0xFF2E: return 0xFF4E; - case 0xFF2F: return 0xFF4F; - case 0xFF30: return 0xFF50; - case 0xFF31: return 0xFF51; - case 0xFF32: return 0xFF52; - case 0xFF33: return 0xFF53; - case 0xFF34: return 0xFF54; - case 0xFF35: return 0xFF55; - case 0xFF36: return 0xFF56; - case 0xFF37: return 0xFF57; - case 0xFF38: return 0xFF58; - case 0xFF39: return 0xFF59; - case 0xFF3A: return 0xFF5A; - case 0x10400: return 0x10428; - case 0x10401: return 0x10429; - case 0x10402: return 0x1042A; - case 0x10403: return 0x1042B; - case 0x10404: return 0x1042C; - case 0x10405: return 0x1042D; - case 0x10406: return 0x1042E; - case 0x10407: return 0x1042F; - case 0x10408: return 0x10430; - case 0x10409: return 0x10431; - case 0x1040A: return 0x10432; - case 0x1040B: return 0x10433; - case 0x1040C: return 0x10434; - case 0x1040D: return 0x10435; - case 0x1040E: return 0x10436; - case 0x1040F: return 0x10437; - case 0x10410: return 0x10438; - case 0x10411: return 0x10439; - case 0x10412: return 0x1043A; - case 0x10413: return 0x1043B; - case 0x10414: return 0x1043C; - case 0x10415: return 0x1043D; - case 0x10416: return 0x1043E; - case 0x10417: return 0x1043F; - case 0x10418: return 0x10440; - case 0x10419: return 0x10441; - case 0x1041A: return 0x10442; - case 0x1041B: return 0x10443; - case 0x1041C: return 0x10444; - case 0x1041D: return 0x10445; - case 0x1041E: return 0x10446; - case 0x1041F: return 0x10447; - case 0x10420: return 0x10448; - case 0x10421: return 0x10449; - case 0x10422: return 0x1044A; - case 0x10423: return 0x1044B; - case 0x10424: return 0x1044C; - case 0x10425: return 0x1044D; - case 0x10426: return 0x1044E; - case 0x10427: return 0x1044F; - case 0x104B0: return 0x104D8; - case 0x104B1: return 0x104D9; - case 0x104B2: return 0x104DA; - case 0x104B3: return 0x104DB; - case 0x104B4: return 0x104DC; - case 0x104B5: return 0x104DD; - case 0x104B6: return 0x104DE; - case 0x104B7: return 0x104DF; - case 0x104B8: return 0x104E0; - case 0x104B9: return 0x104E1; - case 0x104BA: return 0x104E2; - case 0x104BB: return 0x104E3; - case 0x104BC: return 0x104E4; - case 0x104BD: return 0x104E5; - case 0x104BE: return 0x104E6; - case 0x104BF: return 0x104E7; - case 0x104C0: return 0x104E8; - case 0x104C1: return 0x104E9; - case 0x104C2: return 0x104EA; - case 0x104C3: return 0x104EB; - case 0x104C4: return 0x104EC; - case 0x104C5: return 0x104ED; - case 0x104C6: return 0x104EE; - case 0x104C7: return 0x104EF; - case 0x104C8: return 0x104F0; - case 0x104C9: return 0x104F1; - case 0x104CA: return 0x104F2; - case 0x104CB: return 0x104F3; - case 0x104CC: return 0x104F4; - case 0x104CD: return 0x104F5; - case 0x104CE: return 0x104F6; - case 0x104CF: return 0x104F7; - case 0x104D0: return 0x104F8; - case 0x104D1: return 0x104F9; - case 0x104D2: return 0x104FA; - case 0x104D3: return 0x104FB; - case 0x10C80: return 0x10CC0; - case 0x10C81: return 0x10CC1; - case 0x10C82: return 0x10CC2; - case 0x10C83: return 0x10CC3; - case 0x10C84: return 0x10CC4; - case 0x10C85: return 0x10CC5; - case 0x10C86: return 0x10CC6; - case 0x10C87: return 0x10CC7; - case 0x10C88: return 0x10CC8; - case 0x10C89: return 0x10CC9; - case 0x10C8A: return 0x10CCA; - case 0x10C8B: return 0x10CCB; - case 0x10C8C: return 0x10CCC; - case 0x10C8D: return 0x10CCD; - case 0x10C8E: return 0x10CCE; - case 0x10C8F: return 0x10CCF; - case 0x10C90: return 0x10CD0; - case 0x10C91: return 0x10CD1; - case 0x10C92: return 0x10CD2; - case 0x10C93: return 0x10CD3; - case 0x10C94: return 0x10CD4; - case 0x10C95: return 0x10CD5; - case 0x10C96: return 0x10CD6; - case 0x10C97: return 0x10CD7; - case 0x10C98: return 0x10CD8; - case 0x10C99: return 0x10CD9; - case 0x10C9A: return 0x10CDA; - case 0x10C9B: return 0x10CDB; - case 0x10C9C: return 0x10CDC; - case 0x10C9D: return 0x10CDD; - case 0x10C9E: return 0x10CDE; - case 0x10C9F: return 0x10CDF; - case 0x10CA0: return 0x10CE0; - case 0x10CA1: return 0x10CE1; - case 0x10CA2: return 0x10CE2; - case 0x10CA3: return 0x10CE3; - case 0x10CA4: return 0x10CE4; - case 0x10CA5: return 0x10CE5; - case 0x10CA6: return 0x10CE6; - case 0x10CA7: return 0x10CE7; - case 0x10CA8: return 0x10CE8; - case 0x10CA9: return 0x10CE9; - case 0x10CAA: return 0x10CEA; - case 0x10CAB: return 0x10CEB; - case 0x10CAC: return 0x10CEC; - case 0x10CAD: return 0x10CED; - case 0x10CAE: return 0x10CEE; - case 0x10CAF: return 0x10CEF; - case 0x10CB0: return 0x10CF0; - case 0x10CB1: return 0x10CF1; - case 0x10CB2: return 0x10CF2; - case 0x118A0: return 0x118C0; - case 0x118A1: return 0x118C1; - case 0x118A2: return 0x118C2; - case 0x118A3: return 0x118C3; - case 0x118A4: return 0x118C4; - case 0x118A5: return 0x118C5; - case 0x118A6: return 0x118C6; - case 0x118A7: return 0x118C7; - case 0x118A8: return 0x118C8; - case 0x118A9: return 0x118C9; - case 0x118AA: return 0x118CA; - case 0x118AB: return 0x118CB; - case 0x118AC: return 0x118CC; - case 0x118AD: return 0x118CD; - case 0x118AE: return 0x118CE; - case 0x118AF: return 0x118CF; - case 0x118B0: return 0x118D0; - case 0x118B1: return 0x118D1; - case 0x118B2: return 0x118D2; - case 0x118B3: return 0x118D3; - case 0x118B4: return 0x118D4; - case 0x118B5: return 0x118D5; - case 0x118B6: return 0x118D6; - case 0x118B7: return 0x118D7; - case 0x118B8: return 0x118D8; - case 0x118B9: return 0x118D9; - case 0x118BA: return 0x118DA; - case 0x118BB: return 0x118DB; - case 0x118BC: return 0x118DC; - case 0x118BD: return 0x118DD; - case 0x118BE: return 0x118DE; - case 0x118BF: return 0x118DF; - case 0x16E40: return 0x16E60; - case 0x16E41: return 0x16E61; - case 0x16E42: return 0x16E62; - case 0x16E43: return 0x16E63; - case 0x16E44: return 0x16E64; - case 0x16E45: return 0x16E65; - case 0x16E46: return 0x16E66; - case 0x16E47: return 0x16E67; - case 0x16E48: return 0x16E68; - case 0x16E49: return 0x16E69; - case 0x16E4A: return 0x16E6A; - case 0x16E4B: return 0x16E6B; - case 0x16E4C: return 0x16E6C; - case 0x16E4D: return 0x16E6D; - case 0x16E4E: return 0x16E6E; - case 0x16E4F: return 0x16E6F; - case 0x16E50: return 0x16E70; - case 0x16E51: return 0x16E71; - case 0x16E52: return 0x16E72; - case 0x16E53: return 0x16E73; - case 0x16E54: return 0x16E74; - case 0x16E55: return 0x16E75; - case 0x16E56: return 0x16E76; - case 0x16E57: return 0x16E77; - case 0x16E58: return 0x16E78; - case 0x16E59: return 0x16E79; - case 0x16E5A: return 0x16E7A; - case 0x16E5B: return 0x16E7B; - case 0x16E5C: return 0x16E7C; - case 0x16E5D: return 0x16E7D; - case 0x16E5E: return 0x16E7E; - case 0x16E5F: return 0x16E7F; - case 0x1E900: return 0x1E922; - case 0x1E901: return 0x1E923; - case 0x1E902: return 0x1E924; - case 0x1E903: return 0x1E925; - case 0x1E904: return 0x1E926; - case 0x1E905: return 0x1E927; - case 0x1E906: return 0x1E928; - case 0x1E907: return 0x1E929; - case 0x1E908: return 0x1E92A; - case 0x1E909: return 0x1E92B; - case 0x1E90A: return 0x1E92C; - case 0x1E90B: return 0x1E92D; - case 0x1E90C: return 0x1E92E; - case 0x1E90D: return 0x1E92F; - case 0x1E90E: return 0x1E930; - case 0x1E90F: return 0x1E931; - case 0x1E910: return 0x1E932; - case 0x1E911: return 0x1E933; - case 0x1E912: return 0x1E934; - case 0x1E913: return 0x1E935; - case 0x1E914: return 0x1E936; - case 0x1E915: return 0x1E937; - case 0x1E916: return 0x1E938; - case 0x1E917: return 0x1E939; - case 0x1E918: return 0x1E93A; - case 0x1E919: return 0x1E93B; - case 0x1E91A: return 0x1E93C; - case 0x1E91B: return 0x1E93D; - case 0x1E91C: return 0x1E93E; - case 0x1E91D: return 0x1E93F; - case 0x1E91E: return 0x1E940; - case 0x1E91F: return 0x1E941; - case 0x1E920: return 0x1E942; - case 0x1E921: return 0x1E943; - default: return ch; - } - } - - int cwal_utf8_char_toupper( int ch ){ - /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt - dated April 1, 2019. */ - switch(ch){ - case 0x0061: return 0x0041; - case 0x0062: return 0x0042; - case 0x0063: return 0x0043; - case 0x0064: return 0x0044; - case 0x0065: return 0x0045; - case 0x0066: return 0x0046; - case 0x0067: return 0x0047; - case 0x0068: return 0x0048; - case 0x0069: return 0x0049; - case 0x006A: return 0x004A; - case 0x006B: return 0x004B; - case 0x006C: return 0x004C; - case 0x006D: return 0x004D; - case 0x006E: return 0x004E; - case 0x006F: return 0x004F; - case 0x0070: return 0x0050; - case 0x0071: return 0x0051; - case 0x0072: return 0x0052; - case 0x0073: return 0x0053; - case 0x0074: return 0x0054; - case 0x0075: return 0x0055; - case 0x0076: return 0x0056; - case 0x0077: return 0x0057; - case 0x0078: return 0x0058; - case 0x0079: return 0x0059; - case 0x007A: return 0x005A; - case 0x00B5: return 0x039C; - case 0x00E0: return 0x00C0; - case 0x00E1: return 0x00C1; - case 0x00E2: return 0x00C2; - case 0x00E3: return 0x00C3; - case 0x00E4: return 0x00C4; - case 0x00E5: return 0x00C5; - case 0x00E6: return 0x00C6; - case 0x00E7: return 0x00C7; - case 0x00E8: return 0x00C8; - case 0x00E9: return 0x00C9; - case 0x00EA: return 0x00CA; - case 0x00EB: return 0x00CB; - case 0x00EC: return 0x00CC; - case 0x00ED: return 0x00CD; - case 0x00EE: return 0x00CE; - case 0x00EF: return 0x00CF; - case 0x00F0: return 0x00D0; - case 0x00F1: return 0x00D1; - case 0x00F2: return 0x00D2; - case 0x00F3: return 0x00D3; - case 0x00F4: return 0x00D4; - case 0x00F5: return 0x00D5; - case 0x00F6: return 0x00D6; - case 0x00F8: return 0x00D8; - case 0x00F9: return 0x00D9; - case 0x00FA: return 0x00DA; - case 0x00FB: return 0x00DB; - case 0x00FC: return 0x00DC; - case 0x00FD: return 0x00DD; - case 0x00FE: return 0x00DE; - case 0x00FF: return 0x0178; - case 0x0101: return 0x0100; - case 0x0103: return 0x0102; - case 0x0105: return 0x0104; - case 0x0107: return 0x0106; - case 0x0109: return 0x0108; - case 0x010B: return 0x010A; - case 0x010D: return 0x010C; - case 0x010F: return 0x010E; - case 0x0111: return 0x0110; - case 0x0113: return 0x0112; - case 0x0115: return 0x0114; - case 0x0117: return 0x0116; - case 0x0119: return 0x0118; - case 0x011B: return 0x011A; - case 0x011D: return 0x011C; - case 0x011F: return 0x011E; - case 0x0121: return 0x0120; - case 0x0123: return 0x0122; - case 0x0125: return 0x0124; - case 0x0127: return 0x0126; - case 0x0129: return 0x0128; - case 0x012B: return 0x012A; - case 0x012D: return 0x012C; - case 0x012F: return 0x012E; - case 0x0131: return 0x0049; - case 0x0133: return 0x0132; - case 0x0135: return 0x0134; - case 0x0137: return 0x0136; - case 0x013A: return 0x0139; - case 0x013C: return 0x013B; - case 0x013E: return 0x013D; - case 0x0140: return 0x013F; - case 0x0142: return 0x0141; - case 0x0144: return 0x0143; - case 0x0146: return 0x0145; - case 0x0148: return 0x0147; - case 0x014B: return 0x014A; - case 0x014D: return 0x014C; - case 0x014F: return 0x014E; - case 0x0151: return 0x0150; - case 0x0153: return 0x0152; - case 0x0155: return 0x0154; - case 0x0157: return 0x0156; - case 0x0159: return 0x0158; - case 0x015B: return 0x015A; - case 0x015D: return 0x015C; - case 0x015F: return 0x015E; - case 0x0161: return 0x0160; - case 0x0163: return 0x0162; - case 0x0165: return 0x0164; - case 0x0167: return 0x0166; - case 0x0169: return 0x0168; - case 0x016B: return 0x016A; - case 0x016D: return 0x016C; - case 0x016F: return 0x016E; - case 0x0171: return 0x0170; - case 0x0173: return 0x0172; - case 0x0175: return 0x0174; - case 0x0177: return 0x0176; - case 0x017A: return 0x0179; - case 0x017C: return 0x017B; - case 0x017E: return 0x017D; - case 0x017F: return 0x0053; - case 0x0180: return 0x0243; - case 0x0183: return 0x0182; - case 0x0185: return 0x0184; - case 0x0188: return 0x0187; - case 0x018C: return 0x018B; - case 0x0192: return 0x0191; - case 0x0195: return 0x01F6; - case 0x0199: return 0x0198; - case 0x019A: return 0x023D; - case 0x019E: return 0x0220; - case 0x01A1: return 0x01A0; - case 0x01A3: return 0x01A2; - case 0x01A5: return 0x01A4; - case 0x01A8: return 0x01A7; - case 0x01AD: return 0x01AC; - case 0x01B0: return 0x01AF; - case 0x01B4: return 0x01B3; - case 0x01B6: return 0x01B5; - case 0x01B9: return 0x01B8; - case 0x01BD: return 0x01BC; - case 0x01BF: return 0x01F7; - case 0x01C5: return 0x01C4; - case 0x01C6: return 0x01C4; - case 0x01C8: return 0x01C7; - case 0x01C9: return 0x01C7; - case 0x01CB: return 0x01CA; - case 0x01CC: return 0x01CA; - case 0x01CE: return 0x01CD; - case 0x01D0: return 0x01CF; - case 0x01D2: return 0x01D1; - case 0x01D4: return 0x01D3; - case 0x01D6: return 0x01D5; - case 0x01D8: return 0x01D7; - case 0x01DA: return 0x01D9; - case 0x01DC: return 0x01DB; - case 0x01DD: return 0x018E; - case 0x01DF: return 0x01DE; - case 0x01E1: return 0x01E0; - case 0x01E3: return 0x01E2; - case 0x01E5: return 0x01E4; - case 0x01E7: return 0x01E6; - case 0x01E9: return 0x01E8; - case 0x01EB: return 0x01EA; - case 0x01ED: return 0x01EC; - case 0x01EF: return 0x01EE; - case 0x01F2: return 0x01F1; - case 0x01F3: return 0x01F1; - case 0x01F5: return 0x01F4; - case 0x01F9: return 0x01F8; - case 0x01FB: return 0x01FA; - case 0x01FD: return 0x01FC; - case 0x01FF: return 0x01FE; - case 0x0201: return 0x0200; - case 0x0203: return 0x0202; - case 0x0205: return 0x0204; - case 0x0207: return 0x0206; - case 0x0209: return 0x0208; - case 0x020B: return 0x020A; - case 0x020D: return 0x020C; - case 0x020F: return 0x020E; - case 0x0211: return 0x0210; - case 0x0213: return 0x0212; - case 0x0215: return 0x0214; - case 0x0217: return 0x0216; - case 0x0219: return 0x0218; - case 0x021B: return 0x021A; - case 0x021D: return 0x021C; - case 0x021F: return 0x021E; - case 0x0223: return 0x0222; - case 0x0225: return 0x0224; - case 0x0227: return 0x0226; - case 0x0229: return 0x0228; - case 0x022B: return 0x022A; - case 0x022D: return 0x022C; - case 0x022F: return 0x022E; - case 0x0231: return 0x0230; - case 0x0233: return 0x0232; - case 0x023C: return 0x023B; - case 0x023F: return 0x2C7E; - case 0x0240: return 0x2C7F; - case 0x0242: return 0x0241; - case 0x0247: return 0x0246; - case 0x0249: return 0x0248; - case 0x024B: return 0x024A; - case 0x024D: return 0x024C; - case 0x024F: return 0x024E; - case 0x0250: return 0x2C6F; - case 0x0251: return 0x2C6D; - case 0x0252: return 0x2C70; - case 0x0253: return 0x0181; - case 0x0254: return 0x0186; - case 0x0256: return 0x0189; - case 0x0257: return 0x018A; - case 0x0259: return 0x018F; - case 0x025B: return 0x0190; - case 0x025C: return 0xA7AB; - case 0x0260: return 0x0193; - case 0x0261: return 0xA7AC; - case 0x0263: return 0x0194; - case 0x0265: return 0xA78D; - case 0x0266: return 0xA7AA; - case 0x0268: return 0x0197; - case 0x0269: return 0x0196; - case 0x026A: return 0xA7AE; - case 0x026B: return 0x2C62; - case 0x026C: return 0xA7AD; - case 0x026F: return 0x019C; - case 0x0271: return 0x2C6E; - case 0x0272: return 0x019D; - case 0x0275: return 0x019F; - case 0x027D: return 0x2C64; - case 0x0280: return 0x01A6; - case 0x0282: return 0xA7C5; - case 0x0283: return 0x01A9; - case 0x0287: return 0xA7B1; - case 0x0288: return 0x01AE; - case 0x0289: return 0x0244; - case 0x028A: return 0x01B1; - case 0x028B: return 0x01B2; - case 0x028C: return 0x0245; - case 0x0292: return 0x01B7; - case 0x029D: return 0xA7B2; - case 0x029E: return 0xA7B0; - case 0x0345: return 0x0399; - case 0x0371: return 0x0370; - case 0x0373: return 0x0372; - case 0x0377: return 0x0376; - case 0x037B: return 0x03FD; - case 0x037C: return 0x03FE; - case 0x037D: return 0x03FF; - case 0x03AC: return 0x0386; - case 0x03AD: return 0x0388; - case 0x03AE: return 0x0389; - case 0x03AF: return 0x038A; - case 0x03B1: return 0x0391; - case 0x03B2: return 0x0392; - case 0x03B3: return 0x0393; - case 0x03B4: return 0x0394; - case 0x03B5: return 0x0395; - case 0x03B6: return 0x0396; - case 0x03B7: return 0x0397; - case 0x03B8: return 0x0398; - case 0x03B9: return 0x0399; - case 0x03BA: return 0x039A; - case 0x03BB: return 0x039B; - case 0x03BC: return 0x039C; - case 0x03BD: return 0x039D; - case 0x03BE: return 0x039E; - case 0x03BF: return 0x039F; - case 0x03C0: return 0x03A0; - case 0x03C1: return 0x03A1; - case 0x03C2: return 0x03A3; - case 0x03C3: return 0x03A3; - case 0x03C4: return 0x03A4; - case 0x03C5: return 0x03A5; - case 0x03C6: return 0x03A6; - case 0x03C7: return 0x03A7; - case 0x03C8: return 0x03A8; - case 0x03C9: return 0x03A9; - case 0x03CA: return 0x03AA; - case 0x03CB: return 0x03AB; - case 0x03CC: return 0x038C; - case 0x03CD: return 0x038E; - case 0x03CE: return 0x038F; - case 0x03D0: return 0x0392; - case 0x03D1: return 0x0398; - case 0x03D5: return 0x03A6; - case 0x03D6: return 0x03A0; - case 0x03D7: return 0x03CF; - case 0x03D9: return 0x03D8; - case 0x03DB: return 0x03DA; - case 0x03DD: return 0x03DC; - case 0x03DF: return 0x03DE; - case 0x03E1: return 0x03E0; - case 0x03E3: return 0x03E2; - case 0x03E5: return 0x03E4; - case 0x03E7: return 0x03E6; - case 0x03E9: return 0x03E8; - case 0x03EB: return 0x03EA; - case 0x03ED: return 0x03EC; - case 0x03EF: return 0x03EE; - case 0x03F0: return 0x039A; - case 0x03F1: return 0x03A1; - case 0x03F2: return 0x03F9; - case 0x03F3: return 0x037F; - case 0x03F5: return 0x0395; - case 0x03F8: return 0x03F7; - case 0x03FB: return 0x03FA; - case 0x0430: return 0x0410; - case 0x0431: return 0x0411; - case 0x0432: return 0x0412; - case 0x0433: return 0x0413; - case 0x0434: return 0x0414; - case 0x0435: return 0x0415; - case 0x0436: return 0x0416; - case 0x0437: return 0x0417; - case 0x0438: return 0x0418; - case 0x0439: return 0x0419; - case 0x043A: return 0x041A; - case 0x043B: return 0x041B; - case 0x043C: return 0x041C; - case 0x043D: return 0x041D; - case 0x043E: return 0x041E; - case 0x043F: return 0x041F; - case 0x0440: return 0x0420; - case 0x0441: return 0x0421; - case 0x0442: return 0x0422; - case 0x0443: return 0x0423; - case 0x0444: return 0x0424; - case 0x0445: return 0x0425; - case 0x0446: return 0x0426; - case 0x0447: return 0x0427; - case 0x0448: return 0x0428; - case 0x0449: return 0x0429; - case 0x044A: return 0x042A; - case 0x044B: return 0x042B; - case 0x044C: return 0x042C; - case 0x044D: return 0x042D; - case 0x044E: return 0x042E; - case 0x044F: return 0x042F; - case 0x0450: return 0x0400; - case 0x0451: return 0x0401; - case 0x0452: return 0x0402; - case 0x0453: return 0x0403; - case 0x0454: return 0x0404; - case 0x0455: return 0x0405; - case 0x0456: return 0x0406; - case 0x0457: return 0x0407; - case 0x0458: return 0x0408; - case 0x0459: return 0x0409; - case 0x045A: return 0x040A; - case 0x045B: return 0x040B; - case 0x045C: return 0x040C; - case 0x045D: return 0x040D; - case 0x045E: return 0x040E; - case 0x045F: return 0x040F; - case 0x0461: return 0x0460; - case 0x0463: return 0x0462; - case 0x0465: return 0x0464; - case 0x0467: return 0x0466; - case 0x0469: return 0x0468; - case 0x046B: return 0x046A; - case 0x046D: return 0x046C; - case 0x046F: return 0x046E; - case 0x0471: return 0x0470; - case 0x0473: return 0x0472; - case 0x0475: return 0x0474; - case 0x0477: return 0x0476; - case 0x0479: return 0x0478; - case 0x047B: return 0x047A; - case 0x047D: return 0x047C; - case 0x047F: return 0x047E; - case 0x0481: return 0x0480; - case 0x048B: return 0x048A; - case 0x048D: return 0x048C; - case 0x048F: return 0x048E; - case 0x0491: return 0x0490; - case 0x0493: return 0x0492; - case 0x0495: return 0x0494; - case 0x0497: return 0x0496; - case 0x0499: return 0x0498; - case 0x049B: return 0x049A; - case 0x049D: return 0x049C; - case 0x049F: return 0x049E; - case 0x04A1: return 0x04A0; - case 0x04A3: return 0x04A2; - case 0x04A5: return 0x04A4; - case 0x04A7: return 0x04A6; - case 0x04A9: return 0x04A8; - case 0x04AB: return 0x04AA; - case 0x04AD: return 0x04AC; - case 0x04AF: return 0x04AE; - case 0x04B1: return 0x04B0; - case 0x04B3: return 0x04B2; - case 0x04B5: return 0x04B4; - case 0x04B7: return 0x04B6; - case 0x04B9: return 0x04B8; - case 0x04BB: return 0x04BA; - case 0x04BD: return 0x04BC; - case 0x04BF: return 0x04BE; - case 0x04C2: return 0x04C1; - case 0x04C4: return 0x04C3; - case 0x04C6: return 0x04C5; - case 0x04C8: return 0x04C7; - case 0x04CA: return 0x04C9; - case 0x04CC: return 0x04CB; - case 0x04CE: return 0x04CD; - case 0x04CF: return 0x04C0; - case 0x04D1: return 0x04D0; - case 0x04D3: return 0x04D2; - case 0x04D5: return 0x04D4; - case 0x04D7: return 0x04D6; - case 0x04D9: return 0x04D8; - case 0x04DB: return 0x04DA; - case 0x04DD: return 0x04DC; - case 0x04DF: return 0x04DE; - case 0x04E1: return 0x04E0; - case 0x04E3: return 0x04E2; - case 0x04E5: return 0x04E4; - case 0x04E7: return 0x04E6; - case 0x04E9: return 0x04E8; - case 0x04EB: return 0x04EA; - case 0x04ED: return 0x04EC; - case 0x04EF: return 0x04EE; - case 0x04F1: return 0x04F0; - case 0x04F3: return 0x04F2; - case 0x04F5: return 0x04F4; - case 0x04F7: return 0x04F6; - case 0x04F9: return 0x04F8; - case 0x04FB: return 0x04FA; - case 0x04FD: return 0x04FC; - case 0x04FF: return 0x04FE; - case 0x0501: return 0x0500; - case 0x0503: return 0x0502; - case 0x0505: return 0x0504; - case 0x0507: return 0x0506; - case 0x0509: return 0x0508; - case 0x050B: return 0x050A; - case 0x050D: return 0x050C; - case 0x050F: return 0x050E; - case 0x0511: return 0x0510; - case 0x0513: return 0x0512; - case 0x0515: return 0x0514; - case 0x0517: return 0x0516; - case 0x0519: return 0x0518; - case 0x051B: return 0x051A; - case 0x051D: return 0x051C; - case 0x051F: return 0x051E; - case 0x0521: return 0x0520; - case 0x0523: return 0x0522; - case 0x0525: return 0x0524; - case 0x0527: return 0x0526; - case 0x0529: return 0x0528; - case 0x052B: return 0x052A; - case 0x052D: return 0x052C; - case 0x052F: return 0x052E; - case 0x0561: return 0x0531; - case 0x0562: return 0x0532; - case 0x0563: return 0x0533; - case 0x0564: return 0x0534; - case 0x0565: return 0x0535; - case 0x0566: return 0x0536; - case 0x0567: return 0x0537; - case 0x0568: return 0x0538; - case 0x0569: return 0x0539; - case 0x056A: return 0x053A; - case 0x056B: return 0x053B; - case 0x056C: return 0x053C; - case 0x056D: return 0x053D; - case 0x056E: return 0x053E; - case 0x056F: return 0x053F; - case 0x0570: return 0x0540; - case 0x0571: return 0x0541; - case 0x0572: return 0x0542; - case 0x0573: return 0x0543; - case 0x0574: return 0x0544; - case 0x0575: return 0x0545; - case 0x0576: return 0x0546; - case 0x0577: return 0x0547; - case 0x0578: return 0x0548; - case 0x0579: return 0x0549; - case 0x057A: return 0x054A; - case 0x057B: return 0x054B; - case 0x057C: return 0x054C; - case 0x057D: return 0x054D; - case 0x057E: return 0x054E; - case 0x057F: return 0x054F; - case 0x0580: return 0x0550; - case 0x0581: return 0x0551; - case 0x0582: return 0x0552; - case 0x0583: return 0x0553; - case 0x0584: return 0x0554; - case 0x0585: return 0x0555; - case 0x0586: return 0x0556; - case 0x10D0: return 0x1C90; - case 0x10D1: return 0x1C91; - case 0x10D2: return 0x1C92; - case 0x10D3: return 0x1C93; - case 0x10D4: return 0x1C94; - case 0x10D5: return 0x1C95; - case 0x10D6: return 0x1C96; - case 0x10D7: return 0x1C97; - case 0x10D8: return 0x1C98; - case 0x10D9: return 0x1C99; - case 0x10DA: return 0x1C9A; - case 0x10DB: return 0x1C9B; - case 0x10DC: return 0x1C9C; - case 0x10DD: return 0x1C9D; - case 0x10DE: return 0x1C9E; - case 0x10DF: return 0x1C9F; - case 0x10E0: return 0x1CA0; - case 0x10E1: return 0x1CA1; - case 0x10E2: return 0x1CA2; - case 0x10E3: return 0x1CA3; - case 0x10E4: return 0x1CA4; - case 0x10E5: return 0x1CA5; - case 0x10E6: return 0x1CA6; - case 0x10E7: return 0x1CA7; - case 0x10E8: return 0x1CA8; - case 0x10E9: return 0x1CA9; - case 0x10EA: return 0x1CAA; - case 0x10EB: return 0x1CAB; - case 0x10EC: return 0x1CAC; - case 0x10ED: return 0x1CAD; - case 0x10EE: return 0x1CAE; - case 0x10EF: return 0x1CAF; - case 0x10F0: return 0x1CB0; - case 0x10F1: return 0x1CB1; - case 0x10F2: return 0x1CB2; - case 0x10F3: return 0x1CB3; - case 0x10F4: return 0x1CB4; - case 0x10F5: return 0x1CB5; - case 0x10F6: return 0x1CB6; - case 0x10F7: return 0x1CB7; - case 0x10F8: return 0x1CB8; - case 0x10F9: return 0x1CB9; - case 0x10FA: return 0x1CBA; - case 0x10FD: return 0x1CBD; - case 0x10FE: return 0x1CBE; - case 0x10FF: return 0x1CBF; - case 0x13F8: return 0x13F0; - case 0x13F9: return 0x13F1; - case 0x13FA: return 0x13F2; - case 0x13FB: return 0x13F3; - case 0x13FC: return 0x13F4; - case 0x13FD: return 0x13F5; - case 0x1C80: return 0x0412; - case 0x1C81: return 0x0414; - case 0x1C82: return 0x041E; - case 0x1C83: return 0x0421; - case 0x1C84: return 0x0422; - case 0x1C85: return 0x0422; - case 0x1C86: return 0x042A; - case 0x1C87: return 0x0462; - case 0x1C88: return 0xA64A; - case 0x1D79: return 0xA77D; - case 0x1D7D: return 0x2C63; - case 0x1D8E: return 0xA7C6; - case 0x1E01: return 0x1E00; - case 0x1E03: return 0x1E02; - case 0x1E05: return 0x1E04; - case 0x1E07: return 0x1E06; - case 0x1E09: return 0x1E08; - case 0x1E0B: return 0x1E0A; - case 0x1E0D: return 0x1E0C; - case 0x1E0F: return 0x1E0E; - case 0x1E11: return 0x1E10; - case 0x1E13: return 0x1E12; - case 0x1E15: return 0x1E14; - case 0x1E17: return 0x1E16; - case 0x1E19: return 0x1E18; - case 0x1E1B: return 0x1E1A; - case 0x1E1D: return 0x1E1C; - case 0x1E1F: return 0x1E1E; - case 0x1E21: return 0x1E20; - case 0x1E23: return 0x1E22; - case 0x1E25: return 0x1E24; - case 0x1E27: return 0x1E26; - case 0x1E29: return 0x1E28; - case 0x1E2B: return 0x1E2A; - case 0x1E2D: return 0x1E2C; - case 0x1E2F: return 0x1E2E; - case 0x1E31: return 0x1E30; - case 0x1E33: return 0x1E32; - case 0x1E35: return 0x1E34; - case 0x1E37: return 0x1E36; - case 0x1E39: return 0x1E38; - case 0x1E3B: return 0x1E3A; - case 0x1E3D: return 0x1E3C; - case 0x1E3F: return 0x1E3E; - case 0x1E41: return 0x1E40; - case 0x1E43: return 0x1E42; - case 0x1E45: return 0x1E44; - case 0x1E47: return 0x1E46; - case 0x1E49: return 0x1E48; - case 0x1E4B: return 0x1E4A; - case 0x1E4D: return 0x1E4C; - case 0x1E4F: return 0x1E4E; - case 0x1E51: return 0x1E50; - case 0x1E53: return 0x1E52; - case 0x1E55: return 0x1E54; - case 0x1E57: return 0x1E56; - case 0x1E59: return 0x1E58; - case 0x1E5B: return 0x1E5A; - case 0x1E5D: return 0x1E5C; - case 0x1E5F: return 0x1E5E; - case 0x1E61: return 0x1E60; - case 0x1E63: return 0x1E62; - case 0x1E65: return 0x1E64; - case 0x1E67: return 0x1E66; - case 0x1E69: return 0x1E68; - case 0x1E6B: return 0x1E6A; - case 0x1E6D: return 0x1E6C; - case 0x1E6F: return 0x1E6E; - case 0x1E71: return 0x1E70; - case 0x1E73: return 0x1E72; - case 0x1E75: return 0x1E74; - case 0x1E77: return 0x1E76; - case 0x1E79: return 0x1E78; - case 0x1E7B: return 0x1E7A; - case 0x1E7D: return 0x1E7C; - case 0x1E7F: return 0x1E7E; - case 0x1E81: return 0x1E80; - case 0x1E83: return 0x1E82; - case 0x1E85: return 0x1E84; - case 0x1E87: return 0x1E86; - case 0x1E89: return 0x1E88; - case 0x1E8B: return 0x1E8A; - case 0x1E8D: return 0x1E8C; - case 0x1E8F: return 0x1E8E; - case 0x1E91: return 0x1E90; - case 0x1E93: return 0x1E92; - case 0x1E95: return 0x1E94; - case 0x1E9B: return 0x1E60; - case 0x1EA1: return 0x1EA0; - case 0x1EA3: return 0x1EA2; - case 0x1EA5: return 0x1EA4; - case 0x1EA7: return 0x1EA6; - case 0x1EA9: return 0x1EA8; - case 0x1EAB: return 0x1EAA; - case 0x1EAD: return 0x1EAC; - case 0x1EAF: return 0x1EAE; - case 0x1EB1: return 0x1EB0; - case 0x1EB3: return 0x1EB2; - case 0x1EB5: return 0x1EB4; - case 0x1EB7: return 0x1EB6; - case 0x1EB9: return 0x1EB8; - case 0x1EBB: return 0x1EBA; - case 0x1EBD: return 0x1EBC; - case 0x1EBF: return 0x1EBE; - case 0x1EC1: return 0x1EC0; - case 0x1EC3: return 0x1EC2; - case 0x1EC5: return 0x1EC4; - case 0x1EC7: return 0x1EC6; - case 0x1EC9: return 0x1EC8; - case 0x1ECB: return 0x1ECA; - case 0x1ECD: return 0x1ECC; - case 0x1ECF: return 0x1ECE; - case 0x1ED1: return 0x1ED0; - case 0x1ED3: return 0x1ED2; - case 0x1ED5: return 0x1ED4; - case 0x1ED7: return 0x1ED6; - case 0x1ED9: return 0x1ED8; - case 0x1EDB: return 0x1EDA; - case 0x1EDD: return 0x1EDC; - case 0x1EDF: return 0x1EDE; - case 0x1EE1: return 0x1EE0; - case 0x1EE3: return 0x1EE2; - case 0x1EE5: return 0x1EE4; - case 0x1EE7: return 0x1EE6; - case 0x1EE9: return 0x1EE8; - case 0x1EEB: return 0x1EEA; - case 0x1EED: return 0x1EEC; - case 0x1EEF: return 0x1EEE; - case 0x1EF1: return 0x1EF0; - case 0x1EF3: return 0x1EF2; - case 0x1EF5: return 0x1EF4; - case 0x1EF7: return 0x1EF6; - case 0x1EF9: return 0x1EF8; - case 0x1EFB: return 0x1EFA; - case 0x1EFD: return 0x1EFC; - case 0x1EFF: return 0x1EFE; - case 0x1F00: return 0x1F08; - case 0x1F01: return 0x1F09; - case 0x1F02: return 0x1F0A; - case 0x1F03: return 0x1F0B; - case 0x1F04: return 0x1F0C; - case 0x1F05: return 0x1F0D; - case 0x1F06: return 0x1F0E; - case 0x1F07: return 0x1F0F; - case 0x1F10: return 0x1F18; - case 0x1F11: return 0x1F19; - case 0x1F12: return 0x1F1A; - case 0x1F13: return 0x1F1B; - case 0x1F14: return 0x1F1C; - case 0x1F15: return 0x1F1D; - case 0x1F20: return 0x1F28; - case 0x1F21: return 0x1F29; - case 0x1F22: return 0x1F2A; - case 0x1F23: return 0x1F2B; - case 0x1F24: return 0x1F2C; - case 0x1F25: return 0x1F2D; - case 0x1F26: return 0x1F2E; - case 0x1F27: return 0x1F2F; - case 0x1F30: return 0x1F38; - case 0x1F31: return 0x1F39; - case 0x1F32: return 0x1F3A; - case 0x1F33: return 0x1F3B; - case 0x1F34: return 0x1F3C; - case 0x1F35: return 0x1F3D; - case 0x1F36: return 0x1F3E; - case 0x1F37: return 0x1F3F; - case 0x1F40: return 0x1F48; - case 0x1F41: return 0x1F49; - case 0x1F42: return 0x1F4A; - case 0x1F43: return 0x1F4B; - case 0x1F44: return 0x1F4C; - case 0x1F45: return 0x1F4D; - case 0x1F51: return 0x1F59; - case 0x1F53: return 0x1F5B; - case 0x1F55: return 0x1F5D; - case 0x1F57: return 0x1F5F; - case 0x1F60: return 0x1F68; - case 0x1F61: return 0x1F69; - case 0x1F62: return 0x1F6A; - case 0x1F63: return 0x1F6B; - case 0x1F64: return 0x1F6C; - case 0x1F65: return 0x1F6D; - case 0x1F66: return 0x1F6E; - case 0x1F67: return 0x1F6F; - case 0x1F70: return 0x1FBA; - case 0x1F71: return 0x1FBB; - case 0x1F72: return 0x1FC8; - case 0x1F73: return 0x1FC9; - case 0x1F74: return 0x1FCA; - case 0x1F75: return 0x1FCB; - case 0x1F76: return 0x1FDA; - case 0x1F77: return 0x1FDB; - case 0x1F78: return 0x1FF8; - case 0x1F79: return 0x1FF9; - case 0x1F7A: return 0x1FEA; - case 0x1F7B: return 0x1FEB; - case 0x1F7C: return 0x1FFA; - case 0x1F7D: return 0x1FFB; - case 0x1F80: return 0x1F88; - case 0x1F81: return 0x1F89; - case 0x1F82: return 0x1F8A; - case 0x1F83: return 0x1F8B; - case 0x1F84: return 0x1F8C; - case 0x1F85: return 0x1F8D; - case 0x1F86: return 0x1F8E; - case 0x1F87: return 0x1F8F; - case 0x1F90: return 0x1F98; - case 0x1F91: return 0x1F99; - case 0x1F92: return 0x1F9A; - case 0x1F93: return 0x1F9B; - case 0x1F94: return 0x1F9C; - case 0x1F95: return 0x1F9D; - case 0x1F96: return 0x1F9E; - case 0x1F97: return 0x1F9F; - case 0x1FA0: return 0x1FA8; - case 0x1FA1: return 0x1FA9; - case 0x1FA2: return 0x1FAA; - case 0x1FA3: return 0x1FAB; - case 0x1FA4: return 0x1FAC; - case 0x1FA5: return 0x1FAD; - case 0x1FA6: return 0x1FAE; - case 0x1FA7: return 0x1FAF; - case 0x1FB0: return 0x1FB8; - case 0x1FB1: return 0x1FB9; - case 0x1FB3: return 0x1FBC; - case 0x1FBE: return 0x0399; - case 0x1FC3: return 0x1FCC; - case 0x1FD0: return 0x1FD8; - case 0x1FD1: return 0x1FD9; - case 0x1FE0: return 0x1FE8; - case 0x1FE1: return 0x1FE9; - case 0x1FE5: return 0x1FEC; - case 0x1FF3: return 0x1FFC; - case 0x214E: return 0x2132; - case 0x2170: return 0x2160; - case 0x2171: return 0x2161; - case 0x2172: return 0x2162; - case 0x2173: return 0x2163; - case 0x2174: return 0x2164; - case 0x2175: return 0x2165; - case 0x2176: return 0x2166; - case 0x2177: return 0x2167; - case 0x2178: return 0x2168; - case 0x2179: return 0x2169; - case 0x217A: return 0x216A; - case 0x217B: return 0x216B; - case 0x217C: return 0x216C; - case 0x217D: return 0x216D; - case 0x217E: return 0x216E; - case 0x217F: return 0x216F; - case 0x2184: return 0x2183; - case 0x24D0: return 0x24B6; - case 0x24D1: return 0x24B7; - case 0x24D2: return 0x24B8; - case 0x24D3: return 0x24B9; - case 0x24D4: return 0x24BA; - case 0x24D5: return 0x24BB; - case 0x24D6: return 0x24BC; - case 0x24D7: return 0x24BD; - case 0x24D8: return 0x24BE; - case 0x24D9: return 0x24BF; - case 0x24DA: return 0x24C0; - case 0x24DB: return 0x24C1; - case 0x24DC: return 0x24C2; - case 0x24DD: return 0x24C3; - case 0x24DE: return 0x24C4; - case 0x24DF: return 0x24C5; - case 0x24E0: return 0x24C6; - case 0x24E1: return 0x24C7; - case 0x24E2: return 0x24C8; - case 0x24E3: return 0x24C9; - case 0x24E4: return 0x24CA; - case 0x24E5: return 0x24CB; - case 0x24E6: return 0x24CC; - case 0x24E7: return 0x24CD; - case 0x24E8: return 0x24CE; - case 0x24E9: return 0x24CF; - case 0x2C30: return 0x2C00; - case 0x2C31: return 0x2C01; - case 0x2C32: return 0x2C02; - case 0x2C33: return 0x2C03; - case 0x2C34: return 0x2C04; - case 0x2C35: return 0x2C05; - case 0x2C36: return 0x2C06; - case 0x2C37: return 0x2C07; - case 0x2C38: return 0x2C08; - case 0x2C39: return 0x2C09; - case 0x2C3A: return 0x2C0A; - case 0x2C3B: return 0x2C0B; - case 0x2C3C: return 0x2C0C; - case 0x2C3D: return 0x2C0D; - case 0x2C3E: return 0x2C0E; - case 0x2C3F: return 0x2C0F; - case 0x2C40: return 0x2C10; - case 0x2C41: return 0x2C11; - case 0x2C42: return 0x2C12; - case 0x2C43: return 0x2C13; - case 0x2C44: return 0x2C14; - case 0x2C45: return 0x2C15; - case 0x2C46: return 0x2C16; - case 0x2C47: return 0x2C17; - case 0x2C48: return 0x2C18; - case 0x2C49: return 0x2C19; - case 0x2C4A: return 0x2C1A; - case 0x2C4B: return 0x2C1B; - case 0x2C4C: return 0x2C1C; - case 0x2C4D: return 0x2C1D; - case 0x2C4E: return 0x2C1E; - case 0x2C4F: return 0x2C1F; - case 0x2C50: return 0x2C20; - case 0x2C51: return 0x2C21; - case 0x2C52: return 0x2C22; - case 0x2C53: return 0x2C23; - case 0x2C54: return 0x2C24; - case 0x2C55: return 0x2C25; - case 0x2C56: return 0x2C26; - case 0x2C57: return 0x2C27; - case 0x2C58: return 0x2C28; - case 0x2C59: return 0x2C29; - case 0x2C5A: return 0x2C2A; - case 0x2C5B: return 0x2C2B; - case 0x2C5C: return 0x2C2C; - case 0x2C5D: return 0x2C2D; - case 0x2C5E: return 0x2C2E; - case 0x2C61: return 0x2C60; - case 0x2C65: return 0x023A; - case 0x2C66: return 0x023E; - case 0x2C68: return 0x2C67; - case 0x2C6A: return 0x2C69; - case 0x2C6C: return 0x2C6B; - case 0x2C73: return 0x2C72; - case 0x2C76: return 0x2C75; - case 0x2C81: return 0x2C80; - case 0x2C83: return 0x2C82; - case 0x2C85: return 0x2C84; - case 0x2C87: return 0x2C86; - case 0x2C89: return 0x2C88; - case 0x2C8B: return 0x2C8A; - case 0x2C8D: return 0x2C8C; - case 0x2C8F: return 0x2C8E; - case 0x2C91: return 0x2C90; - case 0x2C93: return 0x2C92; - case 0x2C95: return 0x2C94; - case 0x2C97: return 0x2C96; - case 0x2C99: return 0x2C98; - case 0x2C9B: return 0x2C9A; - case 0x2C9D: return 0x2C9C; - case 0x2C9F: return 0x2C9E; - case 0x2CA1: return 0x2CA0; - case 0x2CA3: return 0x2CA2; - case 0x2CA5: return 0x2CA4; - case 0x2CA7: return 0x2CA6; - case 0x2CA9: return 0x2CA8; - case 0x2CAB: return 0x2CAA; - case 0x2CAD: return 0x2CAC; - case 0x2CAF: return 0x2CAE; - case 0x2CB1: return 0x2CB0; - case 0x2CB3: return 0x2CB2; - case 0x2CB5: return 0x2CB4; - case 0x2CB7: return 0x2CB6; - case 0x2CB9: return 0x2CB8; - case 0x2CBB: return 0x2CBA; - case 0x2CBD: return 0x2CBC; - case 0x2CBF: return 0x2CBE; - case 0x2CC1: return 0x2CC0; - case 0x2CC3: return 0x2CC2; - case 0x2CC5: return 0x2CC4; - case 0x2CC7: return 0x2CC6; - case 0x2CC9: return 0x2CC8; - case 0x2CCB: return 0x2CCA; - case 0x2CCD: return 0x2CCC; - case 0x2CCF: return 0x2CCE; - case 0x2CD1: return 0x2CD0; - case 0x2CD3: return 0x2CD2; - case 0x2CD5: return 0x2CD4; - case 0x2CD7: return 0x2CD6; - case 0x2CD9: return 0x2CD8; - case 0x2CDB: return 0x2CDA; - case 0x2CDD: return 0x2CDC; - case 0x2CDF: return 0x2CDE; - case 0x2CE1: return 0x2CE0; - case 0x2CE3: return 0x2CE2; - case 0x2CEC: return 0x2CEB; - case 0x2CEE: return 0x2CED; - case 0x2CF3: return 0x2CF2; - case 0x2D00: return 0x10A0; - case 0x2D01: return 0x10A1; - case 0x2D02: return 0x10A2; - case 0x2D03: return 0x10A3; - case 0x2D04: return 0x10A4; - case 0x2D05: return 0x10A5; - case 0x2D06: return 0x10A6; - case 0x2D07: return 0x10A7; - case 0x2D08: return 0x10A8; - case 0x2D09: return 0x10A9; - case 0x2D0A: return 0x10AA; - case 0x2D0B: return 0x10AB; - case 0x2D0C: return 0x10AC; - case 0x2D0D: return 0x10AD; - case 0x2D0E: return 0x10AE; - case 0x2D0F: return 0x10AF; - case 0x2D10: return 0x10B0; - case 0x2D11: return 0x10B1; - case 0x2D12: return 0x10B2; - case 0x2D13: return 0x10B3; - case 0x2D14: return 0x10B4; - case 0x2D15: return 0x10B5; - case 0x2D16: return 0x10B6; - case 0x2D17: return 0x10B7; - case 0x2D18: return 0x10B8; - case 0x2D19: return 0x10B9; - case 0x2D1A: return 0x10BA; - case 0x2D1B: return 0x10BB; - case 0x2D1C: return 0x10BC; - case 0x2D1D: return 0x10BD; - case 0x2D1E: return 0x10BE; - case 0x2D1F: return 0x10BF; - case 0x2D20: return 0x10C0; - case 0x2D21: return 0x10C1; - case 0x2D22: return 0x10C2; - case 0x2D23: return 0x10C3; - case 0x2D24: return 0x10C4; - case 0x2D25: return 0x10C5; - case 0x2D27: return 0x10C7; - case 0x2D2D: return 0x10CD; - case 0xA641: return 0xA640; - case 0xA643: return 0xA642; - case 0xA645: return 0xA644; - case 0xA647: return 0xA646; - case 0xA649: return 0xA648; - case 0xA64B: return 0xA64A; - case 0xA64D: return 0xA64C; - case 0xA64F: return 0xA64E; - case 0xA651: return 0xA650; - case 0xA653: return 0xA652; - case 0xA655: return 0xA654; - case 0xA657: return 0xA656; - case 0xA659: return 0xA658; - case 0xA65B: return 0xA65A; - case 0xA65D: return 0xA65C; - case 0xA65F: return 0xA65E; - case 0xA661: return 0xA660; - case 0xA663: return 0xA662; - case 0xA665: return 0xA664; - case 0xA667: return 0xA666; - case 0xA669: return 0xA668; - case 0xA66B: return 0xA66A; - case 0xA66D: return 0xA66C; - case 0xA681: return 0xA680; - case 0xA683: return 0xA682; - case 0xA685: return 0xA684; - case 0xA687: return 0xA686; - case 0xA689: return 0xA688; - case 0xA68B: return 0xA68A; - case 0xA68D: return 0xA68C; - case 0xA68F: return 0xA68E; - case 0xA691: return 0xA690; - case 0xA693: return 0xA692; - case 0xA695: return 0xA694; - case 0xA697: return 0xA696; - case 0xA699: return 0xA698; - case 0xA69B: return 0xA69A; - case 0xA723: return 0xA722; - case 0xA725: return 0xA724; - case 0xA727: return 0xA726; - case 0xA729: return 0xA728; - case 0xA72B: return 0xA72A; - case 0xA72D: return 0xA72C; - case 0xA72F: return 0xA72E; - case 0xA733: return 0xA732; - case 0xA735: return 0xA734; - case 0xA737: return 0xA736; - case 0xA739: return 0xA738; - case 0xA73B: return 0xA73A; - case 0xA73D: return 0xA73C; - case 0xA73F: return 0xA73E; - case 0xA741: return 0xA740; - case 0xA743: return 0xA742; - case 0xA745: return 0xA744; - case 0xA747: return 0xA746; - case 0xA749: return 0xA748; - case 0xA74B: return 0xA74A; - case 0xA74D: return 0xA74C; - case 0xA74F: return 0xA74E; - case 0xA751: return 0xA750; - case 0xA753: return 0xA752; - case 0xA755: return 0xA754; - case 0xA757: return 0xA756; - case 0xA759: return 0xA758; - case 0xA75B: return 0xA75A; - case 0xA75D: return 0xA75C; - case 0xA75F: return 0xA75E; - case 0xA761: return 0xA760; - case 0xA763: return 0xA762; - case 0xA765: return 0xA764; - case 0xA767: return 0xA766; - case 0xA769: return 0xA768; - case 0xA76B: return 0xA76A; - case 0xA76D: return 0xA76C; - case 0xA76F: return 0xA76E; - case 0xA77A: return 0xA779; - case 0xA77C: return 0xA77B; - case 0xA77F: return 0xA77E; - case 0xA781: return 0xA780; - case 0xA783: return 0xA782; - case 0xA785: return 0xA784; - case 0xA787: return 0xA786; - case 0xA78C: return 0xA78B; - case 0xA791: return 0xA790; - case 0xA793: return 0xA792; - case 0xA794: return 0xA7C4; - case 0xA797: return 0xA796; - case 0xA799: return 0xA798; - case 0xA79B: return 0xA79A; - case 0xA79D: return 0xA79C; - case 0xA79F: return 0xA79E; - case 0xA7A1: return 0xA7A0; - case 0xA7A3: return 0xA7A2; - case 0xA7A5: return 0xA7A4; - case 0xA7A7: return 0xA7A6; - case 0xA7A9: return 0xA7A8; - case 0xA7B5: return 0xA7B4; - case 0xA7B7: return 0xA7B6; - case 0xA7B9: return 0xA7B8; - case 0xA7BB: return 0xA7BA; - case 0xA7BD: return 0xA7BC; - case 0xA7BF: return 0xA7BE; - case 0xA7C3: return 0xA7C2; - case 0xAB53: return 0xA7B3; - case 0xAB70: return 0x13A0; - case 0xAB71: return 0x13A1; - case 0xAB72: return 0x13A2; - case 0xAB73: return 0x13A3; - case 0xAB74: return 0x13A4; - case 0xAB75: return 0x13A5; - case 0xAB76: return 0x13A6; - case 0xAB77: return 0x13A7; - case 0xAB78: return 0x13A8; - case 0xAB79: return 0x13A9; - case 0xAB7A: return 0x13AA; - case 0xAB7B: return 0x13AB; - case 0xAB7C: return 0x13AC; - case 0xAB7D: return 0x13AD; - case 0xAB7E: return 0x13AE; - case 0xAB7F: return 0x13AF; - case 0xAB80: return 0x13B0; - case 0xAB81: return 0x13B1; - case 0xAB82: return 0x13B2; - case 0xAB83: return 0x13B3; - case 0xAB84: return 0x13B4; - case 0xAB85: return 0x13B5; - case 0xAB86: return 0x13B6; - case 0xAB87: return 0x13B7; - case 0xAB88: return 0x13B8; - case 0xAB89: return 0x13B9; - case 0xAB8A: return 0x13BA; - case 0xAB8B: return 0x13BB; - case 0xAB8C: return 0x13BC; - case 0xAB8D: return 0x13BD; - case 0xAB8E: return 0x13BE; - case 0xAB8F: return 0x13BF; - case 0xAB90: return 0x13C0; - case 0xAB91: return 0x13C1; - case 0xAB92: return 0x13C2; - case 0xAB93: return 0x13C3; - case 0xAB94: return 0x13C4; - case 0xAB95: return 0x13C5; - case 0xAB96: return 0x13C6; - case 0xAB97: return 0x13C7; - case 0xAB98: return 0x13C8; - case 0xAB99: return 0x13C9; - case 0xAB9A: return 0x13CA; - case 0xAB9B: return 0x13CB; - case 0xAB9C: return 0x13CC; - case 0xAB9D: return 0x13CD; - case 0xAB9E: return 0x13CE; - case 0xAB9F: return 0x13CF; - case 0xABA0: return 0x13D0; - case 0xABA1: return 0x13D1; - case 0xABA2: return 0x13D2; - case 0xABA3: return 0x13D3; - case 0xABA4: return 0x13D4; - case 0xABA5: return 0x13D5; - case 0xABA6: return 0x13D6; - case 0xABA7: return 0x13D7; - case 0xABA8: return 0x13D8; - case 0xABA9: return 0x13D9; - case 0xABAA: return 0x13DA; - case 0xABAB: return 0x13DB; - case 0xABAC: return 0x13DC; - case 0xABAD: return 0x13DD; - case 0xABAE: return 0x13DE; - case 0xABAF: return 0x13DF; - case 0xABB0: return 0x13E0; - case 0xABB1: return 0x13E1; - case 0xABB2: return 0x13E2; - case 0xABB3: return 0x13E3; - case 0xABB4: return 0x13E4; - case 0xABB5: return 0x13E5; - case 0xABB6: return 0x13E6; - case 0xABB7: return 0x13E7; - case 0xABB8: return 0x13E8; - case 0xABB9: return 0x13E9; - case 0xABBA: return 0x13EA; - case 0xABBB: return 0x13EB; - case 0xABBC: return 0x13EC; - case 0xABBD: return 0x13ED; - case 0xABBE: return 0x13EE; - case 0xABBF: return 0x13EF; - case 0xFF41: return 0xFF21; - case 0xFF42: return 0xFF22; - case 0xFF43: return 0xFF23; - case 0xFF44: return 0xFF24; - case 0xFF45: return 0xFF25; - case 0xFF46: return 0xFF26; - case 0xFF47: return 0xFF27; - case 0xFF48: return 0xFF28; - case 0xFF49: return 0xFF29; - case 0xFF4A: return 0xFF2A; - case 0xFF4B: return 0xFF2B; - case 0xFF4C: return 0xFF2C; - case 0xFF4D: return 0xFF2D; - case 0xFF4E: return 0xFF2E; - case 0xFF4F: return 0xFF2F; - case 0xFF50: return 0xFF30; - case 0xFF51: return 0xFF31; - case 0xFF52: return 0xFF32; - case 0xFF53: return 0xFF33; - case 0xFF54: return 0xFF34; - case 0xFF55: return 0xFF35; - case 0xFF56: return 0xFF36; - case 0xFF57: return 0xFF37; - case 0xFF58: return 0xFF38; - case 0xFF59: return 0xFF39; - case 0xFF5A: return 0xFF3A; - case 0x10428: return 0x10400; - case 0x10429: return 0x10401; - case 0x1042A: return 0x10402; - case 0x1042B: return 0x10403; - case 0x1042C: return 0x10404; - case 0x1042D: return 0x10405; - case 0x1042E: return 0x10406; - case 0x1042F: return 0x10407; - case 0x10430: return 0x10408; - case 0x10431: return 0x10409; - case 0x10432: return 0x1040A; - case 0x10433: return 0x1040B; - case 0x10434: return 0x1040C; - case 0x10435: return 0x1040D; - case 0x10436: return 0x1040E; - case 0x10437: return 0x1040F; - case 0x10438: return 0x10410; - case 0x10439: return 0x10411; - case 0x1043A: return 0x10412; - case 0x1043B: return 0x10413; - case 0x1043C: return 0x10414; - case 0x1043D: return 0x10415; - case 0x1043E: return 0x10416; - case 0x1043F: return 0x10417; - case 0x10440: return 0x10418; - case 0x10441: return 0x10419; - case 0x10442: return 0x1041A; - case 0x10443: return 0x1041B; - case 0x10444: return 0x1041C; - case 0x10445: return 0x1041D; - case 0x10446: return 0x1041E; - case 0x10447: return 0x1041F; - case 0x10448: return 0x10420; - case 0x10449: return 0x10421; - case 0x1044A: return 0x10422; - case 0x1044B: return 0x10423; - case 0x1044C: return 0x10424; - case 0x1044D: return 0x10425; - case 0x1044E: return 0x10426; - case 0x1044F: return 0x10427; - case 0x104D8: return 0x104B0; - case 0x104D9: return 0x104B1; - case 0x104DA: return 0x104B2; - case 0x104DB: return 0x104B3; - case 0x104DC: return 0x104B4; - case 0x104DD: return 0x104B5; - case 0x104DE: return 0x104B6; - case 0x104DF: return 0x104B7; - case 0x104E0: return 0x104B8; - case 0x104E1: return 0x104B9; - case 0x104E2: return 0x104BA; - case 0x104E3: return 0x104BB; - case 0x104E4: return 0x104BC; - case 0x104E5: return 0x104BD; - case 0x104E6: return 0x104BE; - case 0x104E7: return 0x104BF; - case 0x104E8: return 0x104C0; - case 0x104E9: return 0x104C1; - case 0x104EA: return 0x104C2; - case 0x104EB: return 0x104C3; - case 0x104EC: return 0x104C4; - case 0x104ED: return 0x104C5; - case 0x104EE: return 0x104C6; - case 0x104EF: return 0x104C7; - case 0x104F0: return 0x104C8; - case 0x104F1: return 0x104C9; - case 0x104F2: return 0x104CA; - case 0x104F3: return 0x104CB; - case 0x104F4: return 0x104CC; - case 0x104F5: return 0x104CD; - case 0x104F6: return 0x104CE; - case 0x104F7: return 0x104CF; - case 0x104F8: return 0x104D0; - case 0x104F9: return 0x104D1; - case 0x104FA: return 0x104D2; - case 0x104FB: return 0x104D3; - case 0x10CC0: return 0x10C80; - case 0x10CC1: return 0x10C81; - case 0x10CC2: return 0x10C82; - case 0x10CC3: return 0x10C83; - case 0x10CC4: return 0x10C84; - case 0x10CC5: return 0x10C85; - case 0x10CC6: return 0x10C86; - case 0x10CC7: return 0x10C87; - case 0x10CC8: return 0x10C88; - case 0x10CC9: return 0x10C89; - case 0x10CCA: return 0x10C8A; - case 0x10CCB: return 0x10C8B; - case 0x10CCC: return 0x10C8C; - case 0x10CCD: return 0x10C8D; - case 0x10CCE: return 0x10C8E; - case 0x10CCF: return 0x10C8F; - case 0x10CD0: return 0x10C90; - case 0x10CD1: return 0x10C91; - case 0x10CD2: return 0x10C92; - case 0x10CD3: return 0x10C93; - case 0x10CD4: return 0x10C94; - case 0x10CD5: return 0x10C95; - case 0x10CD6: return 0x10C96; - case 0x10CD7: return 0x10C97; - case 0x10CD8: return 0x10C98; - case 0x10CD9: return 0x10C99; - case 0x10CDA: return 0x10C9A; - case 0x10CDB: return 0x10C9B; - case 0x10CDC: return 0x10C9C; - case 0x10CDD: return 0x10C9D; - case 0x10CDE: return 0x10C9E; - case 0x10CDF: return 0x10C9F; - case 0x10CE0: return 0x10CA0; - case 0x10CE1: return 0x10CA1; - case 0x10CE2: return 0x10CA2; - case 0x10CE3: return 0x10CA3; - case 0x10CE4: return 0x10CA4; - case 0x10CE5: return 0x10CA5; - case 0x10CE6: return 0x10CA6; - case 0x10CE7: return 0x10CA7; - case 0x10CE8: return 0x10CA8; - case 0x10CE9: return 0x10CA9; - case 0x10CEA: return 0x10CAA; - case 0x10CEB: return 0x10CAB; - case 0x10CEC: return 0x10CAC; - case 0x10CED: return 0x10CAD; - case 0x10CEE: return 0x10CAE; - case 0x10CEF: return 0x10CAF; - case 0x10CF0: return 0x10CB0; - case 0x10CF1: return 0x10CB1; - case 0x10CF2: return 0x10CB2; - case 0x118C0: return 0x118A0; - case 0x118C1: return 0x118A1; - case 0x118C2: return 0x118A2; - case 0x118C3: return 0x118A3; - case 0x118C4: return 0x118A4; - case 0x118C5: return 0x118A5; - case 0x118C6: return 0x118A6; - case 0x118C7: return 0x118A7; - case 0x118C8: return 0x118A8; - case 0x118C9: return 0x118A9; - case 0x118CA: return 0x118AA; - case 0x118CB: return 0x118AB; - case 0x118CC: return 0x118AC; - case 0x118CD: return 0x118AD; - case 0x118CE: return 0x118AE; - case 0x118CF: return 0x118AF; - case 0x118D0: return 0x118B0; - case 0x118D1: return 0x118B1; - case 0x118D2: return 0x118B2; - case 0x118D3: return 0x118B3; - case 0x118D4: return 0x118B4; - case 0x118D5: return 0x118B5; - case 0x118D6: return 0x118B6; - case 0x118D7: return 0x118B7; - case 0x118D8: return 0x118B8; - case 0x118D9: return 0x118B9; - case 0x118DA: return 0x118BA; - case 0x118DB: return 0x118BB; - case 0x118DC: return 0x118BC; - case 0x118DD: return 0x118BD; - case 0x118DE: return 0x118BE; - case 0x118DF: return 0x118BF; - case 0x16E60: return 0x16E40; - case 0x16E61: return 0x16E41; - case 0x16E62: return 0x16E42; - case 0x16E63: return 0x16E43; - case 0x16E64: return 0x16E44; - case 0x16E65: return 0x16E45; - case 0x16E66: return 0x16E46; - case 0x16E67: return 0x16E47; - case 0x16E68: return 0x16E48; - case 0x16E69: return 0x16E49; - case 0x16E6A: return 0x16E4A; - case 0x16E6B: return 0x16E4B; - case 0x16E6C: return 0x16E4C; - case 0x16E6D: return 0x16E4D; - case 0x16E6E: return 0x16E4E; - case 0x16E6F: return 0x16E4F; - case 0x16E70: return 0x16E50; - case 0x16E71: return 0x16E51; - case 0x16E72: return 0x16E52; - case 0x16E73: return 0x16E53; - case 0x16E74: return 0x16E54; - case 0x16E75: return 0x16E55; - case 0x16E76: return 0x16E56; - case 0x16E77: return 0x16E57; - case 0x16E78: return 0x16E58; - case 0x16E79: return 0x16E59; - case 0x16E7A: return 0x16E5A; - case 0x16E7B: return 0x16E5B; - case 0x16E7C: return 0x16E5C; - case 0x16E7D: return 0x16E5D; - case 0x16E7E: return 0x16E5E; - case 0x16E7F: return 0x16E5F; - case 0x1E922: return 0x1E900; - case 0x1E923: return 0x1E901; - case 0x1E924: return 0x1E902; - case 0x1E925: return 0x1E903; - case 0x1E926: return 0x1E904; - case 0x1E927: return 0x1E905; - case 0x1E928: return 0x1E906; - case 0x1E929: return 0x1E907; - case 0x1E92A: return 0x1E908; - case 0x1E92B: return 0x1E909; - case 0x1E92C: return 0x1E90A; - case 0x1E92D: return 0x1E90B; - case 0x1E92E: return 0x1E90C; - case 0x1E92F: return 0x1E90D; - case 0x1E930: return 0x1E90E; - case 0x1E931: return 0x1E90F; - case 0x1E932: return 0x1E910; - case 0x1E933: return 0x1E911; - case 0x1E934: return 0x1E912; - case 0x1E935: return 0x1E913; - case 0x1E936: return 0x1E914; - case 0x1E937: return 0x1E915; - case 0x1E938: return 0x1E916; - case 0x1E939: return 0x1E917; - case 0x1E93A: return 0x1E918; - case 0x1E93B: return 0x1E919; - case 0x1E93C: return 0x1E91A; - case 0x1E93D: return 0x1E91B; - case 0x1E93E: return 0x1E91C; - case 0x1E93F: return 0x1E91D; - case 0x1E940: return 0x1E91E; - case 0x1E941: return 0x1E91F; - case 0x1E942: return 0x1E920; - case 0x1E943: return 0x1E921; - default: return ch; - } - } - - -#undef MARKER -#ifdef PUSHED_MSC_WARNING -# pragma warning( pop ) -# undef PUSHED_MSC_WARNING -#endif -/* end of file cwal_utf.c */ -/* start of file JSON_parser/JSON_parser.c */ -/* -Copyright (c) 2007-2013 Jean Gressmann (jean@0x42.de) - -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. -*/ - -/* - Changelog: - 2013-09-08 - Updated license to to be compatible with Debian license requirements. - - 2012-06-06 - Fix for invalid UTF16 characters and some comment fixex (thomas.h.moog@intel.com). - - 2010-11-25 - Support for custom memory allocation (sgbeal@googlemail.com). - - 2010-05-07 - Added error handling for memory allocation failure (sgbeal@googlemail.com). - Added diagnosis errors for invalid JSON. - - 2010-03-25 - Fixed buffer overrun in grow_parse_buffer & cleaned up code. - - 2009-10-19 - Replaced long double in JSON_value_struct with double after reports - of strtold being broken on some platforms (charles@transmissionbt.com). - - 2009-05-17 - Incorporated benrudiak@googlemail.com fix for UTF16 decoding. - - 2009-05-14 - Fixed float parsing bug related to a locale being set that didn't - use '.' as decimal point character (charles@transmissionbt.com). - - 2008-10-14 - Renamed states.IN to states.IT to avoid name clash which IN macro - defined in windef.h (alexey.pelykh@gmail.com) - - 2008-07-19 - Removed some duplicate code & debugging variable (charles@transmissionbt.com) - - 2008-05-28 - Made JSON_value structure ansi C compliant. This bug was report by - trisk@acm.jhu.edu - - 2008-05-20 - Fixed bug reported by charles@transmissionbt.com where the switching - from static to dynamic parse buffer did not copy the static parse - buffer's content. -*/ - - - -#include -#include -#include -#include -#include -#include -#include -#include - - -#ifdef _MSC_VER -# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ -# pragma warning(disable:4996) /* unsecure sscanf */ -# pragma warning(disable:4127) /* conditional expression is constant */ -# endif -#endif - - -#define true 1 -#define false 0 -#define XX -1 /* the universal error code */ - -/* values chosen so that the object size is approx equal to one page (4K) */ -#ifndef JSON_PARSER_STACK_SIZE -# define JSON_PARSER_STACK_SIZE 128 -#endif - -#ifndef JSON_PARSER_PARSE_BUFFER_SIZE -# define JSON_PARSER_PARSE_BUFFER_SIZE 3500 -#endif - -typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason); - -#ifdef JSON_PARSER_DEBUG_MALLOC -# define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason) -#else -# define JSON_parser_malloc(func, bytes, reason) func(bytes) -#endif - -typedef unsigned short UTF16; - -struct JSON_parser_struct { - JSON_parser_callback callback; - void* ctx; - signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error; - char decimal_point; - UTF16 utf16_high_surrogate; - int current_char; - int depth; - int top; - int stack_capacity; - signed char* stack; - char* parse_buffer; - size_t parse_buffer_capacity; - size_t parse_buffer_count; - signed char static_stack[JSON_PARSER_STACK_SIZE]; - char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE]; - JSON_malloc_t malloc; - JSON_free_t free; -}; - -#define COUNTOF(x) (sizeof(x)/sizeof(x[0])) - -/* - Characters are mapped into these character classes. This allows for - a significant reduction in the size of the state transition table. -*/ - - - -enum classes { - C_SPACE, /* space */ - C_WHITE, /* other whitespace */ - C_LCURB, /* { */ - C_RCURB, /* } */ - C_LSQRB, /* [ */ - C_RSQRB, /* ] */ - C_COLON, /* : */ - C_COMMA, /* , */ - C_QUOTE, /* " */ - C_BACKS, /* \ */ - C_SLASH, /* / */ - C_PLUS, /* + */ - C_MINUS, /* - */ - C_POINT, /* . */ - C_ZERO , /* 0 */ - C_DIGIT, /* 123456789 */ - C_LOW_A, /* a */ - C_LOW_B, /* b */ - C_LOW_C, /* c */ - C_LOW_D, /* d */ - C_LOW_E, /* e */ - C_LOW_F, /* f */ - C_LOW_L, /* l */ - C_LOW_N, /* n */ - C_LOW_R, /* r */ - C_LOW_S, /* s */ - C_LOW_T, /* t */ - C_LOW_U, /* u */ - C_ABCDF, /* ABCDF */ - C_E, /* E */ - C_ETC, /* everything else */ - C_STAR, /* * */ - NR_CLASSES -}; - -static const signed char ascii_class[128] = { -/* - This array maps the 128 ASCII characters into character classes. - The remaining Unicode characters should be mapped to C_ETC. - Non-whitespace control characters are errors. -*/ - XX, XX, XX, XX, XX, XX, XX, XX, - XX, C_WHITE, C_WHITE, XX, XX, C_WHITE, XX, XX, - XX, XX, XX, XX, XX, XX, XX, XX, - XX, XX, XX, XX, XX, XX, XX, XX, - - C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH, - C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, - C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - - C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC, - - C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC, - C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC -}; - - -/* - The state codes. -*/ -enum states { - GO, /* start */ - OK, /* ok */ - OB, /* object */ - KE, /* key */ - CO, /* colon */ - VA, /* value */ - AR, /* array */ - ST, /* string */ - ESC, /* escape */ - U1, /* u1 */ - U2, /* u2 */ - U3, /* u3 */ - U4, /* u4 */ - MI, /* minus */ - ZE, /* zero */ - IT, /* integer */ - FR, /* fraction */ - E1, /* e */ - E2, /* ex */ - E3, /* exp */ - T1, /* tr */ - T2, /* tru */ - T3, /* true */ - F1, /* fa */ - F2, /* fal */ - F3, /* fals */ - F4, /* false */ - N1, /* nu */ - N2, /* nul */ - N3, /* null */ - C1, /* / */ - C2, /* / * */ - C3, /* * */ - FX, /* *.* *eE* */ - D1, /* second UTF-16 character decoding started by \ */ - D2, /* second UTF-16 character proceeded by u */ - NR_STATES -}; - -enum actions -{ - CB = -10, /* comment begin */ - CE = -11, /* comment end */ - FA = -12, /* false */ - TR = -13, /* false */ - NU = -14, /* null */ - DE = -15, /* double detected by exponent e E */ - DF = -16, /* double detected by fraction . */ - SB = -17, /* string begin */ - MX = -18, /* integer detected by minus */ - ZX = -19, /* integer detected by zero */ - IX = -20, /* integer detected by 1-9 */ - EX = -21, /* next char is escaped */ - UC = -22 /* Unicode character read */ -}; - - -static const signed char state_transition_table[NR_STATES][NR_CLASSES] = { -/* - The state transition table takes the current state and the current symbol, - and returns either a new state or an action. An action is represented as a - negative number. A JSON text is accepted if at the end of the text the - state is OK and if the mode is MODE_DONE. - - white 1-9 ABCDF etc - space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * */ -/*start GO*/ {GO,GO,-6,XX,-5,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*ok OK*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*object OB*/ {OB,OB,XX,-9,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*key KE*/ {KE,KE,XX,XX,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*colon CO*/ {CO,CO,XX,XX,XX,XX,-2,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*value VA*/ {VA,VA,-6,XX,-5,XX,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX}, -/*array AR*/ {AR,AR,-6,XX,-5,-7,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX}, -/*string ST*/ {ST,XX,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST}, -/*escape ES*/ {XX,XX,XX,XX,XX,XX,XX,XX,ST,ST,ST,XX,XX,XX,XX,XX,XX,ST,XX,XX,XX,ST,XX,ST,ST,XX,ST,U1,XX,XX,XX,XX}, -/*u1 U1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U2,U2,U2,U2,U2,U2,U2,U2,XX,XX,XX,XX,XX,XX,U2,U2,XX,XX}, -/*u2 U2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U3,U3,U3,U3,U3,U3,U3,U3,XX,XX,XX,XX,XX,XX,U3,U3,XX,XX}, -/*u3 U3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U4,U4,U4,U4,U4,U4,U4,U4,XX,XX,XX,XX,XX,XX,U4,U4,XX,XX}, -/*u4 U4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,UC,UC,UC,UC,UC,UC,UC,UC,XX,XX,XX,XX,XX,XX,UC,UC,XX,XX}, -/*minus MI*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ZE,IT,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*zero ZE*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*int IT*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,IT,IT,XX,XX,XX,XX,DE,XX,XX,XX,XX,XX,XX,XX,XX,DE,XX,XX}, -/*frac FR*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX}, -/*e E1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E2,E2,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*ex E2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*exp E3*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*tr T1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T2,XX,XX,XX,XX,XX,XX,XX}, -/*tru T2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T3,XX,XX,XX,XX}, -/*true T3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*fa F1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*fal F2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F3,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*fals F3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F4,XX,XX,XX,XX,XX,XX}, -/*false F4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*nu N1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N2,XX,XX,XX,XX}, -/*nul N2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N3,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*null N3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*/ C1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,C2}, -/*/star C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, -/** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, -/*_. FX*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX}, -/*\ D1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,D2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, -/*\ D2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U1,XX,XX,XX,XX}, -}; - - -/* - These modes can be pushed on the stack. -*/ -enum modes { - MODE_ARRAY = 1, - MODE_DONE = 2, - MODE_KEY = 3, - MODE_OBJECT = 4 -}; - -static void set_error(JSON_parser jc) -{ - switch (jc->state) { - case GO: - switch (jc->current_char) { - case '{': case '}': case '[': case ']': - jc->error = JSON_E_UNBALANCED_COLLECTION; - break; - default: - jc->error = JSON_E_INVALID_CHAR; - break; - } - break; - case OB: - jc->error = JSON_E_EXPECTED_KEY; - break; - case AR: - jc->error = JSON_E_UNBALANCED_COLLECTION; - break; - case CO: - jc->error = JSON_E_EXPECTED_COLON; - break; - case KE: - jc->error = JSON_E_EXPECTED_KEY; - break; - /* \uXXXX\uYYYY */ - case U1: case U2: case U3: case U4: case D1: case D2: - jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; - break; - /* true, false, null */ - case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3: - jc->error = JSON_E_INVALID_KEYWORD; - break; - /* minus, integer, fraction, exponent */ - case MI: case ZE: case IT: case FR: case E1: case E2: case E3: - jc->error = JSON_E_INVALID_NUMBER; - break; - default: - jc->error = JSON_E_INVALID_CHAR; - break; - } -} - -static int -push(JSON_parser jc, int mode) -{ -/* - Push a mode onto the stack. Return false if there is overflow. -*/ - assert(jc->top <= jc->stack_capacity); - - if (jc->depth < 0) { - if (jc->top == jc->stack_capacity) { - const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]); - const size_t new_capacity = jc->stack_capacity * 2; - const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]); - void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack"); - if (!mem) { - jc->error = JSON_E_OUT_OF_MEMORY; - return false; - } - jc->stack_capacity = (int)new_capacity; - memcpy(mem, jc->stack, bytes_to_copy); - if (jc->stack != &jc->static_stack[0]) { - jc->free(jc->stack); - } - jc->stack = (signed char*)mem; - } - } else { - if (jc->top == jc->depth) { - jc->error = JSON_E_NESTING_DEPTH_REACHED; - return false; - } - } - jc->stack[++jc->top] = (signed char)mode; - return true; -} - - -static int -pop(JSON_parser jc, int mode) -{ -/* - Pop the stack, assuring that the current mode matches the expectation. - Return false if there is underflow or if the modes mismatch. -*/ - if (jc->top < 0 || jc->stack[jc->top] != mode) { - return false; - } - jc->top -= 1; - return true; -} - - -#define parse_buffer_clear(jc) \ - do {\ - jc->parse_buffer_count = 0;\ - jc->parse_buffer[0] = 0;\ - } while (0) - -#define parse_buffer_pop_back_char(jc)\ - do {\ - assert(jc->parse_buffer_count >= 1);\ - --jc->parse_buffer_count;\ - jc->parse_buffer[jc->parse_buffer_count] = 0;\ - } while (0) - - - -void delete_JSON_parser(JSON_parser jc) -{ - if (jc) { - if (jc->stack != &jc->static_stack[0]) { - jc->free((void*)jc->stack); - } - if (jc->parse_buffer != &jc->static_parse_buffer[0]) { - jc->free((void*)jc->parse_buffer); - } - jc->free((void*)jc); - } -} - -int JSON_parser_reset(JSON_parser jc) -{ - if (NULL == jc) { - return false; - } - - jc->state = GO; - jc->top = -1; - - /* parser has been used previously? */ - if (NULL == jc->parse_buffer) { - - /* Do we want non-bound stack? */ - if (jc->depth > 0) { - jc->stack_capacity = jc->depth; - if (jc->depth <= (int)COUNTOF(jc->static_stack)) { - jc->stack = &jc->static_stack[0]; - } else { - const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]); - jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack"); - if (jc->stack == NULL) { - return false; - } - } - } else { - jc->stack_capacity = (int)COUNTOF(jc->static_stack); - jc->depth = -1; - jc->stack = &jc->static_stack[0]; - } - - /* set up the parse buffer */ - jc->parse_buffer = &jc->static_parse_buffer[0]; - jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer); - } - - /* set parser to start */ - push(jc, MODE_DONE); - parse_buffer_clear(jc); - - return true; -} - -JSON_parser -new_JSON_parser(JSON_config const * config) -{ -/* - new_JSON_parser starts the checking process by constructing a JSON_parser - object. It takes a depth parameter that restricts the level of maximum - nesting. - - To continue the process, call JSON_parser_char for each character in the - JSON text, and then call JSON_parser_done to obtain the final result. - These functions are fully reentrant. -*/ - - int use_std_malloc = false; - JSON_config default_config; - JSON_parser jc; - JSON_malloc_t alloc; - - /* set to default configuration if none was provided */ - if (NULL == config) { - /* initialize configuration */ - init_JSON_config(&default_config); - config = &default_config; - } - - /* use std malloc if either the allocator or deallocator function isn't set */ - use_std_malloc = NULL == config->malloc || NULL == config->free; - - alloc = use_std_malloc ? malloc : config->malloc; - - jc = (JSON_parser)JSON_parser_malloc(alloc, sizeof(*jc), "parser"); - - if (NULL == jc) { - return NULL; - } - - /* configure the parser */ - memset(jc, 0, sizeof(*jc)); - jc->malloc = alloc; - jc->free = use_std_malloc ? free : config->free; - jc->callback = config->callback; - jc->ctx = config->callback_ctx; - jc->allow_comments = (signed char)(config->allow_comments != 0); - jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0); - jc->decimal_point = *localeconv()->decimal_point; - /* We need to be able to push at least one object */ - jc->depth = config->depth == 0 ? 1 : config->depth; - - /* reset the parser */ - if (!JSON_parser_reset(jc)) { - jc->free(jc); - return NULL; - } - - return jc; -} - -static int parse_buffer_grow(JSON_parser jc) -{ - const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]); - const size_t new_capacity = jc->parse_buffer_capacity * 2; - const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]); - void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer"); - - if (mem == NULL) { - jc->error = JSON_E_OUT_OF_MEMORY; - return false; - } - - assert(new_capacity > 0); - memcpy(mem, jc->parse_buffer, bytes_to_copy); - - if (jc->parse_buffer != &jc->static_parse_buffer[0]) { - jc->free(jc->parse_buffer); - } - - jc->parse_buffer = (char*)mem; - jc->parse_buffer_capacity = new_capacity; - - return true; -} - -static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars) -{ - while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) { - if (!parse_buffer_grow(jc)) { - assert(jc->error == JSON_E_OUT_OF_MEMORY); - return false; - } - } - - return true; -} - -#define parse_buffer_has_space_for(jc, count) \ - (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity) - -#define parse_buffer_push_back_char(jc, c)\ - do {\ - assert(parse_buffer_has_space_for(jc, 1)); \ - jc->parse_buffer[jc->parse_buffer_count++] = c;\ - jc->parse_buffer[jc->parse_buffer_count] = 0;\ - } while (0) - -#define assert_is_non_container_type(jc) \ - assert( \ - jc->type == JSON_T_NULL || \ - jc->type == JSON_T_FALSE || \ - jc->type == JSON_T_TRUE || \ - jc->type == JSON_T_FLOAT || \ - jc->type == JSON_T_INTEGER || \ - jc->type == JSON_T_STRING) - - -static int parse_parse_buffer(JSON_parser jc) -{ - if (jc->callback) { - JSON_value value, *arg = NULL; - - if (jc->type != JSON_T_NONE) { - assert_is_non_container_type(jc); - - switch(jc->type) { - case JSON_T_FLOAT: - arg = &value; - if (jc->handle_floats_manually) { - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - } else { - /* not checking with end pointer b/c there may be trailing ws */ - value.vu.float_value = strtod(jc->parse_buffer, NULL); - } - break; - case JSON_T_INTEGER: - arg = &value; - sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value); - break; - case JSON_T_STRING: - arg = &value; - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - break; - } - - if (!(*jc->callback)(jc->ctx, jc->type, arg)) { - return false; - } - } - } - - parse_buffer_clear(jc); - - return true; -} - -#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800) -#define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00) -#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000) -static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 }; - -static int decode_unicode_char(JSON_parser jc) -{ - int i; - unsigned uc = 0; - char* p; - int trail_bytes; - - assert(jc->parse_buffer_count >= 6); - - p = &jc->parse_buffer[jc->parse_buffer_count - 4]; - - for (i = 12; i >= 0; i -= 4, ++p) { - unsigned x = *p; - - if (x >= 'a') { - x -= ('a' - 10); - } else if (x >= 'A') { - x -= ('A' - 10); - } else { - x &= ~0x30u; - } - - assert(x < 16); - - uc |= x << i; - } - - /* clear UTF-16 char from buffer */ - jc->parse_buffer_count -= 6; - jc->parse_buffer[jc->parse_buffer_count] = 0; - - if (uc == 0xffff || uc == 0xfffe) { - return false; - } - - /* attempt decoding ... */ - if (jc->utf16_high_surrogate) { - if (IS_LOW_SURROGATE(uc)) { - uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc); - trail_bytes = 3; - jc->utf16_high_surrogate = 0; - } else { - /* high surrogate without a following low surrogate */ - return false; - } - } else { - if (uc < 0x80) { - trail_bytes = 0; - } else if (uc < 0x800) { - trail_bytes = 1; - } else if (IS_HIGH_SURROGATE(uc)) { - /* save the high surrogate and wait for the low surrogate */ - jc->utf16_high_surrogate = (UTF16)uc; - return true; - } else if (IS_LOW_SURROGATE(uc)) { - /* low surrogate without a preceding high surrogate */ - return false; - } else { - trail_bytes = 2; - } - } - - jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]); - - for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) { - jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80); - } - - jc->parse_buffer[jc->parse_buffer_count] = 0; - - return true; -} - -static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char) -{ - assert(parse_buffer_has_space_for(jc, 1)); - - jc->escaped = 0; - /* remove the backslash */ - parse_buffer_pop_back_char(jc); - switch(next_char) { - case 'b': - parse_buffer_push_back_char(jc, '\b'); - break; - case 'f': - parse_buffer_push_back_char(jc, '\f'); - break; - case 'n': - parse_buffer_push_back_char(jc, '\n'); - break; - case 'r': - parse_buffer_push_back_char(jc, '\r'); - break; - case 't': - parse_buffer_push_back_char(jc, '\t'); - break; - case '"': - parse_buffer_push_back_char(jc, '"'); - break; - case '\\': - parse_buffer_push_back_char(jc, '\\'); - break; - case '/': - parse_buffer_push_back_char(jc, '/'); - break; - case 'u': - parse_buffer_push_back_char(jc, '\\'); - parse_buffer_push_back_char(jc, 'u'); - break; - default: - return false; - } - - return true; -} - -static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class) -{ - if (!parse_buffer_reserve_for(jc, 1)) { - assert(JSON_E_OUT_OF_MEMORY == jc->error); - return false; - } - - if (jc->escaped) { - if (!add_escaped_char_to_parse_buffer(jc, next_char)) { - jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE; - return false; - } - } else if (!jc->comment) { - if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) { - parse_buffer_push_back_char(jc, (char)next_char); - } - } - - return true; -} - -#define assert_type_isnt_string_null_or_bool(jc) \ - assert(jc->type != JSON_T_FALSE); \ - assert(jc->type != JSON_T_TRUE); \ - assert(jc->type != JSON_T_NULL); \ - assert(jc->type != JSON_T_STRING) - - -int -JSON_parser_char(JSON_parser jc, int next_char) -{ -/* - After calling new_JSON_parser, call this function for each character (or - partial character) in your JSON text. It can accept UTF-8, UTF-16, or - UTF-32. It returns true if things are looking ok so far. If it rejects the - text, it returns false. -*/ - int next_class, next_state; - -/* - Store the current char for error handling -*/ - jc->current_char = next_char; - -/* - Determine the character's class. -*/ - if (next_char < 0) { - jc->error = JSON_E_INVALID_CHAR; - return false; - } - if (next_char >= 128) { - next_class = C_ETC; - } else { - next_class = ascii_class[next_char]; - if (next_class <= XX) { - set_error(jc); - return false; - } - } - - if (!add_char_to_parse_buffer(jc, next_char, next_class)) { - return false; - } - -/* - Get the next state from the state transition table. -*/ - next_state = state_transition_table[jc->state][next_class]; - if (next_state >= 0) { -/* - Change the state. -*/ - jc->state = (signed char)next_state; - } else { -/* - Or perform one of the actions. -*/ - switch (next_state) { -/* Unicode character */ - case UC: - if(!decode_unicode_char(jc)) { - jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; - return false; - } - /* check if we need to read a second UTF-16 char */ - if (jc->utf16_high_surrogate) { - jc->state = D1; - } else { - jc->state = ST; - } - break; -/* escaped char */ - case EX: - jc->escaped = 1; - jc->state = ESC; - break; -/* integer detected by minus */ - case MX: - jc->type = JSON_T_INTEGER; - jc->state = MI; - break; -/* integer detected by zero */ - case ZX: - jc->type = JSON_T_INTEGER; - jc->state = ZE; - break; -/* integer detected by 1-9 */ - case IX: - jc->type = JSON_T_INTEGER; - jc->state = IT; - break; - -/* floating point number detected by exponent*/ - case DE: - assert_type_isnt_string_null_or_bool(jc); - jc->type = JSON_T_FLOAT; - jc->state = E1; - break; - -/* floating point number detected by fraction */ - case DF: - assert_type_isnt_string_null_or_bool(jc); - if (!jc->handle_floats_manually) { -/* - Some versions of strtod (which underlies sscanf) don't support converting - C-locale formated floating point values. -*/ - assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.'); - jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point; - } - jc->type = JSON_T_FLOAT; - jc->state = FX; - break; -/* string begin " */ - case SB: - parse_buffer_clear(jc); - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_STRING; - jc->state = ST; - break; - -/* n */ - case NU: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_NULL; - jc->state = N1; - break; -/* f */ - case FA: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_FALSE; - jc->state = F1; - break; -/* t */ - case TR: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_TRUE; - jc->state = T1; - break; - -/* closing comment */ - case CE: - jc->comment = 0; - assert(jc->parse_buffer_count == 0); - assert(jc->type == JSON_T_NONE); - jc->state = jc->before_comment_state; - break; - -/* opening comment */ - case CB: - if (!jc->allow_comments) { - return false; - } - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - assert(jc->parse_buffer_count == 0); - assert(jc->type != JSON_T_STRING); - switch (jc->stack[jc->top]) { - case MODE_ARRAY: - case MODE_OBJECT: - switch(jc->state) { - case VA: - case AR: - jc->before_comment_state = jc->state; - break; - default: - jc->before_comment_state = OK; - break; - } - break; - default: - jc->before_comment_state = jc->state; - break; - } - jc->type = JSON_T_NONE; - jc->state = C1; - jc->comment = 1; - break; -/* empty } */ - case -9: - parse_buffer_clear(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { - return false; - } - if (!pop(jc, MODE_KEY)) { - return false; - } - jc->state = OK; - break; - -/* } */ case -8: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { - return false; - } - if (!pop(jc, MODE_OBJECT)) { - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; - } - jc->type = JSON_T_NONE; - jc->state = OK; - break; - -/* ] */ case -7: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) { - return false; - } - if (!pop(jc, MODE_ARRAY)) { - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; - } - - jc->type = JSON_T_NONE; - jc->state = OK; - break; - -/* { */ case -6: - parse_buffer_pop_back_char(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) { - return false; - } - if (!push(jc, MODE_KEY)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = OB; - break; - -/* [ */ case -5: - parse_buffer_pop_back_char(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) { - return false; - } - if (!push(jc, MODE_ARRAY)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = AR; - break; - -/* string end " */ case -4: - parse_buffer_pop_back_char(jc); - switch (jc->stack[jc->top]) { - case MODE_KEY: - assert(jc->type == JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = CO; - - if (jc->callback) { - JSON_value value; - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) { - return false; - } - } - parse_buffer_clear(jc); - break; - case MODE_ARRAY: - case MODE_OBJECT: - assert(jc->type == JSON_T_STRING); - if (!parse_parse_buffer(jc)) { - return false; - } - jc->type = JSON_T_NONE; - jc->state = OK; - break; - default: - return false; - } - break; - -/* , */ case -3: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - switch (jc->stack[jc->top]) { - case MODE_OBJECT: -/* - A comma causes a flip from object mode to key mode. -*/ - if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) { - return false; - } - assert(jc->type != JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = KE; - break; - case MODE_ARRAY: - assert(jc->type != JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = VA; - break; - default: - return false; - } - break; - -/* : */ case -2: -/* - A colon causes a flip from key mode to object mode. -*/ - parse_buffer_pop_back_char(jc); - if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = VA; - break; -/* - Bad action. -*/ - default: - set_error(jc); - return false; - } - } - return true; -} - -int -JSON_parser_done(JSON_parser jc) -{ - if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE)) - { - return true; - } - - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; -} - - -int JSON_parser_is_legal_white_space_string(const char* s) -{ - int c, char_class; - - if (s == NULL) { - return false; - } - - for (; *s; ++s) { - c = *s; - - if (c < 0 || c >= 128) { - return false; - } - - char_class = ascii_class[c]; - - if (char_class != C_SPACE && char_class != C_WHITE) { - return false; - } - } - - return true; -} - -int JSON_parser_get_last_error(JSON_parser jc) -{ - return jc->error; -} - - -void init_JSON_config(JSON_config* config) -{ - if (config) { - memset(config, 0, sizeof(*config)); - - config->depth = JSON_PARSER_STACK_SIZE - 1; - config->malloc = malloc; - config->free = free; - } -} - -#undef XX -#undef COUNTOF -#undef parse_buffer_clear -#undef parse_buffer_pop_back_char -/* end of file JSON_parser/JSON_parser.c */ -/* end of file /home/stephan/fossil/cwal/cwal_amalgamation.c */ -/* start of file s2.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include -#include -#include /* memcmp() */ -#include /* FILE, fprintf() */ -#include - -#define S2_USE_SIGNALS S2_HAVE_SIGACTION - -#if S2_USE_SIGNALS -#include /* sigaction(), if our feature macros are set right */ -#endif - -#if 1 -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:\t",__FILE__,__LINE__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -const s2_stoken s2_stoken_empty = s2_stoken_empty_m; -#if 0 -const s2_op s2_op_empty = s2_op_empty_m; -#endif -const s2_engine s2_engine_empty = s2_engine_empty_m; -const s2_stoken_stack s2_stoken_stack_empty = s2_stoken_stack_empty_m; -const s2_estack s2_estack_empty = s2_estack_empty_m; -const s2_sweep_guard s2_sweep_guard_empty = s2_sweep_guard_empty_m; -const s2_scope s2_scope_empty = s2_scope_empty_m; -const s2_enum_builder s2_enum_builder_empty = { -0/*se*/, -0/*flags*/, -0/*entryCount*/, -0/*entries*/ -}; -const s2_kvp_each_state s2_kvp_each_state_empty = { -0/*e*/, 0/*self*/, 0/*callback*/, 0/*valueArgFirst*/ -}; - -#if S2_USE_SIGNALS -static s2_engine * s2Interruptable = 0; -#endif - -s2_engine * s2_engine_alloc( cwal_engine * e ){ - s2_engine * rc = (s2_engine*)cwal_malloc(e, sizeof(s2_engine)); - if(rc){ - *rc = s2_engine_empty; - rc->allocStamp = e; - rc->e = e /* need this for dealloc */; - } - return rc; -} - -void s2_estack_clear( s2_engine * se, s2_estack * st, char allowRecycle ){ - s2_stoken_stack_clear( se, &st->vals, allowRecycle ); - s2_stoken_stack_clear( se, &st->ops, allowRecycle ); -} - -void s2_estack_swap( s2_estack * lhs, s2_estack * rhs ){ - s2_estack const tmp = *lhs; - *lhs = *rhs; - *rhs = tmp; -} - -void s2_engine_stack_swap( s2_engine * se, s2_estack * st ){ - s2_estack const tmp = se->st; - se->st = *st; - *st = tmp; -} - -void s2_engine_reset_stack( s2_engine * se ){ - s2_estack_clear(se, &se->st, 1); -} - - -void s2_engine_free_recycled_funcs( s2_engine * se ) /* in s2_eval.c */; -void s2_modules_close( s2_engine * ) /* in s2_mod.c */; - -/** - Frees up all data stored in se which refers to cwal_values. It - must only be called immediately before the top-most stack is - popped. Cleaned-up data includes any s2_ob()-related buffers. -*/ -static void s2_engine_cleanup_values(s2_engine * se){ - assert(se->e); - assert(!se->scopes.current); - assert(cwal_scope_current_get(se->e) == &se->scopes.topScope); - if(se->stash){ - cwal_value_unref( se->stash ); - se->stash = 0; - } - memset(&se->cache, 0, sizeof(se->cache)) - /* se->cache.keyXXX all point back into se->stash */; - if(se->funcStash){ - cwal_value_unref( cwal_hash_value(se->funcStash) ); - se->funcStash = 0; - } - cwal_exception_set( se->e, 0 ); - s2_propagating_set( se, 0 ); - while(se->ob.count) s2_ob_pop(se); - cwal_list_reserve(se->e, &se->ob, 0); -} - -void s2_engine_finalize( s2_engine * se ){ - cwal_engine * e; - void const * allocStamp; - if(!se) return; -#if S2_USE_SIGNALS - if(s2Interruptable==se) s2Interruptable = 0; -#endif - allocStamp = se->allocStamp; - e = se->e; - if(!e){ - assert(!se->st.vals.top); - assert(!se->recycler.stok.top); - assert(!se->st.ops.top); - assert(!se->buffer.mem); - assert(!se->stash); - assert(!se->dotOp.lhs); - *se = s2_engine_empty; - }else{ - s2_ukwd_free(se); - while( se->scopes.level ){ - /* reminder: se took over cwal's top-most scope during init. */ - cwal_scope_pop(se->e); - } - assert(!se->e->current); - assert(!se->scopes.list && "Should have been cleaned up via pop hook."); - assert(!se->scopes.current && "Should have been cleaned up via pop hook."); - s2_modules_close(se) - /* must come after all scopes are gone because the modules might - have introduced memory which the scopes reference via native - values or function bindings. */; - - /* - Reminder: the following cleanup is only legal as long as we - don't use/deref any Values... - - e.g if the token stack is changed to ref its values, we need to - moved its cleanup higher up. - */ - s2_engine_free_recycled_funcs(se); - s2_engine_reset_stack(se); - s2_stoken_stack_clear( se, &se->recycler.stok, 0 ); - cwal_buffer_clear(e, &se->buffer); - *se = s2_engine_empty; - if(allocStamp==e) cwal_free(e, se); - cwal_engine_destroy( e ); - } -} - - -/* In s2_eval.c */ -int s2_callback_hook_pre(cwal_callback_args const * argv, void * state); -/* In s2_eval.c */ -int s2_callback_hook_post(cwal_callback_args const * argv, void * state, int fRc, cwal_value * rv); -/* cwal_callback_f() pre- and post-call() hooks. This is where we install - 'argv' and 'this'. -*/ -static const cwal_callback_hook cwal_callback_hook_s2 = { - NULL /*state*/, - s2_callback_hook_pre, - s2_callback_hook_post -}; - -static char const * s2_type_name_proxy( cwal_value const * v, - cwal_size_t * len ){ - cwal_value const * tn = cwal_prop_get(v, "__typename", 10); - return tn ? cwal_value_get_cstr(tn, len) : NULL; -} - -static void s2_engine_subexpr_save2(s2_engine * e, s2_subexpr_savestate * to, - char resetIt){ - to->ternaryLevel = e->ternaryLevel; - if(resetIt) e->ternaryLevel = 0; -} - -void s2_engine_subexpr_save(s2_engine * e, s2_subexpr_savestate * to){ - s2_engine_subexpr_save2(e, to, 1); -} - -#define s2__scope_for_level(SE,LVL) \ - (((LVL) && (LVL)<=(SE)->scopes.level) \ - ? ((SE)->scopes.list + (LVL) - 1) \ - : (s2_scope*)NULL) - -s2_scope * s2_scope_for_level( s2_engine const * se, - cwal_size_t level ){ - assert(se); - return s2__scope_for_level(se, level); -} - -#define s2__scope_current(SE) \ - (((SE) && (SE)->e && (SE)->e->current) \ - ? s2__scope_for_level((SE), (SE)->e->current->level) \ - : 0) - -s2_scope * s2_scope_current( s2_engine const * se ){ - return s2__scope_current(se); -} - - - -/** - Ensures that se->scopes.list has at least newDepth scopes in - reserve. Returns 0 on success, CWAL_RC_OOM on allocation error. If - newDepth is 0 then it frees se->scopes.list, but that may only - be used during s2_engine cleanup (else an assert() will fail). -*/ -static int s2_reserve_scopes( s2_engine * se, cwal_size_t newDepth ){ - if(0==newDepth){ - assert(!se->scopes.current && "Else internal mismanagement of s2_engine::scopes."); - cwal_realloc(se->e, se->scopes.list, 0); - se->scopes.list = 0; - se->scopes.alloced = 0; - } - else if(se->scopes.alloced < newDepth){ - s2_scope * sc; - cwal_size_t newCount = se->scopes.alloced - ? (se->scopes.alloced * 8 / 5) : 16; - assert(newCount > newDepth); - sc = (s2_scope *)cwal_realloc( se->e, se->scopes.list, - (cwal_size_t)(newCount * sizeof(s2_scope)) ); - if(!sc) return CWAL_RC_OOM; - ++se->metrics.totalScopeAllocs; - se->scopes.list = sc; - se->scopes.alloced = newCount; - } - return 0; -} - -/** - cwal_engine_vtab::hook::scope_push() hook. state must be a - (s2_engine*). This function syncronizes the cwal scope stack with - our s2_scope stack. The only error condition is a potential - CWAL_RC_OOM if reserving a block of s2_scope entries fails. It - keeps all scopes in a single array, which has a maximum length - directly related to (but not identical to) the highest-ever scope - depth and is expanded as needed (but never shrinks until the engine - is finalized). -*/ -static int s2_scope_hook_push( cwal_scope * s, void * state ){ - s2_engine * se = (s2_engine*)state; - s2_scope * s2sc; - int rc; - assert(0 < s->level); - assert(se); - assert(s2_engine_from_state(s->e) == se); - rc = s2_reserve_scopes(se, s->level); - if(rc) return rc; - /*MARKER(("Pushing scope level %d (%d reserved)\n", (int)s->level, - (int)se->scopes.alloced));*/ - se->scopes.level = s->level; - s2sc = s2__scope_for_level(se, s->level); - *s2sc = s2_scope_empty; - s2sc->cwalScope = s; - s2sc->flags = se->scopes.nextFlags; - s2_engine_subexpr_save2(se, &s2sc->saved, - (S2_SCOPE_F_KEEP_TERNARY_LEVEL - & se->scopes.nextFlags) - ? 0 : 1); - se->scopes.nextFlags = 0; - se->scopes.current = s2sc; - ++se->metrics.totalScopesPushed; - if(s->level > se->metrics.maxScopeDepth){ - se->metrics.maxScopeDepth = (int)s->level; - } - return rc; -} - -/** - cwal_engine_vtab::hook::scope_pop() hook. state must be - a (s2_engine*). -*/ -static void s2_scope_hook_pop( cwal_scope const * s, void * state ){ - s2_engine * se = (s2_engine*)state; - s2_scope * s2sc; - /*MARKER(("Popping scope level %d\n", (int)s->level));*/ - assert(se); - assert(0 < s->level); - assert(s2_engine_from_state(s->e) == se); - assert(se->scopes.alloced >= s->level); - s2sc = s2__scope_for_level(se, s->level); - assert(s2sc); - assert(s == s2sc->cwalScope); - se->scopes.current = s2__scope_for_level(se, s->level-1); - se->scopes.level = s->level-1; - assert(s->level>1 - ? (s->level == 1 + se->scopes.current->cwalScope->level) - : !se->scopes.current); - s2_dotop_state(se, 0, 0, 0); - if(s2sc->evalHolder){ - cwal_value * av = cwal_array_value(s2sc->evalHolder); - s2sc->evalHolder = 0; - assert(1 == cwal_value_refcount(av)); - assert(s2sc->cwalScope == cwal_value_scope(av)); - cwal_value_unref(av); - } - s2_engine_subexpr_restore(se, &s2sc->saved); - *s2sc = s2_scope_empty; - if(1==s->level){ - /* Final scope is popping. Let's clean se->scopes.list. */ - /*MARKER(("pop hook: the engine is shutting down. Freeing %d s2_scopes.\n", - (int)se->scopes.alloced));*/ - assert(!se->scopes.current); - s2_engine_cleanup_values(se); - s2_reserve_scopes(se, 0); - } -} - -static char const * cwal_rc_cstr_f_s2(int rc){ - switch((enum s2_rc_e)rc){ -#define CASE(X) case X: return #X - CASE(S2_RC_placeholder); - CASE(S2_RC_END_EACH_ITERATION); - CASE(S2_RC_TOSS); - CASE(S2_RC_end); - CASE(S2_RC_CLIENT_BEGIN); -#undef CASE - } - return 0; -} - -void s2_static_init(void){ - static int rcFallback = 0; - if(!rcFallback && 1==++rcFallback){ - /* yes, there's a small race condition here, but it's not tragic - except in a 1-in-a-bazillion case where X instances happen to - successfully race here, where X is the static limit of - cwal_rc_cstr_f fallbacks. */ - cwal_rc_cstr_fallback(cwal_rc_cstr_f_s2); - } -} - -int s2_engine_init( s2_engine * se, cwal_engine * e ){ - void const * allocStamp; - int rc = 0; - s2_static_init(); - if(!se || !e) return CWAL_RC_MISUSE; - assert(S2_RC_CLIENT_BEGIN == CWAL_RC_CLIENT_BEGIN + 2000); - assert(5000 == S2_RC_CLIENT_BEGIN); - allocStamp = se->allocStamp; - assert(!se->e || (se->e == e)); - *se = s2_engine_empty; - se->allocStamp = allocStamp; - se->e = e; - - { - cwal_callback_hook hook = cwal_callback_hook_s2; - hook.state = se; - cwal_callback_hook_set(e, &hook); - cwal_engine_type_name_proxy( e, s2_type_name_proxy ); - } - - /* - What follows assumes that the client has created no cwal_values - [of interest] before this routine was called. cwal necessarily - pushes a scope during its setup, but we need to pop that scope - and start a new top scope so that the s2/cwal scope levels - stay in sync. - */ - assert(se->e->current); - assert(se->e->current==&se->e->topScope); - - rc = cwal_engine_client_state_set( e, se, - &s2_engine_empty, 0 ) - /* Required by the s2 function callback hook mechanism. */; - assert(!rc && "Can only fail if client state was already set!"); - - /* Clear all cwal-level scopes and start with a new scope - stack, so that we can hook up our s2_scopes. */ - if( (rc = cwal_scope_pop(se->e)) ) return rc; - assert(!se->e->current); - assert(!e->vtab->hook.scope_push); - assert(!e->vtab->hook.scope_pop); - e->vtab->hook.scope_push = s2_scope_hook_push; - e->vtab->hook.scope_pop = s2_scope_hook_pop; - e->vtab->hook.scope_state = se; - - /** - cwal always needs at least 1 scope active. Now that we have our - scope hooks in place, pop a new top-level scope. - */ - { - cwal_scope * cs = &se->scopes.topScope; - if( (rc = cwal_scope_push(se->e, &cs) ) ) return rc; - assert(se->e->current); - assert(1==se->e->current->level); - assert(se->e->current==&se->scopes.topScope); - assert(se->e==se->scopes.topScope.e); - assert(se->scopes.alloced >= 10 && "But we start with at least 10 scopes?"); - } - - /* We need some strings as keys in a few places, so put a copy in - the stash. - - Reminder: it/they're owned by the stash. - */ -#define STASHVAL(MEMBER,VAL) \ - if(!rc) do{ \ - if(!(se->cache.MEMBER = VAL)) rc = CWAL_RC_OOM; \ - else{ \ - cwal_value_ref(se->cache.MEMBER); \ - rc = s2_stash_set_v( se, se->cache.MEMBER, se->cache.MEMBER ); \ - cwal_value_unref(se->cache.MEMBER); \ - if(rc){ \ - se->cache.MEMBER = 0; \ - } \ - } \ - } while(0) -#define STASHKEY(MEMBER,STR) \ - STASHVAL(MEMBER,cwal_new_string_value(e, (STR), (cwal_size_t)(sizeof(STR)-1))) - - STASHKEY(keyPrototype,"prototype"); - STASHKEY(keyThis,"this"); - STASHKEY(keyArgv,"argv"); - STASHKEY(keyValue,"value"); - STASHKEY(keyName,"name"); - STASHKEY(keyTypename,"__typename"); - STASHKEY(keyScript,"script"); - STASHKEY(keyLine,"line"); - STASHKEY(keyColumn,"column"); - STASHKEY(keyStackTrace,"stackTrace"); - STASHKEY(keyCtorNew,"__new"); - STASHKEY(keyImportFlag,"doPathSearch"); -#if S2_TRY_INTERCEPTORS - STASHKEY(keyInterceptee,"interceptee"); -#endif -#undef STASHKEY -#undef STASHVAL - - return rc; -} - -/** - Enumeration of various scope-sweeping options. - */ -enum S2SweepModes { -SweepMode_VacuumRecursive = -2, -SweepMode_SweepRecursive = -1, -SweepMode_Default = 0, -SweepMode_Sweep = 1, -SweepMode_Vacuum = 2 -}; -/** - Might or might not trigger a sweep or vacuum, depending partially - on the initialBroomMode _hint_ and various internal state. -*/ -static int s2_engine_sweep_impl( s2_engine * se, enum S2SweepModes initialBroomMode ){ - int rc = 0; - s2_scope * sc = s2__scope_current(se); - assert(se && se->e); - assert(sc); - /*MARKER(("SWEEP RUN #%d? sweepTick=%d, s-guard=%d, v-guard=%d\n", - se->sweepTotal, sc->sguard.sweepTick, - sc->sguard.sweep, sc->sguard.vacuum));*/ - if(!sc){ - assert(!"Someone called s2_engine_sweep() without cwal_scope_push()."); - return CWAL_RC_MISUSE; - } -#if defined(DEBUG) - switch(initialBroomMode){ - case SweepMode_Default: - case SweepMode_Sweep: - case SweepMode_Vacuum: - case SweepMode_VacuumRecursive: - case SweepMode_SweepRecursive: - break; - default: - assert(!"Invalid sweep mode!"); - return CWAL_RC_MISUSE; - } -#endif - if(sc->sguard.sweep>0 || se->sweepInterval<=0) return 0; - else if(SweepMode_Default!=initialBroomMode - && (++sc->sguard.sweepTick != se->sweepInterval)) return 0; - else{ - enum S2SweepModes sweepMode = - (SweepMode_Default==initialBroomMode) - ? SweepMode_Sweep : initialBroomMode; - int valsSwept = 0; - ++se->sweepTotal; - sc->sguard.sweepTick = 0; - /* See if we can/should use sweep2 or vacuum... */ - if(SweepMode_Default==initialBroomMode - && se->vacuumInterval>0 - && !sc->sguard.vacuum - && (0 == (se->sweepTotal % se->vacuumInterval)) - ){ - sweepMode = SweepMode_Vacuum; - } - /* MARKER(("SWEEP RUN #%d mode=%d\n", se->sweepTotal, sweepMode)); */ - switch(sweepMode){ - default: - assert(!"Invalid sweep mode!"); - return CWAL_RC_MISUSE; - case SweepMode_Default: - case SweepMode_Sweep: - valsSwept = (int)cwal_engine_sweep(se->e); - break; - case SweepMode_SweepRecursive: -#if 0 - assert(!"NO!"); /* this cannot work with current code */ -#else - /* 20181126: this actually works, at least in the s2 unit - test scripts. It's not terribly interesting in terms of - cleaning up, though - a recursive vacuum is really the - Holy Grail of cleanup. */ -#endif - /*MARKER(("SWEEP RUN #%d mode=%d\n", se->sweepTotal, sweepMode));*/ - valsSwept = (int)cwal_engine_sweep2(se->e, 1); - break; - case SweepMode_Vacuum: - assert(SweepMode_Vacuum==sweepMode); - rc = cwal_engine_vacuum(se->e, se->flags.traceSweeps - ? &valsSwept : 0 - /* b/c this reporting costs */); - assert(!rc && "Vacuum \"cannot fail\"."); - break; - case SweepMode_VacuumRecursive:{ - /** - 20181126: nope, recursive vacuum still doesn't quite - work. It pulls being-eval'd values out from under various - pending expressions. e.g. the 'using' part of a function - definition can trigger it, as can loop constructs. - - [Later that day:] If we guard a scope against vacuuming - based on both s->sguard.vacuum and s->sguard.sweep, - recursive vacuum actually works (meaning it doesn't crash - us), but (A) in the unit test suite it's not cleaning up - any more than non-recursive and (B) it's slow, and would - need to be called infrequently. A recursive vacuum would - only hypothetically be useful for catching pathological - use cases such as this contrived bit of code: - - // Create a pathologically cyclic structure: - var a = [1,2,3], o = {a}; - o.a[] = o; o.a[] = a; - o[o] = o; o[a] = a; - scope { - print(__FLC, 'nulling...'); - o = a = null; - print(__FLC, 'nulled'); - for( var i = 0; i < 20; ++i ){print(__FLC, i)} - print(__FLC,"scope closing"); - } - print(__FLC,"scope closed"); - ; ; ; ; ; ; - print(__FLC,"done"); - - As long as we're in that inner scope (or lower), neither - 'a' nor 'o' can be cleaned up without a recursive vacuum - because they're cyclic. Even if we try to recursively - vacuum, we can only clean them if the sweep/vacuum guards - do not prohibit it the vacuum. (The guards are there to - protect being-eval'd stuff, and the main eval impl always - (IIRC) sweep-guards the currently-evaluating expression.) - i.e. chances are good(?) that we wouldn't be able to vacuum - the scope even if we wanted to. - - A quick test of exactly that case shows that the scope - which owns 'a' and 'o' is indeed protected (because the - 'scope' keyword is part of a pending expression, and - therefore causes that scope to sweep-guard) until the - 'scope' completes. - - Recursive vacuum is arbitrarily expensive and is rarely - useful, so there's really no reason to enable it by - default, but it might be interesting to add a "mega-gc" - function or keyword which forces a recursive vacuum, with - the caveat that that the sguard can block it from - happening. If we bypass the sguard, we eventually _will_ - clean up stuff we definitely don't want to clean up. - */ - s2_scope * s = s2__scope_current(se); - for( ; !rc && s ; s = s2__scope_for_level(se, s->cwalScope->level-1) ){ - if(!s->sguard.vacuum && !s->sguard.sweep){ - int count = 0; - MARKER(("Attempting recursive vaccum on scope level %d...\n", - (int)s->cwalScope->level)); - rc = cwal_scope_vacuum(s->cwalScope, se->flags.traceSweeps - ? &count : 0 - /* b/c this reporting costs */); - valsSwept += count; - }else{ - MARKER(("Skipping recursive vaccum on scope level %d due to sguard.\n", - (int)s->cwalScope->level)); - } - } - assert(!rc && "Vacuum \"cannot fail\"."); - break; - } - } - if(se->flags.traceSweeps>2 - || (se->flags.traceSweeps && valsSwept)){ - char const * label = "???"; - s2_ptoker const * const pt = se->currentScript; - s2_ptoken const * tok = pt ? &pt->token : 0; - char const * tokEnd = tok ? s2_ptoken_end(tok) : 0; - const char * ptEnd = pt ? s2_ptoker_end(pt) : 0; - switch(sweepMode){ - case SweepMode_Vacuum: label = "vacuum"; break; - case SweepMode_VacuumRecursive: label = "recursive vacuum"; break; - case SweepMode_Sweep: label = "sweep"; break; - case SweepMode_SweepRecursive: label = "recursive sweep"; break; - default: break; - } - if(se->flags.traceSweeps>1 && valsSwept - && tokEnd - && tokEnd>=ptEnd - && tokEnd-1" /* trigraph! */, - line, col)); - }else{ - MARKER(("Swept up %d value(s) in %s mode\n", valsSwept, label)); - } - } - return rc; - } -} - -int s2_engine_sweep( s2_engine * se ){ - return s2_engine_sweep_impl(se, SweepMode_Default); -} - -int s2_cb_internal_experiment( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - assert(se); - se->flags.traceSweeps += 3; - rc = s2_engine_sweep_impl( se, SweepMode_VacuumRecursive ); - se->flags.traceSweeps -= 3; - *rv = cwal_value_undefined(); - return rc; -} - -void s2_engine_vacuum( s2_engine * se ){ - s2_scope * sc = s2__scope_current(se); - assert(se && se->e); - assert(sc); - if(!sc->sguard.sweep && !sc->sguard.vacuum){ -#ifdef DEBUG - int vacCount = 0; - cwal_engine_vacuum(se->e, &vacCount); - assert(vacCount >= 0); -#else - cwal_engine_vacuum(se->e, NULL); -#endif - } -} - -void s2_stoken_stack_push( s2_stoken_stack * ts, s2_stoken * t ){ - assert(ts); - assert(t); - assert(!t->next); - assert(t != ts->top); - t->next = ts->top; - ts->top = t; - ++ts->size; -} - -s2_stoken * s2_stoken_stack_pop( s2_stoken_stack * ts ){ - s2_stoken * t = 0; - assert(ts); - if(ts->size>0){ - t = ts->top; - assert(t); - ts->top = t->next; - t->next = 0; - --ts->size; - } - return t; -} - -void s2_stoken_stack_clear( s2_engine * se, s2_stoken_stack * st, char allowRecycle ){ - s2_stoken * t; - while( (t = s2_stoken_stack_pop(st)) ){ - s2_stoken_free(se, t, allowRecycle); - } -} - -static void s2_engine_trace_stack(s2_engine * se, s2_stoken const * t, char isPush){ - int const ttype = t->ttype; - s2_op const * op = s2_ttype_op(ttype); - assert(se->flags.traceTokenStack); - cwal_outputf(se->e, "s2_engine::traceTokenStack: after %s ", - isPush ? "push" : "pop"); - if(op){ - cwal_outputf(se->e, "op (%s) ", op->sym); - }else{ - cwal_outputf(se->e, "token %s (typename=%s) ", - s2_ttype_cstr(ttype), - t->value ? cwal_value_type_name(t->value) : ""); - } - cwal_outputf(se->e, "tokens=%d, ops=%d\n", - se->st.vals.size, se->st.ops.size); - -} - -#define s2__ttrace(isPush, TOK) if(se->flags.traceTokenStack) s2_engine_trace_stack(se, TOK, isPush) - -void s2_engine_push( s2_engine * se, s2_stoken * t ){ - s2_stoken_stack_push( s2_stoken_op(t) ? &se->st.ops : &se->st.vals, t ); - s2__ttrace(1, t); -} - -void s2_engine_push_valtok( s2_engine * se, s2_stoken * t ){ - s2_stoken_stack_push( &se->st.vals, t ); - s2__ttrace(1, t); -} - -void s2_engine_push_op( s2_engine * se, s2_stoken * t ){ - s2_stoken_stack_push( &se->st.ops, t ); - s2__ttrace(1, t); -} - -s2_stoken * s2_engine_push_ttype( s2_engine * se, int i ){ - s2_stoken * t = s2_stoken_alloc2( se, i, 0 ); - if(t) s2_engine_push( se, t ); - return t; -} - -s2_stoken * s2_engine_push_val( s2_engine * se, cwal_value * v ){ - if(!se || !v) return 0; - else{ - s2_stoken * t = s2_stoken_alloc2( se, S2_T_Value, v ); - if(t) s2_engine_push_valtok( se, t ); - return t; - } -} - -s2_stoken * s2_engine_push_tv( s2_engine * se, int ttype, cwal_value * v ){ - s2_stoken * t = s2_stoken_alloc2( se, ttype, v ); - if(t) s2_engine_push_valtok( se, t ); - return t; -} - - -s2_stoken * s2_engine_push_int( s2_engine * se, cwal_int_t i ){ - s2_stoken * rc = 0; - cwal_value * v = cwal_new_integer(se->e, i); - if(!v) return 0; - else{ - if(!(rc = s2_engine_push_val(se, v))){ - cwal_value_unref(v); - } - } - return rc; -} - - -s2_stoken * s2_engine_peek_token( s2_engine * se ){ - return se->st.vals.top; -} - -cwal_value * s2_engine_peek_value( s2_engine * se ){ - return se->st.vals.top ? se->st.vals.top->value : 0; -} - -s2_stoken * s2_engine_peek_op( s2_engine * se ){ - return se->st.ops.top; -} - -static s2_stoken * s2_engine_pop_token_impl( s2_engine * se, - s2_stoken_stack * ts, - char returnItem ){ - s2_stoken * rc = s2_stoken_stack_pop(ts); - if(rc){ - s2__ttrace(0, rc); - if(!returnItem){ - s2_stoken_free( se, rc, 1 ); - rc = 0; - } - } - return rc; -} - -#undef s2__ttrace - -s2_stoken * s2_engine_pop_token( s2_engine * se, char returnItem ){ - return s2_engine_pop_token_impl(se, &se->st.vals, returnItem); -} - -s2_stoken * s2_engine_pop_op( s2_engine * se, char returnItem ){ - return s2_engine_pop_token_impl(se, &se->st.ops, returnItem); -} - -cwal_value * s2_engine_pop_value( s2_engine * se ){ - cwal_value * v = 0; - s2_stoken * t = s2_engine_pop_token(se, 1); - if(t){ - v = t->value; - t->value = 0; - s2_stoken_free(se, t, 1); - } - return v; -} - -#if 0 -void s2_engine_stack_replace( s2_engine * se, s2_estack const * src, - s2_estack * priorStacks ){ - if(priorStacks) *priorStacks = se->st; - else s2_engine_reset_stack(se); - se->st = src ? *src : s2_estack_empty; -} -#endif - -s2_stoken * s2_stoken_alloc( s2_engine * se ){ - s2_stoken * s; - assert(se); - assert(se->e); - ++se->metrics.tokenRequests; - s = s2_stoken_stack_pop(&se->recycler.stok); - if(!s){ - s = (s2_stoken*)cwal_malloc(se->e, sizeof(s2_stoken)); - if(s){ - ++se->metrics.tokenAllocs; - cwal_engine_adjust_client_mem(se->e, - (cwal_int_t)sizeof(s2_stoken)); - assert(se->e->metrics.clientMemCurrent>0); - } - } - if(s){ - *s = s2_stoken_empty; - if(++se->metrics.liveTokenCount > se->metrics.peakLiveTokenCount){ - se->metrics.peakLiveTokenCount = se->metrics.liveTokenCount; - } - } - return s; -} - -s2_stoken * s2_stoken_alloc2( s2_engine * se, int type, cwal_value * v ){ - s2_stoken * t = s2_stoken_alloc(se); - if(t){ - t->ttype = type; - t->value = v; - } - return t; -} - -void s2_stoken_free( s2_engine * se, s2_stoken * t, char allowRecycle ){ - /* - Reminder: t does not hold a reference to t->value, so we do not - let a reference go here. Any stray stack machine values - (e.g. those left over during error handling mid-expression) will - be cleaned up by the scope which is, more likely than not, about - to pop as a result of error propagation (or it's the global scope, - in which case it's free to sweep them up). - */ - assert(se); - assert(se->e); - assert(t); - assert(!t->next); - --se->metrics.liveTokenCount; - if(allowRecycle && (se->recycler.stok.size < se->recycler.maxSTokens)){ - s2_stoken_stack_push( &se->recycler.stok, t ); - }else{ - *t = s2_stoken_empty; - assert(se->e->metrics.clientMemCurrent>0); - cwal_engine_adjust_client_mem(se->e, -((cwal_int_t)sizeof(s2_stoken))); - cwal_free2( se->e, t, sizeof(s2_stoken) ); - } -} - -static int s2_process_op_impl( s2_engine * se, s2_op const * op, - char popOpStack ){ - int rc = se->flags.interrupted; - s2_stoken_stack * st = &se->st.vals; - int const oldStackSize = st->size; - int popArgCount = 0; - cwal_value * rv = 0; - s2_stoken * topOp = 0; - assert(op); - se->opErrPos = 0; - if(rc) return rc; - else if(se->flags.traceTokenStack){ - MARKER(("running operator %s (#%d) arity=%d assoc=%d prec=%d\n", - op->sym, op->id, op->arity, op->assoc, op->prec)); - } - /** - pop op (if needed) first so that we can guaranty operators that - they are not the top op on the stack when they are called. Useful - for '=', which will want to know if the LHS is a dot operator or - not. - */ - if(popOpStack){ - topOp = s2_engine_pop_op(se, 1); - } - if(!op->call){ - rc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED, - "Operator %s does not have an internal " - "call() impl.", - op->sym); - } - else if(op->arity>=0){ -#if 1 - assert((st->size >= op->arity) - || (op->assoc>0 && op->arity==1 /* unary +, -, ~ */)); -#endif - if(st->size < op->arity){ - rc = s2_engine_err_set(se, CWAL_RC_RANGE, - "Not enough operands on the stack."); - }else{ - rc = op->call(op, se, op->arity, &rv); - popArgCount = op->arity; - } - }else{ -#if 0 - assert(!"not possible... except when running the old test.c, it seems..."); -#else - /* Variadic operator ... */ - s2_stoken * t = se->st.vals.top; - int i = 0; - char doBreak = 0; - /* Count how the arguments by looking for - a S2_T_MarkVariadicStart token. */ - for( ; t && !doBreak && (i size); t = t->next ){ - switch(t->ttype){ - case S2_T_MarkVariadicStart: - doBreak = 1; - break; - default: - ++i; - break; - } - } - assert(doBreak); - if(!doBreak){ - rc = s2_engine_err_set(se, CWAL_RC_MISUSE, - "Missing S2_T_MarkVariadicStart!"); - }else{ - /* MARKER(("variadic argc=%d\n", i)); */ - rc = op->call(op, se, i, &rv); - assert( st->size == (oldStackSize - i) ); - if(1){ - S2_UNUSED_VAR s2_stoken * variadicCheck = s2_engine_peek_token(se); - assert(variadicCheck); - assert(S2_T_MarkVariadicStart == variadicCheck->ttype); - } - s2_engine_pop_token( se, 0 ) /* S2_T_MarkVariadicStart */; - popArgCount = i + 1 /* variadic marker */; - } -#endif - } - if(!rc){ - assert( st->size == (oldStackSize - popArgCount) ); - if(st->size != (oldStackSize - popArgCount)){ - rc = s2_engine_err_set(se, CWAL_RC_MISUSE, - "Unexpected stack size after " - "running operator '%s'\n", - op->sym); - }else if(rv){ - /* Push the result value... */ - s2_stoken * tResult = topOp ? topOp/*recycle it*/ : 0; - topOp = 0; - if(!tResult){ - tResult = s2_stoken_alloc(se); - if(!tResult) rc = CWAL_RC_OOM; - }else{ - *tResult = s2_stoken_empty; - } - if(tResult){ - tResult->ttype = rv ? S2_T_Value : S2_T_Undefined; - tResult->value = rv ? rv : cwal_value_undefined(); - s2_engine_push_valtok(se, tResult); -#if 1 - /* - 20171115: 'abc'[2][0][0][0][0][0] - - is assert()ing (sometimes) in cwal_value_ref() when string - interning is on because 'c' is an interned value but the - above op chain is not ref'ing it (as it should). We could - patch that in s2_eval_expr_impl(), but this is a more - general problem which also potentially affect any place this - routine is used, e.g. function calls may theoretically - trigger it. - - Reminder: the above might fail in s2sh interactive mode - while not failing in a unit test script: triggering it is - dependent on engine-level state. The eval-hold resolves it, - in either case. - */ - rc = s2_eval_hold(se, rv); -#endif - }else{ - if(rv) cwal_refunref(rv); - } - } - }else{ - assert(!rv); - } - if(topOp){ - if(rc && !se->opErrPos) se->opErrPos = s2_ptoken_begin(&topOp->srcPos); - s2_stoken_free(se, topOp, 1); - } - return s2_check_interrupted(se, rc); -} - - -int s2_process_op( s2_engine * se, s2_op const * op ){ - return s2_process_op_impl(se, op, 0); -} - -int s2_process_top( s2_engine * se ){ - s2_stoken_stack * so = &se->st.ops; - s2_op const * op = s2_stoken_op(so->top); - char const * srcPos = so->top ? s2_ptoken_begin(&so->top->srcPos) : 0; - int rc = s2_check_interrupted(se, 0); - if(rc) return rc; - else if(!op){ - rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "Token type %s (#%d) is not an operator.", - so->top ? s2_ttype_cstr(so->top->ttype) : "", - so->top ? so->top->ttype : S2_T_INVALID); - }else if(op - && op->arity>0 - && se->st.vals.sizearity){ - rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "Value stack does not have enough values for " - "operator '%s'.", op->sym); - }else{ - rc = s2_process_op_impl( se, op, 1 ); - if(rc && srcPos && !se->opErrPos) se->opErrPos = srcPos; - } - return rc; -} - -int s2_process_op_type( s2_engine * se, int ttype ){ - s2_op const * op = s2_ttype_op(ttype); - return op - ? s2_process_op(se, op) - : CWAL_RC_TYPE; -} - -int s2_error_set( s2_engine * se, cwal_error * err, int code, char const * fmt, ... ){ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_error_setv(se->e, err, code, fmt, args); - va_end(args); - return rc; -} - - -/* in s2_eval.c */ -int s2_strace_generate( s2_engine * se, cwal_value ** rv ); - -int s2_add_script_props2( s2_engine * se, - cwal_value * ex, - char const * scriptName, - int line, int col){ - int rc = 0; - /** - FIXME: clean up these setters to not strand these temporary - values (on error cases) until the next sweep. - */ - if(scriptName && *scriptName){ - cwal_value * snv = cwal_new_string_value(se->e, - scriptName, - cwal_strlen(scriptName)); - cwal_value_ref(snv); - rc = snv - ? cwal_prop_set_v(ex, se->cache.keyScript, snv) - : CWAL_RC_OOM; - cwal_value_unref(snv); - } - if(!rc && line>0){ - rc = cwal_prop_set_v(ex, se->cache.keyLine, - cwal_new_integer(se->e, line)); - if(!rc) rc = cwal_prop_set_v(ex, se->cache.keyColumn, - cwal_new_integer(se->e, col)); - } - /* MARKER(("strace count=%u\n", se->strace.count)); */ - if(!rc - && se->flags.exceptionStackTrace - && se->strace.count - && !cwal_prop_has_v(ex, se->cache.keyStackTrace, 0)){ - cwal_value * stackTrace = 0; - rc = s2_strace_generate(se, &stackTrace); - if(!rc && stackTrace){ - /* s2_dump_val(stackTrace, "stackTrace"); */ - cwal_value_ref(stackTrace); - rc = cwal_prop_set_v(ex, se->cache.keyStackTrace, stackTrace); - cwal_value_unref(stackTrace); - } - } - return rc; -} - -int s2_add_script_props( s2_engine * se, cwal_value * ex, s2_ptoker const * script ){ - if(ex && !script) script = se->currentScript; - if(!ex - || !script - || !cwal_props_can(ex) - || cwal_prop_has_v(ex, se->cache.keyLine, 1) - /* ^^^ very cursory check for "already has this state" */ - ) return 0; - else{ - s2_linecol_t line = 0, col = 0; - cwal_size_t scriptNameLen = 0; - s2_ptoker const * top = 0; - char const * scriptName = s2_ptoker_name_first(script, &scriptNameLen); - char const * errPos = s2_ptoker_err_pos(script); - assert(errPos); - s2_ptoker_count_lines(top ? top : script, errPos, &line, &col); - if(scriptName || (line>0)){ - return s2_add_script_props2(se, ex, scriptName, line, col); - }else{ - return 0; - } - } -} - -int s2_exception_add_script_props( s2_engine * se, s2_ptoker const * script ){ - cwal_value * ex = cwal_exception_get(se->e); - return ex ? s2_add_script_props(se, ex, script) : 0; -} - -cwal_value * s2_error_exception( s2_engine * se, - cwal_error * err, - char const * scriptName, - int line, int col ){ - return cwal_error_exception(se->e, err, scriptName, line, col); -} - -int s2_throw_err( s2_engine * se, cwal_error * err, - char const * script, - int line, int col ){ - return cwal_error_throw(se->e, err, script, line, col); -} - -int s2_throw_value( s2_engine * se, s2_ptoker const * pr, int code, cwal_value *v ){ - int rc = 0, rc2 = 0; - cwal_value * exv; - char wasException; - assert(v); - if(CWAL_RC_OOM==code) return code; - else if(!pr) pr = se->currentScript; - exv = cwal_exception_value(cwal_value_exception_part(se->e, v)); - wasException = exv ? 1 : 0; - if(!exv){ - exv = cwal_new_exception_value(se->e, code ? code : CWAL_RC_EXCEPTION, v); - if(!exv) rc2 = CWAL_RC_OOM; - } - if(exv){ - cwal_value * const propTarget = wasException ? v : exv; - cwal_value_ref(exv); - if(pr && !cwal_prop_has_v(propTarget, se->cache.keyLine, 1) - /* ^^^ *seems* (at an admittedly quick glance) to not inherit - location information, so we'll harvest it. */){ - /* 20191228: this was changed to only set the properties if the - passed-in object seems to not have/inherit them to begin - with. Previously we were, when v inherited an exception, - setting this state in v even through v inherited it from exv, - which led to, e.g., duplicate (but ever-so-slightly - different) stackTrace values. - */ - rc2 = s2_add_script_props(se, propTarget, pr); - } - if(!rc2){ - rc = cwal_exception_set(se->e, propTarget); - } - cwal_value_unref(exv) - /* On success, cwal holds a reference. On error, if exv was - derived from v then the caller had better hold a reference to - it or else we're all doomed. If exv is a prototype of v, - rather than being v itself, it has a reference through that - association. */; - } - return rc2 ? rc2 : rc; -} - - -int s2_throw( s2_engine * se, int code, char const * fmt, ... ){ - int rc; - switch(code){ - case CWAL_RC_OOM: rc = code; - break; - default: { - va_list args; - va_start(args,fmt); - rc = cwal_error_setv( se->e, NULL, code, fmt, args); - va_end(args); - if(rc==code) rc = cwal_error_throw(se->e, 0, 0, 0, 0); - break; - } - } - return rc; -} - -int s2_cb_throw( cwal_callback_args const * args, int code, char const * fmt, ... ){ - int rc; - switch(code){ - case CWAL_RC_OOM: rc = code; - break; - default: { - va_list vargs; - va_start(vargs,fmt); - rc = cwal_exception_setfv(args->engine, code, fmt, vargs); - va_end(vargs); - break; - } - } - return rc; -} - -int s2_engine_err_has( s2_engine const * se ){ - int rc = se->flags.interrupted; - if(!rc){ - rc = cwal_engine_error_get(se->e, NULL, NULL); - if(!rc){ - rc = cwal_exception_get(se->e) - ? CWAL_RC_EXCEPTION : 0; - } - } - return rc; -} - -int s2_engine_err_setv( s2_engine * se, int code, char const * fmt, va_list vargs ){ - return cwal_error_setv(se->e, 0, code, fmt, vargs); -} - -int s2_engine_err_set( s2_engine * se, int code, char const * fmt, ... ){ - int rc; - va_list args; - va_start(args,fmt); - rc = cwal_error_setv(se->e, NULL, code, fmt, args); - va_end(args); - return rc; -} - -void s2_engine_err_reset( s2_engine * se ){ - se->flags.interrupted = 0; - cwal_engine_error_reset(se->e); -} - -void s2_engine_err_reset2( s2_engine * se ){ - s2_engine_err_reset(se); - cwal_exception_set(se->e, 0); - s2_propagating_set(se, 0); -} - -void s2_engine_err_clear( s2_engine * se ){ - se->flags.interrupted = 0; - cwal_error_clear(se->e, 0); -} - -int s2_engine_err_get( s2_engine const * se, char const ** msg, cwal_size_t * msgLen ){ - return cwal_engine_error_get(se->e, msg, msgLen); -} - -void s2_dump_value( cwal_value * v, char const * msg, - char const * file, char const * func, int line ){ - cwal_scope const * sc = cwal_value_scope(v); - static cwal_json_output_opt jopt = cwal_json_output_opt_empty_m; - static int once = 0; - FILE * out = stdout /* Reminder: we cannot use cwal_output() because - v might be a built-in const without a cwal_engine - instance. */; - if(v){ - assert((sc || cwal_value_is_builtin(v)) - && "Seems like we've cleaned up too early."); - } - if(!once){ - jopt.cyclesAsStrings = 1; - jopt.functionsAsObjects = 0; - jopt.addNewline = 1; - jopt.indentSingleMemberValues = 0; - jopt.indent = 0; - jopt.indentString.str = " "; - jopt.indentString.len = cwal_strlen(jopt.indentString.str); - once = 1; - } - if(file && func && line>0){ - fprintf(out,"%s:%d:%s(): ", file, line, func); - } - fprintf(out,"%s%s%s@%p[scope=#%d ref#=%d] " - "==> ", - msg ? msg : "", msg?": ":"", - cwal_value_type_name(v), - (void const *)v, - (int)(sc ? sc->level : 0), - (int)cwal_value_refcount(v) - ); - switch( v ? cwal_value_type_id(v) : -1 ){ - case -1: - fwrite("\n", 7, 1, out); - break; - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_HASH: - case CWAL_TYPE_UNIQUE: - fprintf(out, "%s@%p\n", - cwal_value_type_name(v), (void const*)v); - break; - case CWAL_TYPE_UNDEF: - fwrite("undefined\n", 10, 1, out); - break; - default: - cwal_json_output_FILE( v, out, &jopt ); - break; - } -} - -void s2_fatal( int code, char const * fmt, ... ){ - va_list args; - cwal_printf_FILE(stderr, "FATAL ERROR: code=%d (%s)\n", - code, cwal_rc_cstr(code)); - if(fmt && *fmt){ - va_start(args,fmt); - cwal_printfv_FILE(stderr, fmt, args); - va_end(args); - fwrite("\n", 1, 1, stderr); - } - abort(); -} - -int s2_var_decl_v( s2_engine * se, cwal_value * key, - cwal_value * v, uint16_t flags ){ - return cwal_var_decl_v(se->e, 0, key, v, flags); -} - -int s2_var_decl( s2_engine * se, char const * key, cwal_size_t keyLen, - cwal_value * v, uint16_t flags ){ - return cwal_var_decl(se->e, 0, key, keyLen, v, flags); -} - -static int s2_stash_init(s2_engine * se){ - assert(!se->stash); - se->stash = cwal_new_hash_value(se->e, 47) - /* 20200118: the stash currently only contains - ~26 values, not including anything which - loadable modules might install. */ - ; - if(!se->stash) return CWAL_RC_OOM; - else{ - cwal_scope * topScope; - topScope = &se->scopes.topScope; - assert(topScope->level); - cwal_value_rescope(topScope, se->stash); - cwal_value_ref(se->stash) - /* Make sure it doesn't get swept up! */ - ; - cwal_value_make_vacuum_proof(se->stash, 1) - /* And not vacuumed, either. */ - ; - } - return 0; -} - -int s2_stash_set_v( s2_engine * se, cwal_value * key, cwal_value * v ){ - int rc = 0; - cwal_hash * h; - if(!key || !v) return CWAL_RC_MISUSE; - else if(!se->stash){ - rc = s2_stash_init(se); - if(rc) return rc; - } - h = cwal_value_get_hash(se->stash); - rc = cwal_hash_insert_v(h, key, v, 1 ); - if(!rc){ - rc = cwal_hash_grow_if_loaded(h, 0.75); - } - return rc; -} - -int s2_stash_set( s2_engine * se, char const * key, cwal_value * v ){ - int rc; - cwal_value * kv = cwal_new_string_value(se->e, key, cwal_strlen(key)); - if(kv){ - cwal_value_ref(kv); - rc = s2_stash_set_v(se, kv, v); - cwal_value_unref(kv); - }else{ - rc = CWAL_RC_OOM; - } - return rc; -} - -cwal_value * s2_stash_get2( s2_engine * se, char const * key, - cwal_size_t keyLen){ - if(!se || !key || !*key || !se->stash) return NULL; - else { - return cwal_hash_search( cwal_value_get_hash(se->stash), - key, keyLen ); - } -} - -cwal_kvp * s2_stash_get2_kvp( s2_engine * se, char const * key, - cwal_size_t keyLen){ - if(!se || !key || !*key || !se->stash) return NULL; - else { - if(0==keyLen && *key) keyLen = cwal_strlen(key); - return cwal_hash_search_kvp( cwal_value_get_hash(se->stash), - key, keyLen ); - } -} - -cwal_value * s2_stash_get( s2_engine * se, char const * key ){ - return s2_stash_get2(se, key, cwal_strlen(key)); -} - -cwal_value * -s2_stash_get_v( s2_engine * se, cwal_value const * key ){ - if(!se || !key) return NULL; - else return se->stash - ? cwal_hash_search_v( cwal_value_get_hash(se->stash), - key ) - : 0; -} - - -#define HAS_ENUM_FLAG(ClientFlags) \ - (S2_VAL_F_CLASS_ENUM & (ClientFlags)) -#define HAS_DOTLIKE_FLAG(ClientFlags) \ - ((S2_VAL_F_DOT_LIKE_OBJECT & (ClientFlags)) \ - || HAS_ENUM_FLAG(ClientFlags)) - - -#if S2_TRY_INTERCEPTORS - -/* static */ cwal_function * s2_value_is_interceptor( cwal_value const * v ){ - return (cwal_container_flags_get(v) & CWAL_CONTAINER_INTERCEPTOR) - ? cwal_value_get_function(v) - : 0; -} - -/** - Proxy for handling get/set interceptor calls. - - If S2_TRY_INTERCEPTORS is false, this is a no-op which returns 0, - else... - - If s2_value_is_interceptor(func) then it is treated like an - interceptor, calling the function on self and passing it the - setterArg (if not NULL) or no arguments (assumed to be the getter - call form). If rv and setterArg are not NULL, the result goes in - *rv: the result of setters is ignored by the framework (the setter - APIs simply have no way to communicate overridden results all the - way back up the stack). - */ -static int s2__check_intercept( s2_engine * se, - cwal_value * propFoundIn, - cwal_value * self, - cwal_value * func, - cwal_value * setterArg, - cwal_value **rv ){ - int rc = 0; - cwal_function * f = s2_value_is_interceptor(func); - if(f){ - cwal_value * frv = 0; - /*if(propFoundIn != self){ - s2_dump_val(propFoundIn,"propFoundIn"); - s2_dump_val(self, "self "); - }*/ - rc = cwal_function_call2( f, propFoundIn, - self, - setterArg ? NULL : &frv, - setterArg ? 1 : 0, - setterArg ? &setterArg : NULL ); - if(rc){ - /* Error! We hope an exception was thrown so that - the caller of this func can respond to it. */ - assert(!frv); - }else{ - if(rv) *rv = frv ? frv : cwal_value_undefined(); - else if(frv){ - cwal_refunref(frv); - frv = 0; - } - } - } - return rc; -} - -#endif/* end S2_TRY_INTERCEPTORS */ - -/** - Internal helper for s2_get_v_proxy2(). Fetches the n'th UTF8 - character from the given str, returning cwal_value_undefined() if n - is out of range or the input appears to not be UTF8, and NULL on - allocation error. The value is returned as a new length-1 string. - - TODO: negative values to count from the end, but then we'll - also need to go patch string.charAt() for that. -*/ -cwal_value * s2_charat_by_index( s2_engine *se, - cwal_string const * str, - cwal_int_t n ){ - cwal_midsize_t slen = 0; - unsigned char const * cstr = - (unsigned char const * )cwal_string_cstr2(str, &slen); - unsigned int cp = 0; - assert(cstr); - if(n < 0 || (cwal_size_t)n >= cwal_string_length_bytes(str)){ - return cwal_value_undefined(); - }else if(cwal_string_is_ascii(str)){ - cp = cstr[n]; - }else if(cwal_utf8_char_at( cstr, cstr + slen, (cwal_size_t)n, - &cp )){ - return cwal_value_undefined(); - } - - { - unsigned char buf[6] = {0,0,0,0,0,0}; - int const clen = - cwal_utf8_char_to_cstr(cp, buf,(cwal_size_t)sizeof(buf)); - assert(clen<(int)sizeof(buf)); - if(clen<1) return cwal_value_undefined(); - else return cwal_new_string_value(se->e, (char const *)buf, - (cwal_size_t)clen); - } -} - -/** - Internal s2_get_v() impl. Possibly recursively looks up self's - protototype chain until it finds the given property key. Implements - several special-case lookups as well: - - - a hash-type 'self' container self with the "dot uses hash - entries" flag searches hash entries first and falls back to - property lookup. - - - If key is the string "prototype" then self's prototype is - returned. - - - If self is-a string and key is-a integer then it does a - character-at operation, returning the value as a length-1 string or - (if the index is out of range) the undefined value (or NULL on - allocation error). - - Reminder to self: this is what fixed the problem that in some - callbacks, this.xyx was always resolving to undefined. - - if foundIn is not NULL then (A) prototype lookups are enabled and - (B) *foundIn gets set to the prototype in which the property is - found (which can be different from self). -*/ -static cwal_value * s2_get_v_proxy2( s2_engine * se, - cwal_value * self, - cwal_value * key, - cwal_value ** foundIn){ - cwal_value * v = NULL; - cwal_hash * h = 0; - cwal_value * foundAt = 0; -#if S2_TRY_INTERCEPTORS - cwal_value * origin = self; -#endif - - uint16_t vflags = cwal_container_client_flags_get(self); - assert(se && self && key); - s2_engine_err_reset(se); - /* s2_dump_val(key,"get_v_proxy key"); */ - if(s2_value_is_prototype_string(se, key)){ - /* x.prototype */ - return cwal_value_prototype_get( se->e, self ); - }else if(cwal_value_is_unique(self) - && s2_value_is_value_string(se, key)){ - /* enum.entry.value */ - return cwal_unique_wrapped_get( self ); - }else if(cwal_value_is_string(self) - && cwal_value_is_integer(key)){ - /* string[index] */ - cwal_int_t const n = cwal_value_get_integer(key); - return s2_charat_by_index( se, cwal_value_get_string(self), n ); - } - -#if 0 && S2_TRY_INTERCEPTORS - cwal_prop_getX_v( self, key, &v ); -#else - while(!v && self){ - cwal_kvp const * kvp = 0; - if(HAS_DOTLIKE_FLAG(vflags) - && (h = cwal_value_get_hash(self))){ - kvp = cwal_hash_search_kvp_v(h, key); -#if 1 - /* Fall back to its own properties, instead of - immediately going up to the prototype. */ - if(!kvp){ - kvp = cwal_prop_get_kvp_v(self, key, 0, NULL); - } -#endif - }else{ - kvp = cwal_prop_get_kvp_v(self, key, 0, NULL); - } - if(kvp) v = cwal_kvp_value(kvp); - if(v) { - foundAt = self; - break; - } - else if(!foundIn) break; - self = cwal_value_prototype_get(se->e, self); - if(self) vflags = cwal_container_client_flags_get(self); - } - - if(v){ - if(foundIn) *foundIn = foundAt; -#if S2_TRY_INTERCEPTORS - s2__check_intercept( se, foundAt, origin, v, 0, &v ); -#endif - } -#endif - return v; -} - -cwal_value * s2_var_get_v( s2_engine * se, int scopeDepth, - cwal_value const * key ){ - return cwal_scope_search_v( cwal_scope_current_get(se->e), - scopeDepth, key, 0 ); -} - -cwal_value * s2_var_get( s2_engine * se, int scopeDepth, - char const * key, cwal_size_t keyLen ){ - return (!se || !key) - ? 0 - : cwal_scope_search( cwal_scope_current_get(se->e), - scopeDepth, key, - keyLen, 0 ); -} - -int s2_var_set( s2_engine * se, int scopeDepth, - char const * key, cwal_size_t keyLen, - cwal_value * v ){ - return key - ? cwal_scope_chain_set( cwal_scope_current_get(se->e), - scopeDepth, key, keyLen, v ) - : CWAL_RC_MISUSE; -} - -int s2_var_set_v( s2_engine * se, int scopeDepth, - cwal_value * key, cwal_value * v ){ - return key - ? cwal_scope_chain_set_v( cwal_scope_current_get(se->e), - scopeDepth, key, v ) - : CWAL_RC_MISUSE; -} - -int s2_set_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value * v ){ - - return s2_set_with_flags_v(se, self, key, v, - CWAL_VAR_F_PRESERVE); -} - -/** - Internal proxy for s2_set_with_flags_v(). It handles the setting of - Object properties and Hash entries if - HAS_DOTLIKE_FLAG(clientFlags). kvpFlags are the flags to set on the - property (e.g. CWAL_VAR_F_CONST and CWAL_VAR_F_HIDDEN are fairly - common). Pass CWAL_VAR_F_PRESERVE to keep any existing flags. -*/ -static int s2_set_prop_proxy(s2_engine * se, - cwal_value * self, - cwal_value * key, cwal_value * v, - uint16_t kvpFlags ){ - int rc = 0; - uint16_t const clientFlags = cwal_container_client_flags_get(self); - cwal_hash * h = HAS_DOTLIKE_FLAG(clientFlags) - ? cwal_value_get_hash(self) - : 0; - if(se){/*avoid unused param warning*/} -#if !S2_TRY_INTERCEPTORS - if(h){ - rc = v - ? cwal_hash_insert_with_flags_v(h, key, v, 1, kvpFlags ) - : cwal_hash_remove_v(h, key); - }else{ - rc = cwal_prop_set_with_flags_v( self, key, v, kvpFlags ); - } -#else - if(!v){ - rc = h - ? cwal_hash_remove_v(h, key) - : cwal_prop_unset_v(self, key); - } - else if(!cwal_value_may_iterate(self)){ - /* compatibility crutch. Some (not all) of the 'set' code works - fine without this, but we have tests which check for this - condition. Plus, it matches what the s2 manual says. */ - return CWAL_RC_IS_VISITING; - } - else{ - cwal_value * foundIn = 0; - cwal_kvp * kvp = h - ? cwal_hash_search_kvp_v(h, key) - : cwal_prop_get_kvp_v(self, key, 1, &foundIn); - cwal_value * const fv = kvp ? cwal_kvp_value(kvp) : 0; - cwal_function * f = 0; - assert( kvp ? !!fv : 1 ); - /* s2_dump_val(key,"set key"); */ - /* s2_dump_val(fv,"set val"); */ - /* s2_dump_val(cwal_prop_get_v(self, key),"prop-get()"); */ - /*if(cwal_value_is_array(self)){ - s2_dump_val(self,"set self"); - }*/ - /*if(fv && cwal_value_is_function(fv)){ - s2_dump_val(fv,"fv"); - MARKER(("func with flags: %02x\n", - cwal_container_client_flags_get(fv))); - }*/ - if(!kvp && (clientFlags & S2_VAL_F_DISALLOW_UNKNOWN_PROPS)){ - rc = CWAL_RC_DISALLOW_NEW_PROPERTIES; - } - else if(fv && (f = s2_value_is_interceptor(fv))){ - /* MARKER(("setter interceptor!\n")); */ - rc = s2__check_intercept( se, - foundIn ? foundIn : self, - self, fv, v, 0 ); - /* Never reset kvpFlags for these */ - } - else{ - if(!kvp && h){ - rc = cwal_hash_insert_with_flags_v(h, key, v, 1, kvpFlags); - } - else if(kvp && (h || foundIn==self)){ - rc = cwal_kvp_value_set2(kvp, v); - if(!rc){ - cwal_value_rescope( cwal_value_scope(self), v ) - /* Without this rescope, we end up using a stale - value at some point in some specific code - constellations. */; - cwal_kvp_flags_set( kvp, kvpFlags ); - } - } - else{ - rc = cwal_prop_set_with_flags_v( self, key, v, kvpFlags ) - /* ^^^ that requires a second property lookup internally, - but with no prototype search. It's required to keep the - "property assignment never overwrites inherited - properties" behaviour. - */ - ; - } - } - assert(CWAL_RC_MISUSE != rc); - } -#endif/* end S2_TRY_INTERCEPTORS */ - return rc; -} - -int s2_set_with_flags_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value * v, - uint16_t kvpFlags ){ - int rc = 0; - char const *errMsg = 0; - /* if(!kvpFlags) kvpFlags = CWAL_VAR_F_PRESERVE; */ - s2_engine_err_reset(se); - if(self){ - /* Object/property access */ - char const isKeyAnInt = cwal_value_is_integer(key); - cwal_tuple * tp = 0; -#if 0 - /* 20191210: we may want to consider extending the - CWAL_CONTAINER_DISALLOW_PROP_SET flag semantics to include - array and tuple indexes. Noting, however, that tuples don't - have container flags. - */ - if(cwal_container_flags_get(self) - & CWAL_CONTAINER_DISALLOW_PROP_SET){ - rc = CWAL_RC_DISALLOW_PROP_SET; - errMsg = "Setting properties is disallowed on this value."; - }else -#endif -#if 0 - /* Reminder we probably(?) don't(?) want this to guard prototype - changing */ - if((rc = s2_immutable_container_check(se,self, 0))){ - return rc; - }else -#endif - if(isKeyAnInt && (tp=cwal_value_get_tuple(self))){ - uint16_t const tlen = cwal_tuple_length(tp); - cwal_int_t const n = cwal_value_get_integer(key); - if(n<0 || n>=(cwal_int_t)tlen){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Index %d is out of range " - "for a length-%d tuple.", - (int)n, (int)tlen); - } - rc = cwal_tuple_set(tp, (uint16_t)n, v); - assert(!rc && "the only error cases involve memory corruption or bad args."); - } - else if(cwal_props_can(self)){ /* A container */ - cwal_array * ar; - if(isKeyAnInt && (ar=cwal_value_array_part(se->e,self))){ - /* ==> Array[Index] */ - /* - Reminder: cwal_value_array_part() ends up setting entries in - arrays used as prototypes, but that is arguably expected. - */ - cwal_int_t i; - if((rc = s2_immutable_container_check(se,self, 0))){ - return rc; - } - i = cwal_value_get_integer(key); - if(i<0){ - rc = CWAL_RC_RANGE; - errMsg = "Array indexes may not be negative."; - }else{ - /*MARKER("Setting array index #%u\n",(unsigned)i); - s2_dump_val(v,"array entry");*/ - rc = cwal_array_set(ar, (cwal_size_t)i, v); - if(rc) errMsg = "cwal_array_set() failed."; - } - }/* end array[int] */ - else if(s2_value_is_prototype_string(se, key)){ - /* Special case: prototype pseudo-keyword/property */ - if(!cwal_props_can(self)){ - /* Normally never reached b/c assignment ops catch - this case, but non-assignment ops can also call - this. */ - rc = CWAL_RC_ACCESS; - errMsg = "Cannot re-assign prototypes of non-container types " - "- they are fixed in place at the C level."; - } - else if(cwal_value_null()==v || cwal_value_undefined()==v){ - /* Special case: allow unsetting the prototype via - assignment to these. Normally this would fail in - cwal_value_prototype_set() because it expects (as a - prototype) a container type or NULL. Maybe that - limitation (in cwal) is unnecessary. - */ - v = 0; - } - if(!rc){ - rc = cwal_value_prototype_set( self, v ); - switch(rc){ - case 0: break; - case CWAL_RC_DISALLOW_PROTOTYPE_SET: - errMsg = "Setting the prototype is disallowed on this value."; - break; - case CWAL_RC_CYCLES_DETECTED: - errMsg = "Setting prototype would introduce a cycle in the prototype chain."; - break; - case CWAL_RC_TYPE: - errMsg = "Invalid type for a prototype (only containers " - "allowed, or null/undefined to remove the prototype)."; - break; - default: - errMsg = "cwal_value_prototype_set() failed"; - break; - } - } - }/*prototype pseudo-property*/ - else{ - /* Set container property... */ - uint16_t const clientFlags = cwal_container_client_flags_get(self); - if((rc = s2_immutable_container_check(se,self, 0))){ - return rc; - } - else if(HAS_ENUM_FLAG(clientFlags)){ - rc = CWAL_RC_DISALLOW_PROP_SET; - }else{ - rc = s2_set_prop_proxy( se, self, key, v, kvpFlags ); - } - /*if(rc){ - MARKER(("rc=%s\n", cwal_rc_cstr(rc))); - }*/ - switch(rc){ - case CWAL_RC_OOM: break; - case CWAL_RC_NOT_FOUND:{ - if(clientFlags & S2_VAL_F_DISALLOW_UNKNOWN_PROPS){ - cwal_size_t keyLen = 0; - char const * keyStr = cwal_value_get_cstr(key,&keyLen); - if(keyStr){ - return s2_engine_err_set(se, rc, - "Unknown property '%.*s'.", - (int)keyLen, keyStr); - }else{ - return s2_engine_err_set(se, rc, - "Unknown property with key type '%s'.", - cwal_value_type_name(key)); - } - } - break; - } - case CWAL_RC_TYPE: - if(cwal_prop_key_can(key)){ - return s2_engine_err_set(se, rc, - "Invalid target type (%s) " - "for assignment.", - cwal_value_type_name(self)); - }else{ - return s2_engine_err_set(se, rc, - "Type (%s) is not valid as a property key.", - cwal_value_type_name(key)); - } - case CWAL_RC_DISALLOW_NEW_PROPERTIES: - return s2_engine_err_set(se, rc, - "Container does not allow " - "new properties."); - case CWAL_RC_DISALLOW_PROP_SET: - return s2_engine_err_set(se, rc, - "Container is marked as immutable."); - case CWAL_RC_CONST_VIOLATION:{ - cwal_size_t keyLen = 0; - char const * keyStr = cwal_value_get_cstr(key,&keyLen); - if(keyStr){ - return s2_engine_err_set(se, rc, - "Cannot assign to const '%.*s'.", - (int)keyLen, keyStr); - }else{ - return s2_engine_err_set(se, rc, - "Cannot assign to const property.", - cwal_value_type_name(self)); - } - } - case CWAL_RC_LOCKED:{ - cwal_size_t len = 0; - char const * tname = cwal_value_type_name2(v, &len); - return s2_engine_err_set(se, rc, - "'%.*s' value is currently locked against modification.", - (int)len, tname); - } - case CWAL_RC_IS_VISITING_LIST: - return s2_engine_err_set(se, rc, - "Cannot perform this operation on a list/hash " - "during traversal."); - case CWAL_RC_IS_VISITING: - case CWAL_RC_ACCESS/*historical*/:{ - return s2_engine_err_set(se, rc, - "Cannot modify properties " - "during traversal."); - } - }/*prop/hash set rc check*/ - }/*set property*/ - }else{ - cwal_size_t tlen = 0; - char const * tn = cwal_value_type_name2(self, &tlen); - return s2_engine_err_set(se, CWAL_RC_TYPE, - "Cannot set properties on " - "non-container type '%.*s'.", - (int)tlen, tn); - } - }else{ - /* Scope-level set */ - cwal_scope * s = cwal_scope_current_get(se->e); -#if 0 - s2_dump_val(key,"KEY Setting scope var"); - s2_dump_val(v,"VALUE Setting scope var"); - { - cwal_kvp const * kvp = cwal_scope_search_kvp_v(s, -1, key, 0); - if(kvp){ - s2_dump_val(cwal_kvp_key(kvp),"KEY Setting scope var"); - s2_dump_val(cwal_kvp_value(kvp),"VALUE Setting scope var"); - MARKER(("KVP flags=0x%04u\n", cwal_kvp_flags(kvp))); - } - } -#endif - /** - FIXME?: disable a SET on a scope variable which is undeclared in - all scopes, throw an error in that case. Requires a search+set - (and set has to do its own search, again) or a flag to one of - the cwal-level setters which tells it to fail if the var cannot - be found. That's handled at the evaluation/operator level. - */ - rc = cwal_scope_chain_set_with_flags_v( s, - v ? -1 : 0 /* don't allow 'unset' - across scopes*/, - key, v, kvpFlags ); - switch(rc){ - case 0: - case CWAL_RC_EXCEPTION: - break; - case CWAL_RC_NOT_FOUND: - assert(!v && "But the code said so!"); - /* if(!v) rc = 0; */ - break; - case CWAL_RC_CONST_VIOLATION:{ - cwal_size_t keyLen = 0; - char const * keyStr = cwal_value_get_cstr(key,&keyLen); - if(keyStr){ - rc = s2_engine_err_set(se, rc, - "Cannot assign to const '%.*s'.", - (int)keyLen, keyStr); - }else{ - errMsg = "Cannot assign to a const."; - } - break; - } - case CWAL_RC_DISALLOW_NEW_PROPERTIES: - rc = s2_engine_err_set(se, rc, - "Scope's storage does not allow " - "new properties."); - break; - case CWAL_RC_DISALLOW_PROP_SET: - rc = s2_engine_err_set(se, rc, - "Scope's storage is marked as immutable."); - break; - default: - errMsg = "s2_var_set_v() failed."; - break; - } - } - if(errMsg && (CWAL_RC_EXCEPTION!=rc)){ - assert(rc); - rc = s2_engine_err_set(se, rc, "%s", errMsg ); - } - return rc; -} - -int s2_set_with_flags( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value * v, - uint16_t flags ){ - int rc = 0; - /* This impl removes lots of dupe code at the cost of a potentially - temporary string. */ - cwal_value * kv; - if(!se || !key) return CWAL_RC_MISUSE; - kv = cwal_new_string_value(se->e, key, keyLen); - rc = kv ? 0 : CWAL_RC_OOM; - if(!rc) { - cwal_value_ref(kv); - rc = s2_set_with_flags_v( se, self, kv, v, flags ); - cwal_value_unref(kv); - } - return rc; -} - - -int s2_set( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value * v ){ - return s2_set_with_flags( se, self, key, keyLen, v, CWAL_VAR_F_PRESERVE ); -} - -#define s2__err(SE) (SE)->e->err - -static int s2__get_check_exception(s2_engine * se, int rc){ - if(!rc){ - /* rc = s2_check_interrupted(se, rc); */ - if(!rc && cwal_exception_get(se->e)) rc = CWAL_RC_EXCEPTION; - else if( s2__err(se).code ){ - rc = 1 - ? s2__err(se).code - : s2_throw_err_ptoker(se, 0); - } - } - return rc; -} - - -static cwal_value * s2_get_v_proxy( s2_engine * se, - cwal_value * self, - cwal_value * key ){ - cwal_value * foundIn = 0; - return s2_get_v_proxy2(se, self, key, &foundIn); -} - - -/** - C-string equivalent of s2_get_v_proxy(). -*/ -static cwal_value * s2_get_proxy( s2_engine * se, - cwal_value * self, - char const * key, - cwal_size_t keyLen ){ -#if 1 - /* quick/dirty hack to avoid having add S2_TRY_INTERCEPTORS - support to this function just yet. This adds allocations, - which is bad, of course. */ - cwal_value * ks = cwal_new_string_value(se->e, key, keyLen); - cwal_value * rv = 0; - s2_engine_err_reset(se); - if(!ks){ - if(se->currentScript){ - s2_err_ptoker(se, se->currentScript, CWAL_RC_OOM, 0); - }else{ - s2_error_set(se, 0, CWAL_RC_OOM, 0); - assert(s2__err(se).code); - } - return 0; - } - cwal_value_ref(ks); - rv = s2_get_v_proxy(se, self, ks); - cwal_value_unref(ks); - return rv; -#else - cwal_hash * h = 0; - cwal_value * v = 0; - uint16_t vflags = 0; - assert(se && self && key); - if(9==keyLen && 'p'==*key && (0==memcmp( key, "prototype", 9 ))){ - return cwal_value_prototype_get( se->e, self ); - } - else if(5==keyLen && 'v'==*key && (0==memcmp( key, "value", 5 )) - && cwal_value_is_unique(self)){ - return cwal_unique_wrapped_get( self ); - } - - /* s2_dump_val(self,key); */ - while(!v && self){ - vflags = cwal_container_client_flags_get(self); - if(HAS_DOTLIKE_FLAG(vflags) - && (h = cwal_value_get_hash(self))){ - v = cwal_hash_search(h, key, keyLen); -#if 1 - /* Fall back to its own properties, instead of - immediately going up to the prototype. */ - if(!v) v = cwal_prop_get(self, key, keyLen); -#endif - }else{ - v = cwal_prop_get(self, key, keyLen); - } - self = v ? 0 : cwal_value_prototype_get(se->e, self); - /* s2_dump_val(self,key); */ - } - /* s2_dump_val(v,key); */ - return v; -#endif -} - -int s2_get_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value ** rv ){ - int rc = 0; - cwal_value * xrv = 0; - if(!self){ - /* do a scope lookup */ - xrv = s2_var_get_v( se, -1, key ); - rc = 0; - }else{ - cwal_array * ar; - cwal_tuple * tp = 0; - char const isKeyAnInt = cwal_value_is_integer(key); - assert(key); - xrv = 0; - if(isKeyAnInt - && (ar=cwal_value_array_part(se->e,self))){ - cwal_int_t const i = cwal_value_get_integer(key); - if(i<0){ - rc = s2_engine_err_set(se, CWAL_RC_RANGE, - "Array indexes may not be negative."); - - }else{ - xrv = cwal_array_get(ar, (cwal_size_t)i); - rc = 0; - } - }else if(isKeyAnInt - && (tp = cwal_value_get_tuple(self))){ - cwal_size_t const tlen = cwal_tuple_length(tp); - cwal_int_t const n = cwal_value_get_integer(key); - if(n<0 || n>=(cwal_int_t)tlen){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Index %d is out of range " - "for a length-%d tuple.", - (int)n, (int)tlen); - } - xrv = cwal_tuple_get(tp, (cwal_size_t)n); - rc = 0; - } - else { - if(isKeyAnInt && cwal_value_is_string(self)){ - /* string[index] */ - if(cwal_value_get_integer(key)<0){ - rc = s2_engine_err_set(se, CWAL_RC_RANGE, - "String indexes may not be negative."); - }else if(!(xrv = s2_get_v_proxy( se, self, key ))){ - rc = CWAL_RC_OOM; - }else if( (rc = s2__get_check_exception(se, rc)) ){ - cwal_refunref(xrv); - xrv = 0; - } - }else{ - /* Object property lookup... */ - xrv = s2_get_v_proxy( se, self, key ); - if( (rc = s2__get_check_exception(se, rc)) ){ - cwal_refunref(xrv); - xrv = 0; - } - } - } - if(!rc && !xrv){ - uint16_t const selfFlags = cwal_container_client_flags_get(self); - if(S2_VAL_F_DISALLOW_UNKNOWN_PROPS & selfFlags){ - cwal_size_t keyLen = 0; - char const * keyStr = cwal_value_get_cstr(key,&keyLen); - rc = keyLen - ? s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - /* DISALLOW_NEW_PROPERTIES? */ - "Unknown property '%.*s'.", - (int)keyLen, keyStr) - : s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - "Unknown property with key type '%s'.", - cwal_value_type_name(key)); - } - } - } - if(!rc) *rv = xrv; - return rc; -} - -int s2_get( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value ** rv ){ - int rc = 0; - cwal_value * v = 0; - if(!key) return CWAL_RC_MISUSE; - else if(self){ - v = s2_get_proxy( se, self, key, keyLen ); - rc = s2__get_check_exception(se, rc); - if(!rc){ - if(v){ - *rv = v; - } - else{ - uint16_t const selfFlags = cwal_container_client_flags_get(self); - if(S2_VAL_F_DISALLOW_UNKNOWN_PROPS & selfFlags){ - rc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - "Unknown property '%.*s'.", - (int)keyLen, key); - } - } - } - }else{ - v = s2_var_get( se, -1, key, keyLen ); - rc = s2__get_check_exception(se, rc); - if(rc){ - assert(!v); - }else{ - *rv = v; - } - } - return rc; -} - - -int s2_install_core_prototypes(s2_engine * se){ - int rc = CWAL_RC_OOM; - cwal_value * v; - if(! (v = s2_prototype_object(se)) ) goto end; - /* Object installs the other core ones, to try to stave off - potential chicken/egg timing issues. */ - rc = 0; - end: - return rc; -} - -int s2_throw_err_ptoker( s2_engine * se, s2_ptoker const * pr ){ - char const * errPos; - s2_linecol_t line = 0, col = 0; - if(!pr) pr = se->currentScript; - assert(s2__err(se).code); - assert(pr); - errPos = se->opErrPos - ? se->opErrPos - : s2_ptoker_err_pos(pr); - assert(errPos); - s2_ptoker_count_lines( pr, errPos, &line, &col); - return s2_throw_err(se, NULL, s2_ptoker_name_first(pr, 0), - line, col); -} - -/** @internal - - Appends se->err with file/line column info based on (st ? st : - se->currentScript). - - Returns se->err.code on success and CWAL_RC_OOM if allocating the - script name part fails. -*/ -int s2_err_ammend_flc(s2_engine * se, s2_ptoker const * st){ - int rc = 0; - char const * name; - cwal_size_t nameLen = 0; - char const * errPos; - s2_linecol_t line = 0, col = 0; - cwal_error * const err = &s2__err(se); - assert(err->code); - if(!st) st = se->currentScript; - errPos = se->opErrPos - ? se->opErrPos - : s2_ptoker_err_pos(st); - assert(errPos); - name = s2_ptoker_name_first(st, &nameLen); - s2_ptoker_count_lines(st, errPos, &line, &col); - err->line = line; - err->col = col; - err->script.used = 0; - if(!rc && name){ - rc = cwal_buffer_append( se->e, &err->script, name, nameLen ); - }else if(err->script.capacity){ - err->script.mem[0] = 0; - } - return rc ? rc : err->code; -} - -static int s2_err_ptoker_impl( s2_engine * se, char throwIt, - s2_ptoker const * st, - int code, char const * fmt, - va_list vargs ){ - int rc = 0; - char const * name; - cwal_size_t nameLen = 0; - cwal_error * const err = &s2__err(se); - cwal_buffer * obuf = &err->msg; - s2_ptoker const * top = s2_ptoker_top_parent( st ); - char const * errPos = se->opErrPos - ? se->opErrPos - : s2_ptoker_err_pos(st); - s2_linecol_t line = 0, col = 0; - assert(errPos); - if(errPos>=s2_ptoker_end(st) - && s2_ptoker_end(st) > s2_ptoker_begin(st)){ - /* Shameless workaround for syntax errors at - the end of a script */ - errPos = s2_ptoker_end(st)-1; - } - s2_engine_err_reset(se); - err->code = code ? code : CWAL_RC_EXCEPTION; - name = s2_ptoker_name_first(st, &nameLen); - /* expand source range to include parent parsers, - so that we get the right line/column numbers. - */ - if(errPos>=s2_ptoker_begin(top) && errPoscurrentScript - /* ^^^^^^ elide location info from error string when it looks - like a script-side exception is up-coming */ - ) { - char const * tailPart = ": "; - if(name){ - rc = cwal_buffer_printf( se->e, obuf, "%s:", name ); - } - if(!rc && errPos < s2_ptoker_end(top)){ - if(name){ - rc = cwal_buffer_printf( se->e, obuf, - "%d:%d%s", - line, col, tailPart); - }else{ - rc = cwal_buffer_printf( se->e, obuf, - "line %d, col %d%s", - line, col, tailPart); - } - }else if(errPos == s2_ptoker_end(top)){ - rc = cwal_buffer_printf( se->e, obuf, - "@ EOF%s", tailPart); - }else{ - rc = cwal_buffer_printf( se->e, obuf, - "@ unknown source position%s", - tailPart); - } - } - - if(!rc){ - if(fmt && *fmt){ - rc = cwal_buffer_printfv(se->e, obuf, fmt, vargs); - }else{ - rc = cwal_buffer_printf(se->e, obuf, - "Error #%d (%s)%s%s", - code, cwal_rc_cstr(code), - (st->errMsg ? ": " : ""), - st->errMsg ? st->errMsg : ""); - } - } - err->line = line; - err->col = col; - err->script.used = 0; - if(!rc && name){ - rc = cwal_buffer_append( se->e, &err->script, name, nameLen ); - } - if(!rc && throwIt){ - rc = s2_throw_err(se, err, name, line, col); - } - return s2_check_interrupted(se, rc ? rc : err->code); -} - -int s2_err_ptoker( s2_engine * se, s2_ptoker const * st, - int code, char const * fmt, ... ){ - int rc; - va_list args; - va_start(args,fmt); - rc = s2_err_ptoker_impl(se, 0, st, code, fmt, args); - va_end(args); - return rc; -} - -int s2_throw_ptoker( s2_engine * se, s2_ptoker const * st, - int code, char const * fmt, ... ){ - int rc; - va_list args; - va_start(args,fmt); - rc = s2_err_ptoker_impl(se, 1, st, code, fmt, args); - va_end(args); - return rc; -} - - -int s2_slurp_braces( s2_engine *se, s2_ptoker * st, - s2_ptoken * out ){ - int const opener = st->token.ttype; - int closer; - int rc = 0; - int level = 1; - s2_ptoken origSrc; - s2_ptoken origPb; - int adjustedOpener = opener; - char const * typeLabel = 0; - char const * end = 0; - if(!out) out = &st->token; - switch(opener){ - case S2_T_ParenOpen: - closer = S2_T_ParenClose; - adjustedOpener = S2_T_ParenGroup; - typeLabel = "(PARENS)"; - break; - case S2_T_BraceOpen: - closer = S2_T_BraceClose; - adjustedOpener = S2_T_BraceGroup; - typeLabel = "[BRACES]"; - break; - case S2_T_SquigglyOpen: - adjustedOpener = S2_T_SquigglyBlock; - closer = S2_T_SquigglyClose; - typeLabel = "{SQUIGGLY}"; - break; - default: - return s2_err_ptoker(se, st, CWAL_RC_TYPE, - "Invalid token type #%d (%s) for " - "s2_slurp_braces()", - opener, s2_ttype_cstr(opener)); - } - origSrc = st->token; - origPb = *s2_ptoker_putback_get(st); - switch(opener){ - case S2_T_SquigglyOpen: - /* - consider: { ..., <<token; - for( ; (0==(rc=s2_next_token(se, st, 0, 0))); ){ - /* - consider: ( ..., <<ttype)){ - end = 0; - break; - }else{ - if(opener == tok->ttype){ - ++level; - errTok = *tok; - } - else if(closer == tok->ttype){ - assert(level>0); - if(!--level) break; - } - } - } - if(!rc && 0 != level){ - s2_ptoker_errtoken_set(st, &errTok); - assert(typeLabel); - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Unexpected EOF while slurping %s block.", - typeLabel); - } - break; - }/*Parens/Braces*/ - }/* switch */ - - if(!rc && !end){ - s2_ptoker_errtoken_set(st, &origSrc); - -#if 0 - MARKER(("slurped <<%.*s>>\n", - /* (int)s2_ptoken_len2(out), s2_ptoken_adjbegin(out), */ - (int)(s2_ptoken_end(&s2_ptoker_token(st)) - s2_ptoken_begin(&origSrc)), - s2_ptoken_begin(&origSrc))); -#endif - - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Unexpected EOF or mismatched braces " - "while slurping %s block.", - typeLabel); - }else if(!rc){ - assert(end); - out->ttype = adjustedOpener; - s2_ptoken_begin_set(out, s2_ptoken_begin(&origSrc)); - s2_ptoken_adjbegin_set(out, s2_ptoken_begin(out)); - s2_ptoken_end_set(out, end); - assert(s2_ptoken_len(out) >= 2); - assert(opener == (int)*s2_ptoken_begin(out)); - assert(closer == (int)*(s2_ptoken_end(out)-1)); - /* Skip leading whitespaces */ - while(((s2_ptoken_adjbegin_incr(out, 1)) < (end-1)) - && s2_is_space((int)*s2_ptoken_adjbegin(out))){ - } - /* Skip trailing whitespaces */ - s2_ptoken_adjend_set(out, end - 1/* == closer */); - while( ((s2_ptoken_adjend_incr(out,-1)) > s2_ptoken_adjbegin(out)) - && s2_is_space((int)*s2_ptoken_adjend(out)) ){ - } - s2_ptoken_adjend_set(out, (s2_ptoken_adjend(out)+1))/* possibly back to the '}' byte */; - out->ttype = adjustedOpener; - rc = 0; -#if 0 - MARKER(("slurped <<%.*s>> out->adjEnd=%d\n", - /* (int)s2_ptoken_len2(out), s2_ptoken_adjbegin(out), */ - (int)s2_ptoken_len(out), s2_ptoken_begin(out), - *s2_ptoken_adjend(out))); -#endif - assert(opener == *s2_ptoken_begin(out)); - assert(closer == *(s2_ptoken_end(out)-1)); - assert( s2_ptoken_adjend(out) >= s2_ptoken_adjbegin(out) ); - assert( s2_ptoken_adjend(out) <= s2_ptoken_end(out) ); - } - if(out != &st->token){ - s2_ptoker_token_set(st, &origSrc); - s2_ptoker_putback_set(st, &origPb); - } - return rc; -} - - -int s2_slurp_heredoc( s2_engine * se, s2_ptoker * st, s2_ptoken * tgt ){ - int rc = 0; - char const * docBegin; - char const * docEnd; - char const * idBegin; - char const * idEnd; - char const * theEnd = NULL; - s2_ptoken const origin = st->token; - s2_ptoken tId = s2_ptoken_empty; - cwal_size_t idLen; - int modeFlag = 0; - assert(S2_T_HeredocStart==st->token.ttype); - if(!tgt) tgt = &st->token; - - rc = s2_ptoker_next_token(st); - if(rc) goto tok_err; - - if(S2_T_Colon==st->token.ttype){ - modeFlag = st->token.ttype; - rc = s2_ptoker_next_token(st); - if(rc) goto tok_err; - } - - switch(st->token.ttype){ - case S2_T_Identifier: - case S2_T_LiteralStringDQ: - case S2_T_LiteralStringSQ: - break; - default: - rc = CWAL_SCR_SYNTAX; - st->errMsg = "Expecting identifier or " - "quoted string " - "at start of HEREDOC."; - goto tok_err; - } - tId = st->token; - idBegin = s2_ptoken_begin(&tId); - idEnd = s2_ptoken_end(&tId); - idLen = s2_ptoken_len(&tId); - docBegin = idEnd; - switch(modeFlag){ - case S2_T_Colon: - if(('\n'==*docBegin || s2_is_space((int)*docBegin)) - || ('\r'==*docBegin && '\n'==docBegin[1])){ - docBegin += '\r'==*docBegin ? 2 : 1; - ++st->currentLine; - st->currentCol = 0; - } - break; - default: - while(s2_is_space((int)*docBegin)){ - if('\n'==*docBegin){ - ++st->currentLine; - st->currentCol = 0; - }else{ - ++st->currentCol; - } - ++docBegin; - } - } - rc = CWAL_SCR_SYNTAX; - for( docEnd = docBegin; docEnd < s2_ptoker_end(st); ++docEnd ){ - if(*docEnd != *idBegin){ - if('\n'==*docEnd){ - ++st->currentLine; - st->currentCol = 0; - }else{ - ++st->currentCol; - } - continue; - } - else if(0 == memcmp( docEnd, idBegin, idLen)){ - char const * back = docEnd-1; - theEnd = docEnd + idLen; - switch(modeFlag){ - case S2_T_Colon: - if(back>=docBegin && ('\n'==*back || s2_is_space((int)*back))) --docEnd; - break; - default: - for( ; back>=docBegin && s2_is_space((int)*back); --back) --docEnd; - } - rc = 0; - st->currentCol += (s2_linecol_t)idLen; - break; - } - } - if(rc){ - s2_ptoker_errtoken_set(st, &tId); - rc = s2_err_ptoker(se, st, - rc, "Did not find end of HEREDOC " - "starting with '%.*s'.", - (int)idLen, idBegin); - }else{ - s2_ptoken_begin_set(tgt, s2_ptoken_begin(&origin)); - s2_ptoken_end_set(tgt, theEnd); - tgt->ttype = S2_T_Heredoc; - s2_ptoken_adjbegin_set(tgt, docBegin); - s2_ptoken_adjend_set(tgt, docEnd); - tgt->line = origin.line; - tgt->column = origin.column; - assert(docEnd>=docBegin); -#if 0 - MARKER(("HEREDOC<%.*s> body: %.*s\n", - (int)(idEnd-idBegin), idBegin, - (int)(docEnd-docBegin), docBegin)); -#endif - } - return rc; - tok_err: - assert(rc); - assert(st->errMsg); - rc = s2_err_ptoker(se, st, rc, "%s", st->errMsg); - return rc; -} - - -int s2_ptoker_next_is_ttype( s2_engine * se, s2_ptoker * pr, int nextFlags, int ttype, char consumeIt ){ - s2_ptoken next = s2_ptoken_empty; - s2_next_token( se, pr, nextFlags, &next); - if(next.ttype==ttype){ - if(consumeIt){ - s2_ptoker_token_set(pr, &next); - } - return ttype; - }else{ - s2_ptoker_next_token_set(pr, &next); - return 0; - } -} - -static int s2_next_token_eol_skipper( int ttype ){ - switch(ttype){ - case S2_T_EOL: - case S2_T_NL: - return ttype; - default: - return s2_ttype_is_junk(ttype); - } -} - -static int s2_next_token_eol_noskipper( int ttype ){ - switch(ttype){ - case S2_T_EOL: - case S2_T_NL: - return 0; - default: - return s2_ttype_is_junk(ttype); - } -} - - -int s2_next_token( s2_engine * se, s2_ptoker * st, - int flags, - s2_ptoken * tgt ){ - s2_ptoken tt = s2_ptoken_empty; - s2_ptoken const oldT = st->token; - s2_ptoken const oldP = *s2_ptoker_putback_get(st); - s2_ttype_predicate_f const skipper = - (S2_NEXT_NO_SKIP_EOL & flags) - ? s2_next_token_eol_noskipper - : s2_next_token_eol_skipper; - int rc; - ++se->metrics.nextTokenCalls; - /* s2_engine_err_reset(se); */ - rc = s2_ptoker_lookahead_skip( st, &tt, skipper); - rc = s2_check_interrupted(se, rc); - if(rc){ - assert(st->errMsg || se->flags.interrupted); - return se->flags.interrupted - ? se->flags.interrupted - : (s2__err(se).code - ? s2__err(se).code - : s2_err_ptoker( se, st, rc, "%s", st->errMsg )) - ; - }else{ - s2_ptoker_token_set(st, &tt); - switch((S2_NEXT_NO_POSTPROCESS & flags) ? 0 : tt.ttype){ - case S2_T_Semicolon: - st->token.ttype = S2_T_EOX; - break; - case S2_T_CR: - assert(!"skipped by the junk-skipper!"); - CWAL_SWITCH_FALL_THROUGH; - case S2_T_NL: - assert( S2_NEXT_NO_SKIP_EOL & flags ); - st->token.ttype = S2_T_EOL; - break; - case S2_T_SquigglyOpen: - case S2_T_ParenOpen: - case S2_T_BraceOpen: - /* Group these at the tokenization level to simplify - evaluation. This completely removes open/close - parens/braces handling from the eval side by evaluating - these groups as a Value by recursively eval'ing their - content. - */ - rc = s2_slurp_braces(se, st, 0 ); - break; - case S2_T_HeredocStart: - rc = s2_slurp_heredoc(se, st, 0); - assert(rc ? 1 : S2_T_Heredoc==st->token.ttype); - break; - default: - break; - } - } - if(rc && !s2_ptoker_errtoken_has(st)){ - s2_ptoker_errtoken_set(st, &st->token); - } - if(tgt){ - *tgt = st->token; - s2_ptoker_token_set(st, &oldT); - s2_ptoker_putback_set(st, &oldP); - }else if(!rc){ - s2_ptoker_putback_set(st, &oldT); - } - return s2_check_interrupted(se, rc); -} - - -int s2_ptoken_create_value( s2_engine * se, - s2_ptoker const * pr, - s2_ptoken const * t, - cwal_value ** rv ){ - int rc = CWAL_RC_TYPE; - cwal_value * v = NULL; - cwal_engine * e = se->e; -#define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM - switch(t->ttype){ - case S2_T_LiteralIntBin: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntHex: - case S2_T_LiteralIntOct:{ - cwal_int_t dd = 0; - char const check = s2_ptoken_parse_int(t, &dd); - assert(check && - "If this is false, then there's a mismatch " - "between the tokenizer and converter."); - if(check){/*avoid unused var warning in non-debug builds*/} - v = cwal_new_integer(e, dd); - RC; - break; - } - case S2_T_LiteralDouble:{ - cwal_double_t dd = 0; - s2_ptoken_parse_double( t, &dd); - v = cwal_new_double(e, dd); - RC; - break; - } - case S2_T_LiteralStringSQ: - case S2_T_LiteralStringDQ:{ - cwal_buffer * escapeBuf = &se->buffer; - cwal_size_t const oldUsed = escapeBuf->used; - cwal_size_t newLen; - char const * begin; - cwal_kvp * kvp = 0; - assert(s2_ptoken_len(t)>=2 /* for the quotes */); - rc = s2_unescape_string(e, - s2_ptoken_begin(t) + 1 /*quote*/, - s2_ptoken_end(t) - 1 /*quote*/, - escapeBuf ); - if(rc){ - rc = s2_err_ptoker(se, pr, CWAL_RC_RANGE==rc ? CWAL_SCR_SYNTAX : rc, - /* allow catch() to block ^^^^^ this! */ - "Unescaping string failed with rc=%s, " - "likely due to non-UTF8 content " - "or an unknown \\Uxxxxxxxx sequence.", - cwal_rc_cstr(rc)) - /** - 20191220: whether or not this should trigger - s2_throw_ptoker() (exception) or s2_err_ptoker() (fatal) - is debatable. It seems unfortunate that an invalid \U - should outright kill a script, especially if it arrives - via input from outside the script. Treating it as a - non-exception using the code CWAL_SCR_SYNTAX, we enable - catch{...} to optionally downgrade it to an exception. - */; - escapeBuf->used = oldUsed; - break; - } - assert(escapeBuf->used >= oldUsed); - - /*MARKER("STRING: [%.*s]\n", (int)(escapeBuf->used - oldUsed), - (char const *)escapeBuf->mem+oldUsed);*/ - assert(0 == escapeBuf->mem[escapeBuf->used]); - newLen = escapeBuf->used - oldUsed; - begin = newLen ? (char const *)(escapeBuf->mem+oldUsed) : 0; - /* Check if we have a stashed string with this value. If so, use it.*/ - kvp = (begin && newLen<=10 - /*minor ^^^^^^^^^^^ optimization: max length of any of the - * core cached strings*/) - ? s2_stash_get2_kvp(se, begin, newLen) - : 0; - if(kvp){ - v = cwal_kvp_key(kvp) /* the KEY, not the value, else we end up - doing really stupid stuff! */; -#if 0 - MARKER(("Reusing stashed string: %.*s\n", (int)newLen, begin )); -#endif - } - else{ - v = cwal_new_string_value(e, begin, newLen); - } - escapeBuf->used = oldUsed; - /* s2_dump_val(v,"string literal"); */ - RC; - break; - } - case S2_T_SquigglyBlock:{ - cwal_size_t const len = s2_ptoken_len2(t); - char const * beg = s2_ptoken_adjbegin(t); - char const * end = s2_ptoken_adjend(t); - assert(beg); - assert(end); - assert(beg <= end); - v = cwal_new_string_value(e, beg, len); - /*MARKER(("SquigglyBlock ==> cwal_string: '{' %s '}'\n", - cwal_value_get_cstr(v, 0)));*/ - RC; - break; - } - default:{ - /* Use the raw token bytes as a string... */ - cwal_size_t const len = s2_ptoken_len2(t); - char const * begin = s2_ptoken_begin2(t); - char const * end = s2_ptoken_end2(t); - cwal_kvp * kvp; - assert(begin && end && begin <= end); - /* If se->stash has a matching string in it, simply return - that. If code calling this is too lax/lazy with refs, this - will backfire horribly, as they fetch a value from here, - thinking it's new, then deref it. */ - /* MARKER("STRING: [%.*s]\n", (int)(end - begin), begin); */ - /* MARKER("STRING: len=%ld\n", (long) (end - begin) ); */ - kvp = len ? s2_stash_get2_kvp(se, begin, len) : 0; - if(kvp){ - v = cwal_kvp_key(kvp); - /* MARKER(("got stashed string: %.*s\n", (int)len, begin)); */ - /* s2_dump_val(v,"got stashed string?"); */ - } - else if(!v){ - /* didn't find a stashed copy, so create a new string... */ - v = cwal_new_string_value(e, begin, len); - } - /* s2_dump_val(v,"stringified token"); */ - RC; - break; - } - } - if(!rc) { - assert(v); - *rv = v; - }else{ - assert(!v); - } - return rc; -#undef RC -}/* s2_ptoken_create_value() */ - -#if 0 -/** @internal - - Deprecated, currenly unused. - - A helper for binary and unary operator overloading. All of the - pointer arguments must point to valid memory. - - This routine looks for an operator Function in one of lhs or rhs - (as described below) and, if found, calls it. opName is the name - of the operator (the property name in script space) and opLen - must be the non-0 length of that operator. - - Binary mode: - - If neither lhs nor rhs are NULL then: - - - the operator is treated like a binary call with lhs as the 'this' - for the call and parameters (lhs, rhs) - - - Unary prefix mode: - - If lhs is NULL then: - - - rhs must not be NULL - - - the operator is assumed to be treated like a unary prefix call on the - rhs, and the operator function (if any) is called with rhs as the 'this' - and _no_ arguments. - - - Unary suffix mode: - - If lhs is not NULL and rhs is: - - - assume the op is a unary suffix operator for lhs and call it with - lhs as 'this' and itself as an arg. - - If lhs (or rhs) has no such function, or finds a non-function, - *theRc is set to 0 and 0 is returned. - - - If it finds a function, it calls that function and sets *rv to its - result value, *theRc to its return value, then returns non-0. - - On any error (running the function or fetching the operator) then - *theRc is set to the error value and this function will return 0 - if the operator was not found or not called, and non-0 if it was - called (in which case *theRc came from the operator function). -*/ -char s2_value_op_proxy( s2_engine * se, char const * opName, - cwal_size_t opLen, cwal_value * lhs, - cwal_value * rhs, - cwal_value **rv, int * theRc ); -char s2_value_op_proxy( s2_engine * se, char const * opName, cwal_size_t opLen, - cwal_value * lhs, cwal_value * rhs, - cwal_value **rv, int * theRc ){ - cwal_value * fv = 0; - int rc; - cwal_value * theThis = lhs ? lhs : rhs; - cwal_function * fn; - assert(se && se->e); - assert(opName && opLen); - assert(theRc); - assert(rv); - assert(lhs || rhs); - /* - Check for member function... - */ - rc = s2_get( se, theThis, opName, opLen, &fv ); - if(rc || !fv){ - *theRc = rc; - return 0; - } - else if((fn = cwal_value_function_part(se->e, fv))){ - cwal_value * argv[2] = {0,0}; - int argc = (lhs && rhs) ? 2 : (lhs ? 1 : 0); - argv[0] = argc ? lhs : 0; - argv[1] = argc ? rhs : 0; - /* s2_dump_val(theThis,opName); */ - /* s2_dump_val(argv[0],opName); */ - /* s2_dump_val(argv[1],opName); */ - *theRc = cwal_function_call( fn, theThis, rv, argc, argv ); - return 1; - }else{ - /* A non-function value with that name? */ - *theRc = 0; - return 0; - } -} -#endif -/* currently unused, but potentially still useful, code */ - -int s2_value_to_buffer( cwal_engine * e, cwal_buffer * buf, cwal_value * arg ){ - int rc = 0; - switch(cwal_value_type_id(arg)){ - case CWAL_TYPE_STRING:{ - cwal_string const * s = cwal_value_get_string(arg); - rc = cwal_buffer_append( e, buf, cwal_string_cstr(s), - cwal_string_length_bytes(s) ); - break; - } - case CWAL_TYPE_BOOL: { - char const b = cwal_value_get_bool(arg); - rc = cwal_buffer_append( e, buf, - b ? "true" : "false", - b ? 4 : 5); - break; - } - case CWAL_TYPE_UNDEF: - rc = cwal_buffer_append( e, buf, "undefined", 9); - break; - case CWAL_TYPE_NULL: - rc = cwal_buffer_append( e, buf, "null", 4); - break; - case CWAL_TYPE_INTEGER: - rc = cwal_buffer_printf( e, buf, "%"CWAL_INT_T_PFMT, - cwal_value_get_integer(arg)); - break; - case CWAL_TYPE_DOUBLE: - rc = cwal_buffer_printf( e, buf, "%"CWAL_DOUBLE_T_PFMT, - cwal_value_get_double(arg)); - if(!rc){ - /* Trim trailing zeroes... */ - unsigned char * pos = buf->mem + buf->used - 1; - while(pos>buf->mem && '0' == *pos && '.' != *(pos-1)) { - *pos = 0; - --pos; - --buf->used; - } - } - break; - case CWAL_TYPE_BUFFER:{ - cwal_buffer const * vb = cwal_value_get_buffer(arg); - assert(vb); - if(vb->used){ - rc = cwal_buffer_reserve( e, buf, buf->used + vb->used + 1 ) - /* Preallocation required in case buf===vb */; - if(!rc){ - if(vb==buf){ - memmove(buf->mem + buf->used , vb->mem, vb->used); - buf->used *= 2; - buf->mem[buf->used] = 0; - break; - }else{ - rc = cwal_buffer_append( e, buf, vb->mem, vb->used ); - } - } - } - break; - } - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_TUPLE:{ - cwal_output_buffer_state job = cwal_output_buffer_state_empty; - job.e = e; - job.b = buf; - rc = cwal_json_output( arg, cwal_output_f_buffer, &job, NULL ); - break; - } - case CWAL_TYPE_HASH: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_UNIQUE: - rc = cwal_buffer_printf(e, buf, "%s@%p", - cwal_value_type_name(arg), - (void const*)arg); - break; - default: - rc = cwal_exception_setf( e, CWAL_RC_TYPE, - "Don't know how to to-string arguments " - "of type '%s'.", - cwal_value_type_name(arg)); - break; - } - return rc; -} - -int s2_cb_value_to_string( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_buffer buf = cwal_buffer_empty; - rc = s2_value_to_buffer(args->engine, &buf, args->self); - if(!rc - && !(*rv = cwal_string_value(cwal_buffer_to_zstring( args->engine, &buf))) - ){ - rc = CWAL_RC_OOM; - } - cwal_buffer_reserve(args->engine, &buf, 0); - return rc; -} - -static int s2_cb_to_json_token_impl( cwal_callback_args const * args, - cwal_value **rv, - char useSelf - /*0=use args->argv[0], else args->self*/ - ){ - if(!args->argc && !useSelf){ - misuse: - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "'toJSONString' requires one argument."); - }else{ - int rc = 0; - cwal_value * v = NULL; - cwal_value * vIndent; - cwal_json_output_opt outOpt = cwal_json_output_opt_empty; - cwal_buffer buf = cwal_buffer_empty; - cwal_buffer * jb = NULL; - cwal_size_t oldUsed; - char selfBuf = 0; - int indentIndex = -1; - int cyclesIndex = -1; - if(useSelf){ - jb = cwal_value_buffer_part(args->engine, args->self); - /* Change semantics when this===Buffer */ - v = jb - ? (args->argc ? args->argv[0] : NULL) - : args->self; - indentIndex = jb ? 1 : 0; - } - else{ - assert(args->argc); - v = args->argv[0]; - indentIndex = 1; - } - if(!v) goto misuse; - cyclesIndex = indentIndex + 1; - assert(indentIndex >= 0); - vIndent = (indentIndex >= (int)args->argc) - ? NULL - : args->argv[indentIndex]; - outOpt.cyclesAsStrings = (cyclesIndex < (int)args->argc) - ? cwal_value_get_bool(args->argv[cyclesIndex]) - : 0; - selfBuf = jb ? 1 : 0; - if(!selfBuf) jb = &buf; - oldUsed = jb->used; - if(vIndent){ - /* Accept indentation as either a string, an integer, or an enum - entry which wraps one of those. - */ - vIndent = s2_value_unwrap(vIndent); - if(!(outOpt.indentString.str = - cwal_value_get_cstr(vIndent, &outOpt.indentString.len))){ - outOpt.indent = cwal_value_get_integer(vIndent); - } - } - rc = cwal_json_output_buffer( args->engine, v, - jb, &outOpt ); - if(CWAL_RC_CYCLES_DETECTED==rc){ - rc = cwal_exception_setf(args->engine, rc, - "Cycles detected in JSON output."); - }else if(!rc){ - v = selfBuf - /* ? cwal_new_integer(args->engine, - (cwal_int_t)(jb->used - oldUsed))*/ - ? args->self - /* TODO? Use a z-string and transfer the buffer memory? */ - : cwal_new_string_value( args->engine, - ((char const *)jb->mem + oldUsed), - jb->used - oldUsed ); - if(!v) rc = CWAL_RC_OOM; - else *rv = v; - } - if(buf.mem) cwal_buffer_reserve( args->engine, &buf, 0 ); - return rc; - } -} - -int s2_cb_this_to_json_token( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_to_json_token_impl( args, rv, 1 ); -} - -int s2_cb_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_to_json_token_impl( args, rv, 0 ); -} - - - - -int s2_cb_value_compare( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_value * lhs; - cwal_value * rhs; - if(!args->argc || (args->argc>3)){ - return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, - "Expecting (value [,value [,bool typeStrict]]) arguments."); - } - lhs = (args->argc > 1) - ? args->argv[0] - : args->self; - rhs = (args->argc > 1) - ? args->argv[1] - : args->argv[0]; - if(lhs==rhs){ - *rv = cwal_new_integer(args->engine, 0) /* does not allocate */; - return 0; - } - if((3==args->argc) && cwal_value_get_bool(args->argv[2])){ - cwal_type_id const tL = cwal_value_type_id(lhs); - cwal_type_id const tR = cwal_value_type_id(rhs); - if(tL != tR){ - rc = (int)(tL-tR); - *rv = cwal_new_integer(args->engine, (cwal_int_t)(tLengine, (cwal_int_t)(rc>0 ? 1 : (rc < 0 ? -1 : 0))) - /* does not allocate */; - return 0; -} - -cwal_engine * s2_engine_engine(s2_engine * se){ - return se ? se->e : 0; -} - - -void s2_engine_subexpr_restore(s2_engine * e, - s2_subexpr_savestate const * from){ - e->ternaryLevel = from->ternaryLevel; -} - -int s2_scope_push_with_flags( s2_engine * se, cwal_scope * tgt, - uint16_t scopeFlags ){ - int rc = s2_check_interrupted(se, 0); - if(rc) return rc; - assert(se); - assert(se->e); - assert(tgt); - assert(!tgt->level); - assert(!tgt->e); - if(!se || !se->e || !tgt || tgt->e || tgt->level) return CWAL_RC_MISUSE; - se->scopes.nextFlags = scopeFlags; - rc = cwal_scope_push( se->e, &tgt ); - if(!rc){ - assert(tgt->level); - assert(s2__scope_current(se)->flags == scopeFlags); - assert(tgt == s2__scope_for_level(se, tgt->level)->cwalScope); - assert(0 == se->scopes.nextFlags); - }else{ - se->scopes.nextFlags = 0; - } - return rc; -} - -s2_engine * s2_engine_from_state( cwal_engine * e ){ - return (s2_engine *) cwal_engine_client_state_get( e, &s2_engine_empty ); -} - -s2_engine * s2_engine_from_args( cwal_callback_args const * args ){ - return (s2_engine *) cwal_engine_client_state_get( args->engine, &s2_engine_empty ); -} - -static int s2_cb_json_parse_impl( cwal_callback_args const * args, - cwal_value **rv, char isFilename ){ - int rc; - cwal_value * root = NULL; - cwal_size_t slen = 0; - cwal_json_parse_info pInfo = cwal_json_parse_info_empty; - char const * cstr; - cwal_value * arg = args->argc ? args->argv[0] : args->self; - cwal_engine * e = args->engine; - if(isFilename && (rc = s2_cb_disable_check(args, S2_DISABLE_FS_READ))){ - return rc; - } - cstr = cwal_value_get_cstr(arg, &slen); - if(!cstr){ - cwal_buffer * b = cwal_value_buffer_part(e, arg); - if(b){ - cstr = (char const *)b->mem; - slen = b->used; - } - if(!cstr){ - return cwal_exception_setf(e, CWAL_RC_MISUSE, - "Expecting a %s as argument or 'this'.", - isFilename - ? "filename" : "JSON (string|Buffer)"); - } - } - rc = isFilename - ? cwal_json_parse_filename( e, cstr, &root, &pInfo ) - : cwal_json_parse_cstr( e, cstr, slen, &root, &pInfo ); - if(rc){ - if(pInfo.errorCode){ - return cwal_exception_setf(e, rc, - "Parsing JSON failed at byte " - "offset %"CWAL_SIZE_T_PFMT - ", line %"CWAL_SIZE_T_PFMT - ", column %"CWAL_SIZE_T_PFMT - " with code %d (%s).", - (cwal_size_t)pInfo.length, - (cwal_size_t)pInfo.line, - (cwal_size_t)pInfo.col, - (int)pInfo.errorCode, - cwal_rc_cstr(pInfo.errorCode)); - }else{ - return cwal_exception_setf(e, rc, - "Parsing JSON failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - } - } - assert(root); - *rv = root; - return 0; -} - -int s2_cb_json_parse_string( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_json_parse_impl(args, rv, 0); -} - -int s2_cb_json_parse_file( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_json_parse_impl(args, rv, 1); -} - -int s2_install_json( s2_engine * se, cwal_value * tgt, char const * key ){ - cwal_value * mod = NULL; - int rc; - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - - if(key && *key){ - mod = cwal_new_object_value(se->e); - if(!mod) return CWAL_RC_OOM; - if( (rc = cwal_prop_set(tgt, key, cwal_strlen(key), mod)) ){ - cwal_value_unref(mod); - return rc; - } - }else{ - mod = tgt; - } - - cwal_value_ref(mod); - { - s2_func_def const funcs[] = { - /* S2_FUNC2("experiment", s2_cb_container_experiment), */ - S2_FUNC2("parse", s2_cb_json_parse_string), - S2_FUNC2("parseFile", s2_cb_json_parse_file), - S2_FUNC2("stringify", s2_cb_arg_to_json_token), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, mod, funcs, 0); - if(!rc){ - /* Install mod.clone() convenience method */ - rc = s2_eval_cstr_with_var(se, "J", mod, "JSON module init", - "J.clone=proc()" - "using.{P:J.parse,S:J.stringify}" - "{return using.P(using.S(argv.0))}", - -1, 0); - } - } - if(rc){ - if(tgt==mod) cwal_value_unhand(mod); - else cwal_value_unref(mod); - }else{ - cwal_value_unhand(mod); - } - return rc; -} - -int s2_cb_getenv( cwal_callback_args const * args, cwal_value **rv ){ - cwal_size_t len = 0; - char const * key = args->argc - ? cwal_value_get_cstr(args->argv[0], &len) - : 0; - if(!key){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a string argument."); - }else{ - char const * val = getenv(key); - *rv = val - ? cwal_new_string_value(args->engine, val, cwal_strlen(val)) - /* ^^^ we "could" use x-strings and save a few bytes - of memory, but if we add setenv() we'd likely shoot - ourselves in the foot here. - */ - : cwal_value_undefined(); - return *rv ? 0 : CWAL_RC_OOM; - } -} - -int s2_cb_rc_hash( cwal_callback_args const * args, cwal_value **rv ){ - s2_engine * se = s2_engine_from_args(args); - int rc; - cwal_int_t i; - cwal_hash * h; - cwal_value * hv; - static const char * stashKey = "RcHash"; - assert(se); - hv = s2_stash_get(se, stashKey); - if(hv){ - *rv = hv; - return 0; - } - h = cwal_new_hash(args->engine, 137) - /*the installed set has 110 entries as of 20171013*/; - if(!h) return CWAL_RC_OOM; - hv = cwal_hash_value(h); - cwal_value_ref(hv); - for( i = 0; i <= (cwal_int_t)S2_RC_CLIENT_BEGIN; ++i ){ - char const * str = cwal_rc_cstr2(i); - if(str){ - cwal_value * k; - cwal_value * v = cwal_new_xstring_value(args->engine, - str, cwal_strlen(str)); - if(!v){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(v); - k = cwal_new_integer(args->engine, i); - if(!k){ - cwal_value_unref(v); - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(k); - rc = cwal_hash_insert_v( h, k, v, 0 ); - cwal_value_unref(k); - cwal_value_unref(v); - if(rc) break; -#if 1 - /* reverse mapping. */ - rc = cwal_hash_insert_v( h, v, k, 0 ); - if(rc) break; -#endif - } - } - if(rc){ - cwal_value_unref(hv); - }else{ - *rv = hv; - s2_stash_set( se, stashKey, hv ) - /* if this fails, no big deal */; - cwal_value_unhand(hv); - } - return 0; -} - -int s2_cb_new_unique( cwal_callback_args const * args, cwal_value **rv ){ - *rv = cwal_new_unique(args->engine, args->argc ? args->argv[0] : 0); - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_interrupt( s2_engine * se ){ - s2_ptoker const * pt = se->currentScript; - if(pt){ - /* fake an error pos which s2_err_ptoker() will see... */ - se->opErrPos = s2_ptoken_begin(&pt->token) - ? s2_ptoken_begin(&pt->token) : s2_ptoker_begin(pt); - s2_err_ptoker( se, pt, CWAL_RC_INTERRUPTED, - "Interrupted by s2_interrupt()."); - se->opErrPos = 0; - }else{ - s2_engine_err_set(se, CWAL_RC_INTERRUPTED, - "Interrupted by s2_interrupt()."); - } - return se->flags.interrupted = CWAL_RC_INTERRUPTED; -} - -#if S2_USE_SIGNALS -/** - s2_engine interruption via signals... - - Potential TODO: instead of manipulating an s2_engine instance - directly from the signal handler, which has potential - timing-related issues vis-a-vis s2_engine lifetime, simply set a - global flag here and check/consume it from the s2_engine APIs. The - disadvantage to that is that we get less precise interruption - location information (limited to where an engine explicitly checks - it), but that seems like a small price to pay for "more correct" - s2_engine lifetime interaction. -*/ -static void s2_sigc_handler(int s){ - if(s2Interruptable && !s2Interruptable->flags.interrupted){ - s2_engine * se = s2Interruptable; - s2_ptoker const * pt = se ? se->currentScript : 0; - s2Interruptable = 0 /* disable concurrent interruption */; - if(pt){ - cwal_error * const err = &s2__err(se); - MARKER(("Interruping via signal handler!\n")); - /* fake an error pos which s2_err_ptoker() will see... */ - se->opErrPos = s2_ptoken_begin(&pt->token) - ? s2_ptoken_begin(&pt->token) : s2_ptoker_begin(pt) - /* kludge for the following call to get an error location */; - s2_err_ptoker( se, pt, CWAL_RC_INTERRUPTED, - "Interrupted by signal #%d.", s); - se->opErrPos = 0; - if(err->line>0){ - /* We do this to help track down locations where interruption is - triggered but does not preempt the normal result code of the - interrupted operation like it should. - */ - MARKER(("Interrupt was at line %d, column %d of '%.*s'.\n", - err->line, err->col, - (int)err->script.used, (char const *)err->script.mem)); - } - }else if(se){ - s2_engine_err_set(se, CWAL_RC_INTERRUPTED, - "Interrupted by signal #%d.", s); - } - if(se){ - se->flags.interrupted = CWAL_RC_INTERRUPTED; - } - assert(!s2Interruptable); - s2Interruptable = se /* re-enable interruption */; - } -} -#endif -/* S2_USE_SIGNALS */ - -int s2_check_interrupted( s2_engine * se, int rc ){ - switch(rc){ - case CWAL_RC_ASSERT: - case CWAL_RC_CANNOT_HAPPEN: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_INTERRUPTED: - case CWAL_RC_OOM: - return rc; - default: - return se->e->fatalCode - ? (se->flags.interrupted = se->e->fatalCode) - : (se->flags.interrupted - ? se->flags.interrupted - : rc); - } -} - -void s2_set_interrupt_handlable( s2_engine * se ){ -#if S2_USE_SIGNALS - if((s2Interruptable = se)){ - struct sigaction sigIntHandler; - sigIntHandler.sa_handler = s2_sigc_handler; - sigemptyset(&sigIntHandler.sa_mask); - sigIntHandler.sa_flags = 0; - sigaction(SIGINT, &sigIntHandler, NULL); - } -#endif -} - -char s2_cstr_to_rc(char const *str, cwal_int_t len, int * code){ -#if 0 - /** - 2020-02-03: TODO (hash collisions permitting): this could be - implemented much more efficiently using the same hashing - mechanism we use for several pieces over in s2_eval.c. There's a - risk of hash collisions with a list that long, but it's worth a - try... - */ - if(len<0) len = (cwal_int_t)cwal_strlen(str); - if(str){ /* If passed a string, try (the hard way) to - find the integer code. */ - if((len>8 && 'C'==*str && 'W'==str[1]) /* CWAL_RC_... */ - || (len>6 && 'S'==*str && '2'==str[1]) /* S2_RC_... */ - ){ - int i = ('C'==*str) - ? 0 : CWAL_RC_CLIENT_BEGIN; - cwal_int_t const max = ('C'==*str) - ? CWAL_RC_CLIENT_BEGIN : S2_RC_CLIENT_BEGIN; - cwal_size_t elen; - char const * estr; - for( ; i <= max; - ++i ){ - estr = cwal_rc_cstr2(i); - if(estr && ((elen=cwal_strlen(estr)) == (cwal_size_t)len) - && 0==memcmp(str,estr,elen) - ){ - if(code) *code = i; - return 1; - } - } - } - } -#else - cwal_size_t const n = (len<0) ? cwal_strlen(str) : (cwal_size_t)len; - if(n<10/*length of shortest entry: CWAL_RC_OK*/) return 0; - switch(s2_hash_keyword(str, n)){ -#define W(RC) if((cwal_size_t)sizeof(#RC)-1 == n \ - && 0==cwal_compare_cstr(str, n, #RC, n)) \ - { *code = RC; return 1; } else return 0 - /* Generated by s2-keyword-hasher.s2 (or equivalent): */ - - case 0x00014848: W(CWAL_RC_OK); - case 0x000a4d67: W(CWAL_RC_ERROR); - case 0x000292ae: W(CWAL_RC_OOM); - case 0x000a4761: W(CWAL_RC_FATAL); - case 0x005266b4: W(CWAL_RC_CONTINUE); - case 0x000a47d0: W(CWAL_RC_BREAK); - case 0x0014a578: W(CWAL_RC_RETURN); - case 0x000525e8: W(CWAL_RC_EXIT); - case 0x00a4e143: W(CWAL_RC_EXCEPTION); - case 0x00149814: W(CWAL_RC_ASSERT); - case 0x0014a19c: W(CWAL_RC_MISUSE); - case 0x00a552b9: W(CWAL_RC_NOT_FOUND); - case 0x1493fc9a: W(CWAL_RC_ALREADY_EXISTS); - case 0x000a4d4e: W(CWAL_RC_RANGE); - case 0x00052a2e: W(CWAL_RC_TYPE); - case 0x029615ff: W(CWAL_RC_UNSUPPORTED); - case 0x001488a0: W(CWAL_RC_ACCESS); - case 0x02953c70: W(CWAL_RC_IS_VISITING); - case 0x52a80e30: W(CWAL_RC_IS_VISITING_LIST); - case 0x5259accb: W(CWAL_RC_DISALLOW_NEW_PROPERTIES); - case 0x5259b77c: W(CWAL_RC_DISALLOW_PROP_SET); - case 0x5259c2c8: W(CWAL_RC_DISALLOW_PROTOTYPE_SET); - case 0x2938b265: W(CWAL_RC_CONST_VIOLATION); - case 0x00149b62: W(CWAL_RC_LOCKED); - case 0x2936e803: W(CWAL_RC_CYCLES_DETECTED); - case 0x526013da: W(CWAL_RC_DESTRUCTION_RUNNING); - case 0x00a4b185: W(CWAL_RC_FINALIZED); - case 0x149b07dc: W(CWAL_RC_HAS_REFERENCES); - case 0x02941f9f: W(CWAL_RC_INTERRUPTED); - case 0x00a469dd: W(CWAL_RC_CANCELLED); - case 0x00014804: W(CWAL_RC_IO); - case 0x0a48d963: W(CWAL_RC_CANNOT_HAPPEN); - case 0x5297a321: W(CWAL_RC_JSON_INVALID_CHAR); - case 0x5297ae5a: W(CWAL_RC_JSON_INVALID_KEYWORD); - case 0x5297c0c2: W(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE); - case 0x5297c884: W(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE); - case 0x5297adcb: W(CWAL_RC_JSON_INVALID_NUMBER); - case 0x529835fe: W(CWAL_RC_JSON_NESTING_DEPTH_REACHED); - case 0x529901c6: W(CWAL_RC_JSON_UNBALANCED_COLLECTION); - case 0x52979d2b: W(CWAL_RC_JSON_EXPECTED_KEY); - case 0x5297a088: W(CWAL_RC_JSON_EXPECTED_COLON); - case 0x293361be: W(CWAL_SCR_CANNOT_CONSUME); - case 0x029429d7: W(CWAL_SCR_INVALID_OP); - case 0x52a35f1b: W(CWAL_SCR_UNKNOWN_IDENTIFIER); - case 0x5266c911: W(CWAL_SCR_CALL_OF_NON_FUNCTION); - case 0x5289396d: W(CWAL_SCR_MISMATCHED_BRACE); - case 0x528deb5d: W(CWAL_SCR_MISSING_SEPARATOR); - case 0x52a00aa1: W(CWAL_SCR_UNEXPECTED_TOKEN); - case 0x294ffd4d: W(CWAL_SCR_UNEXPECTED_EOF); - case 0x0527f4f2: W(CWAL_SCR_DIV_BY_ZERO); - case 0x00295497: W(CWAL_SCR_SYNTAX); - case 0x0005251c: W(CWAL_SCR_EOF); - case 0x52a959aa: W(CWAL_SCR_TOO_MANY_ARGUMENTS); - case 0x52859352: W(CWAL_SCR_EXPECTING_IDENTIFIER); - case 0x512547c8: W(S2_RC_END_EACH_ITERATION); - case 0x000146f8: W(S2_RC_TOSS); - -#undef W - } -#endif - return 0; -} - -void s2_value_upscope( s2_engine * se, cwal_value * v ){ - s2_scope * sc = s2__scope_current(se); - assert(sc); - if(sc && sc->cwalScope->parent){ -#if 1 - cwal_value_upscope(v); -#else - cwal_value_rescope(sc->cwalScope->parent, v); -#endif - } -} - -int s2_immutable_container_check_cb( cwal_callback_args const * args, cwal_value const * v ){ - return s2_immutable_container_check( s2_engine_from_args(args), v, 1 ); -} - -int s2_immutable_container_check( s2_engine * se, cwal_value const * v, int throwIt ){ - cwal_flags16_t const containerFlags = cwal_container_flags_get(v); - assert(se && se->e); - if(CWAL_CONTAINER_DISALLOW_PROP_SET & containerFlags){ - char const * fmt = "Setting/clearing properties is " - "disallowed on this container (of type '%s')."; - char const * typeName = cwal_value_type_name(v); - if(!typeName) typeName = ""; - if(throwIt){ - return s2_throw(se, CWAL_RC_DISALLOW_PROP_SET, - fmt, typeName); - }else{ - return s2_engine_err_set(se, CWAL_RC_DISALLOW_PROP_SET, - fmt, typeName); - } - } - return 0; -} - -int s2_install_value( s2_engine *se, cwal_value * tgt, - cwal_value * v, - char const * name, - int nameLen, - uint16_t propertyFlags ){ - int rc; - cwal_value * vname; - if(nameLen<0) nameLen = (int)cwal_strlen(name); - if(!se || !v || !name) return CWAL_RC_MISUSE; - else if(!nameLen) return CWAL_RC_RANGE; - else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE; - vname = cwal_new_string_value(se->e, name, (cwal_size_t)nameLen); - if(!vname) return 0; - /** - We use s2_set_xxx() to honor the dot-like-object flag on hashes. - - OTOH: 1) that's the only place in this function we use the - s2_engine object and 2) the dot-like-object bits have been - removed from the public scripting API and are internally used - only for enums (which are immutable, so we can't install - functions into them). 3) we can derive an s2_engine from a - cwal_engine with s2_engine_from_state(). That argues for - changing this signature to take a cwal_engine instead. We'll - try that one day. - */ - cwal_value_ref(vname); - rc = tgt - ? s2_set_with_flags_v( se, tgt, vname, v, propertyFlags ) - : cwal_scope_chain_set_with_flags_v(cwal_scope_current_get(se->e), - 0, vname, v, propertyFlags); - cwal_value_unref(vname); - return rc; -} - -int s2_install_callback( s2_engine *se, cwal_value * tgt, - cwal_callback_f callback, - char const * name, - int nameLen, - uint16_t propertyFags, - void * state, - cwal_finalizer_f stateDtor, - void const * stateTypeID ){ - int rc = 0; - cwal_value * fv; - if(!se || !callback) return CWAL_RC_MISUSE; - else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE; - fv = cwal_new_function_value(se->e, callback, state, stateDtor, - stateTypeID); - if(fv){ - cwal_value_ref(fv); - rc = s2_install_value( se, tgt, fv, name, nameLen, propertyFags ); - cwal_value_unref(fv); - }else{ - rc = CWAL_RC_OOM; - } - return rc; -} - - -int s2_install_functions( s2_engine *se, cwal_value * tgt, - s2_func_def const * defs, - uint16_t propertyFlags ){ - int rc = 0; - if(!se || !defs) return CWAL_RC_MISUSE; - else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE; - for( ; !rc && defs->name; ++defs ){ - cwal_value * fv = cwal_new_function_value( se->e, defs->callback, - defs->state, - defs->stateDtor, - defs->stateTypeID); - if(!fv){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(fv); - rc = s2_install_value( se, tgt, fv, defs->name, -1, - propertyFlags ); - if(!rc && defs->cwalContainerFlags){ - cwal_container_client_flags_set( fv, defs->cwalContainerFlags ); - } - cwal_value_unref(fv); - } - return rc; -} - -int s2_container_config( cwal_value * v, char allowPropSet, - char allowNewProps, - char allowGetUnknownProps ){ - if(!cwal_props_can(v)) return CWAL_RC_TYPE; - else{ - cwal_flags16_t containerFlags = cwal_container_flags_get(v); - cwal_flags16_t clientFlags = cwal_container_client_flags_get(v); - if(allowPropSet){ - containerFlags &= ~CWAL_CONTAINER_DISALLOW_PROP_SET; - }else{ - containerFlags |= CWAL_CONTAINER_DISALLOW_PROP_SET; - } - if(allowNewProps){ - containerFlags &= ~CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES; - }else{ - containerFlags |= CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES; - } - if(allowGetUnknownProps){ - clientFlags &= ~S2_VAL_F_DISALLOW_UNKNOWN_PROPS; - }else{ - clientFlags |= S2_VAL_F_DISALLOW_UNKNOWN_PROPS; - } - cwal_container_flags_set(v, containerFlags); - cwal_container_client_flags_set(v, clientFlags); - return 0; - } -} - - -/** @internal - - Expects cachedKey to be an entry from se->cache or NULL. ckey/keyLen describe - an input string to compare against the final argument. This function returns - true (non-0) if the final argument compares string-equivalent to (ckey,keyLen) - OR is the same pointer address as the 2nd argument, else it returns false. -*/ -static char s2_value_is_cached_string( s2_engine const * se, - cwal_value const * cachedKey, - char const * ckey, cwal_size_t keyLen, - cwal_value const * key ){ - if(cachedKey == key) return 1; - else{ - cwal_size_t xkeyLen = 0; - char const * xkey = cwal_value_get_cstr(key, &xkeyLen); - if(se){/*avoid unused param warning*/} - return (xkeyLen==keyLen && *ckey==*xkey && 0==cwal_compare_cstr( ckey, keyLen, - xkey, keyLen)) - ? 1 : 0; - } -} - -char s2_value_is_value_string( s2_engine const * se, cwal_value const * key ){ - return s2_value_is_cached_string( se, se->cache.keyValue, "value", 5, key ); -} - -char s2_value_is_prototype_string( s2_engine const * se, cwal_value const * key ){ - return s2_value_is_cached_string( se, se->cache.keyPrototype, "prototype", 9, key ); -} - - -void s2_hash_dot_like_object( cwal_value * hv, int dotLikeObj ){ - /* cwal_hash * hash = cwal_value_get_hash(hv); */ - uint16_t f = cwal_container_client_flags_get(hv); - cwal_container_client_flags_set(hv, - dotLikeObj - ? (S2_VAL_F_DOT_LIKE_OBJECT | (f & S2_VAL_F_COMMON_MASK)) - : (~S2_VAL_F_DOT_LIKE_OBJECT & f)); -#if !defined(NDEBUG) - assert(dotLikeObj - ? (S2_VAL_F_DOT_LIKE_OBJECT & cwal_container_client_flags_get(hv)) - : !(S2_VAL_F_DOT_LIKE_OBJECT & cwal_container_client_flags_get(hv)) - ); - f = cwal_container_client_flags_get(hv); - if(dotLikeObj){ - assert(S2_VAL_F_MODE_DOT == (f & S2_VAL_F_MODE_MASK)); - assert((S2_VAL_F_DOT_LIKE_OBJECT & 0xFF) == (f & 0xFF)); - }else{ - assert(S2_VAL_F_MODE_DOT != (f & S2_VAL_F_MODE_MASK)); - assert((S2_VAL_F_DOT_LIKE_OBJECT & 0xFF) != (f & 0xFF)); - } -#endif -} - -/** - Internal impl for s2_minify_script() and friends. -*/ -static int s2_minify_script_impl( s2_engine * se, cwal_buffer const * src, - cwal_output_f dest, void * destState, - unsigned int * nlCount ){ - int rc = 0; - s2_ptoker pr = s2_ptoker_empty; - s2_ptoken prev = s2_ptoken_empty; - s2_ptoken const * t = &pr.token; - s2_ptoker const * oldScript = se->currentScript; - static const unsigned int nlEveryN = 3 /* output approx. 1 of every nlEveryN EOLs */; - - rc = s2_ptoker_init_v2( se->e, &pr, (char const *)src->mem, - (cwal_int_t)src->used, - 0 ); - if(rc) goto end; - for( ; !rc - && !(rc = s2_next_token(se, &pr, S2_NEXT_NO_SKIP_EOL, 0)) - && !s2_ptoker_is_eof(&pr); - prev = pr.token){ - assert(!s2_ttype_is_junk(t->ttype)); - /* - We have to keep (some) newlines so that block constructs which - optionally use EOL as EOX will work :/. - */ - if(s2_ttype_is_eol(t->ttype)){ - ++(*nlCount); - if(t->ttype==prev.ttype){ - /* elide runs of EOLs */ - continue; - } - } - else if(S2_T_SquigglyBlock==t->ttype - || S2_T_BraceGroup==t->ttype - || S2_T_ParenGroup==t->ttype){ - cwal_buffer kludge = cwal_buffer_empty; - s2_ptoker sub = s2_ptoker_empty; - char const * opener; - char const * closer; - s2_ptoker_sub_from_token(&pr, t, &sub); - kludge.mem = (unsigned char *)s2_ptoker_begin(&sub); - kludge.used = kludge.capacity = (cwal_size_t)s2_ptoker_len(&sub); - if(S2_T_BraceGroup==t->ttype){ - opener = "["; - closer = "]"; - }else if(S2_T_ParenGroup==t->ttype){ - opener = "("; - closer = ")"; - }else{ - opener = "{"; - closer = "}"; - } - rc = dest( destState, opener, 1 ); - if(!rc) rc = s2_minify_script_impl( se, &kludge, - dest, destState, nlCount ); - if(!rc) rc = dest( destState, closer, 1 ); - continue; - } - if(s2_ttype_is_eol(t->ttype) - && S2_T_SquigglyBlock!=prev.ttype - && (*nlCount % nlEveryN)){ - continue; /* we need EOLs intact after _some_ {blocks}, but - we can elide all others. We'll insert some now - and then, though, just for readability.*/ - } - if((s2_ptoken_empty.ttype != prev.ttype /* first pass */) - && !s2_is_space(t->ttype) - && !s2_ttype_is_eol(t->ttype) - && !s2_is_space(prev.ttype) - && !s2_ttype_op(prev.ttype) - && !s2_ttype_op(t->ttype) - && (int)':' != t->ttype /* for 2nd half of ternary if */ - /*&& s2_ttype_is_pod(t->ttype) - && s2_ttype_is_pod(prev.ttype)*/ - ){ - rc = dest( destState, " ", 1); - } - if(!rc){ - assert(s2_ptoken_begin(t) && s2_ptoken_end(t)); - assert(s2_ptoken_end(t) > s2_ptoken_begin(t)); - rc = dest( destState, s2_ptoken_begin(t), s2_ptoken_len(t) ); - } - } - end: - s2_ptoker_finalize(&pr); - se->currentScript = oldScript; - return rc; -} - -int s2_minify_script_buffer( s2_engine * se, cwal_buffer const * src, - cwal_buffer * dest ){ - if(src == dest) return CWAL_RC_MISUSE; - else{ - unsigned int nlCount = 0; - cwal_output_buffer_state job = cwal_output_buffer_state_empty; - job.e = se->e; - job.b = dest; - return s2_minify_script_impl( se, src, cwal_output_f_buffer, &job, - &nlCount ); - } -} - -int s2_minify_script( s2_engine * se, cwal_input_f src, void * srcState, - cwal_output_f out, void * outState ){ - cwal_buffer buf = cwal_buffer_empty; - int rc = cwal_buffer_fill_from(se->e, &buf, src, srcState); - if(!rc){ - unsigned int nlCount = 0; - cwal_buffer_clear(se->e, &buf); - rc = s2_minify_script_impl( se, &buf, out, outState, &nlCount ); - } - cwal_buffer_clear(se->e, &buf); - return rc; -} - -int s2_cb_minify_script( cwal_callback_args const * args, cwal_value ** rv ){ - s2_engine * se = s2_engine_from_args(args); - int rc = 0; - char const * src = 0; - cwal_size_t srcLen = 0; - cwal_buffer * dest = 0; - cwal_value * destV = 0; - assert(se); - src = args->argc - ? cwal_value_get_cstr(args->argv[0], &srcLen) - : 0; - if(!src){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting one string or Buffer argument."); - } - if(args->argc>1){ - dest = cwal_value_get_buffer(args->argv[1]); - } - if(!dest){ - dest = cwal_new_buffer(args->engine, srcLen/2); - if(!dest) return CWAL_RC_OOM; - } - destV = cwal_buffer_value(dest); - assert(destV); - cwal_value_ref(destV); - if(srcLen && *src){ - cwal_buffer kludge = cwal_buffer_empty; - kludge.mem = (unsigned char *)src; - kludge.used = srcLen; - kludge.capacity = srcLen; - rc = s2_minify_script_buffer(se, &kludge, dest); - } - if(rc){ - cwal_value_unref(destV); - }else{ - *rv = destV; - cwal_value_unhand(destV); - } - return rc; -} - -int s2_ctor_method_set( s2_engine * se, cwal_value * container, cwal_function * method ){ - if(!se || !container || !method) return CWAL_RC_MISUSE; - else if(!cwal_props_can(container)) return CWAL_RC_TYPE; - else{ - return s2_set_with_flags_v( se, container, se->cache.keyCtorNew, - cwal_function_value(method), - CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN ); - } -} - -int s2_ctor_callback_set( s2_engine * se, cwal_value * container, cwal_callback_f method ){ - int rc; - cwal_function * f; - cwal_value * fv; - if(!se || !container || !method) return CWAL_RC_MISUSE; - else if(!cwal_props_can(container)) return CWAL_RC_TYPE; - fv = cwal_new_function_value(se->e, method, 0, 0, 0); - f = fv ? cwal_value_get_function(fv) : NULL; - if(f){ - cwal_value_ref(fv); - rc = s2_ctor_method_set( se, container, f ); - cwal_value_unref(fv); - }else{ - rc = CWAL_RC_OOM; - } - return rc; - -} - -char s2_value_is_newing(cwal_value const * v){ - return (cwal_container_client_flags_get( v ) - & S2_VAL_F_IS_NEWING) ? 1 : 0; -} - - -int s2_ctor_fetch( s2_engine * se, s2_ptoker const * pr, - cwal_value * operand, - cwal_function **rv, - int errPolicy ){ - cwal_value * vtor = 0; - vtor = s2_get_v_proxy2( se, operand, se->cache.keyCtorNew, 0 ); - if(!vtor){ - static char const * msgfmt = - "Construction requires a container " - "with a constructor function property named '%s'."; - if(!errPolicy) return CWAL_RC_NOT_FOUND; - else if(errPolicy<0){ - return pr - ? s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0)) - : s2_throw(se, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0)) - ; - }else{ - return pr - ? s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0)) - : s2_engine_err_set(se, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0)) - ; - } - }else{ - static const char * msgfmt = - "Construction requires a container with " - "a constructor function property named '%s', " - "but the ctor resolves to type '%s'."; - cwal_function * f = cwal_value_function_part(se->e, vtor); - if(f){ - if(rv) *rv = f; - return 0; - } - else if(!errPolicy) return CWAL_RC_NOT_FOUND; - else if(errPolicy<0){ - return pr - ? s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0), - cwal_value_type_name(vtor)) - : s2_throw(se, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0), - cwal_value_type_name(vtor)) - ; - }else{ - return pr - ? s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0), - cwal_value_type_name(vtor)) - : s2_engine_err_set(se, CWAL_RC_NOT_FOUND, msgfmt, - cwal_value_get_cstr(se->cache.keyCtorNew,0), - cwal_value_type_name(vtor)) - ; - } - } -} - -/** - Returns true if v is legal for use as an operand do the "new" - keyword. This arguably should return true only if the property - is found in v itself, not in prototypes. Hmmm. -*/ -char s2_value_is_newable( s2_engine * se, cwal_value * v ){ -#if 0 - cwal_value * vtor = 0; - /* see notes in s2_keyword_f_new() */ - return (s2_get_v( se, v, se->cache.keyCtorNew, &vtor )) - ? 0 - : !!cwal_value_function_part(se->e, vtor ? vtor : v); -#eif 0 - cwal_value * vtor = 0; - return (s2_get_v( se, v, se->cache.keyCtorNew, &vtor )) - ? 0 - : vtor ? !!cwal_value_function_part(se->e, vtor) : 0; -#else - if(!cwal_props_can(v)) return 0; - else{ - cwal_function * f = 0; - s2_ctor_fetch(se, NULL, v, &f, 0); - return f ? 1 : 0; - } -#endif -} - -unsigned int s2_sizeof_script_func_state(){ - return (unsigned int)sizeof(s2_func_state); -} - -void s2_value_to_lhs_scope( cwal_value const * lhs, cwal_value * v){ - assert(lhs); - assert(v); - assert(!cwal_value_is_builtin(lhs) - && "or else a caller is misusing this function."); - if(lhs != v){ - cwal_scope * const s = cwal_value_scope(lhs); - assert(s && "Very bad. Corrupt value."); - if(s) cwal_value_rescope(s, v); - } -} - -cwal_value * s2_propagating_get( s2_engine * se ){ - return cwal_propagating_get(se->e); -} - -cwal_value * s2_propagating_take( s2_engine * se ){ - return cwal_propagating_take(se->e); -} - -cwal_value * s2_propagating_set( s2_engine * se, cwal_value * v ){ - return cwal_propagating_set(se->e, v); -} - -int s2_typename_set_v( s2_engine * se, cwal_value * container, cwal_value * name ){ - return s2_set_with_flags_v(se, container, se->cache.keyTypename, name, - CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN); -} - -int s2_typename_set( s2_engine * se, cwal_value * container, - char const * name, cwal_size_t nameLen ){ - int rc; - cwal_value * v = cwal_new_string_value(se->e, name, nameLen); - if(v){ - cwal_value_ref(v); - rc = s2_typename_set_v( se, container, v); - cwal_value_unref(v); - }else{ - rc = CWAL_RC_OOM; - } - return rc; -} - - -char const * s2_strstr( char const * haystack, cwal_size_t hayLen, - char const * needle, cwal_size_t needleLen ){ - char const * pos = haystack; - char const * end = haystack + hayLen - needleLen+1; - if(hayLen= haystack && pos < end; ++pos){ - if(0==memcmp(pos, needle, needleLen)) return pos; - } - return 0; -} - - -int s2_set_from_script_v( s2_engine * se, char const * src, - int srcLen, cwal_value * addResultTo, - cwal_value * propName ){ - if(!se || !src || !addResultTo || !propName){ - return CWAL_RC_MISUSE; - } - else if(!cwal_props_can(addResultTo)) return CWAL_RC_TYPE; - else{ - int rc; - cwal_value * rv = 0; - char const * pn = cwal_value_get_cstr(propName, 0); - if(srcLen<0) srcLen = (int)cwal_strlen(src); - rc = s2_eval_cstr( se, 1, pn ? pn : "s2_set_from_script_v", - src, srcLen, &rv ); - switch(rc){ - case CWAL_RC_RETURN: - rv = s2_propagating_take(se); - s2_engine_err_reset(se); - rc = 0; - break; - } - if(!rc){ - cwal_value_ref(rv); - rc = s2_set_v( se, addResultTo, propName, - rv ? rv : cwal_value_undefined() ); - cwal_value_unref(rv); - } - return rc; - } -} - - -int s2_set_from_script( s2_engine * se, char const * src, - int srcLen, cwal_value * addResultTo, - char const * propName, - cwal_size_t propNameLen ){ - if(!se || !src || !addResultTo || !propName){ - return CWAL_RC_MISUSE; - } - else if(!propNameLen) return CWAL_RC_RANGE; - else if(!cwal_props_can(addResultTo)) return CWAL_RC_TYPE; - else{ - int rc; - cwal_value * prop = cwal_new_string_value(se->e, propName, - propNameLen); - cwal_value_ref(prop); - rc = prop - ? s2_set_from_script_v( se, src, srcLen, addResultTo, - prop ) - : CWAL_RC_OOM; - cwal_value_unref(prop); - return rc; - } -} - -/** - The following LIKE/GLOB comparison bits were all ripped directly - from sqlite3. The names have been changed only to fit this project, - not to obfuscate their origins. -*/ - -/* -** A structure defining how to do GLOB-style comparisons. -** Taken from sqlite3. -*/ -struct s2_compareInfo { - unsigned char matchAll; /* "*" or "%" */ - unsigned char matchOne; /* "?" or "_" */ - unsigned char matchSet; /* "[" or 0 */ - unsigned char noCase; /* true to ignore case differences */ -}; - -static const struct s2_compareInfo s2_compareInfo_glob = {'*', '?', '[', 0}; -static const struct s2_compareInfo s2_compareInfo_like = {'%', '_', 0, 0}; -static const struct s2_compareInfo s2_compareInfo_likenc = {'%', '_', 0, 1}; - -/** - Optimized cwal_utf8_read_char1() which skips the function call if - A[0] is an ASCII character. -*/ -#define Utf8Read(A) (A[0]<0x80?*(A++):cwal_utf8_read_char1(&A)) -/* -** Assuming zIn points to the first byte of a UTF-8 character, -** advance zIn to point to the first byte of the next UTF-8 character. -*/ -#define Utf8SkipChar(zIn) \ - if( (*(zIn++))>=0xc0 ){ \ - while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ - } (void)0 - -/* -** Taken from sqlite3. -** -** Compare two UTF-8 strings for equality where the first string is -** a GLOB or LIKE expression. Return values: -** -** CWAL_RC_OK: Match -** CWAL_RC_NOT_FOUND: No match -** CWAL_RC_NOT_FOUND: No match in spite of having * or % wildcards. -** -** Globbing rules: -** -** '*' Matches any sequence of zero or more characters. -** -** '?' Matches exactly one character. -** -** [...] Matches one character from the enclosed list of -** characters. -** -** [^...] Matches one character not in the enclosed list. -** -** With the [...] and [^...] matching, a ']' character can be included -** in the list by making it the first character after '[' or '^'. A -** range of characters can be specified using '-'. Example: -** "[a-z]" matches any single lower-case letter. To match a '-', make -** it the last character in the list. -** -** Like matching rules: -** -** '%' Matches any sequence of zero or more characters -** -*** '_' Matches any one character -** -** Ec Where E is the "esc" character and c is any other -** character, including '%', '_', and esc, match exactly c. -** -** The comments within this routine usually assume glob matching. -** -** This routine is usually quick, but can be N**2 in the worst case. -*/ -static int patternCompare( - const unsigned char *zPattern, /* The glob pattern */ - const unsigned char *zString, /* The string to compare against the glob */ - const struct s2_compareInfo *pInfo, /* Information about how to do the compare */ - unsigned int matchOther /* The escape char (LIKE) or '[' (GLOB) */ -){ - unsigned int c, c2; /* Next pattern and input string chars */ - const unsigned int matchOne = pInfo->matchOne; /* "?" or "_" */ - const unsigned int matchAll = pInfo->matchAll; /* "*" or "%" */ - const unsigned char noCase = pInfo->noCase; /* True if uppercase==lowercase */ - const unsigned char *zEscaped = 0; /* One past the last escaped input char */ - - while( (c = Utf8Read(zPattern))!=0 ){ - if( c==matchAll ){ /* Match "*" */ - /* Skip over multiple "*" characters in the pattern. If there - ** are also "?" characters, skip those as well, but consume a - ** single character of the input string for each "?" skipped */ - while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){ - if( c==matchOne && cwal_utf8_read_char1(&zString)==0 ){ - return CWAL_RC_NOT_FOUND/*SQLITE_NOWILDCARDMATCH*/; - } - } - if( c==0 ){ - return CWAL_RC_OK; /* "*" at the end of the pattern matches */ - }else if( c==matchOther ){ - if( pInfo->matchSet==0 ){ - c = cwal_utf8_read_char1(&zPattern); - if( c==0 ) return CWAL_RC_NOT_FOUND /*SQLITE_NOWILDCARDMATCH*/; - }else{ - /* "[...]" immediately follows the "*". We have to do a slow - ** recursive search in this case, but it is an unusual case. */ - assert( matchOther<0x80 ); /* '[' is a single-byte character */ - while( *zString ){ - int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther); - if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch; - Utf8SkipChar(zString); - } - return CWAL_RC_NOT_FOUND; - } - } - - /* At this point variable c contains the first character of the - ** pattern string past the "*". Search in the input string for the - ** first matching character and recursively continue the match from - ** that point. - ** - ** For a case-insensitive search, set variable cx to be the same as - ** c but in the other case and search the input string for either - ** c or cx. - */ - if( c<=0x80 ){ - unsigned int cx; - int bMatch; - if( noCase ){ - cx = cwal_utf8_char_toupper(c); - c = cwal_utf8_char_tolower(c); - }else{ - cx = c; - } - while( (c2 = *(zString++))!=0 ){ - if( c2!=c && c2!=cx ) continue; - bMatch = patternCompare(zPattern,zString,pInfo,matchOther); - if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch; - } - }else{ - int bMatch; - while( (c2 = Utf8Read(zString))!=0 ){ - if( c2!=c ) continue; - bMatch = patternCompare(zPattern,zString,pInfo,matchOther); - if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch; - } - } - return CWAL_RC_NOT_FOUND; - } - if( c==matchOther ){ - if( pInfo->matchSet==0 ){ - c = cwal_utf8_read_char1(&zPattern); - if( c==0 ) return CWAL_RC_NOT_FOUND; - zEscaped = zPattern; - }else{ - unsigned int prior_c = 0; - int seen = 0; - int invert = 0; - c = cwal_utf8_read_char1(&zString); - if( c==0 ) return CWAL_RC_NOT_FOUND; - c2 = cwal_utf8_read_char1(&zPattern); - if( c2=='^' ){ - invert = 1; - c2 = cwal_utf8_read_char1(&zPattern); - } - if( c2==']' ){ - if( c==']' ) seen = 1; - c2 = cwal_utf8_read_char1(&zPattern); - } - while( c2 && c2!=']' ){ - if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ - c2 = cwal_utf8_read_char1(&zPattern); - if( c>=prior_c && c<=c2 ) seen = 1; - prior_c = 0; - }else{ - if( c==c2 ){ - seen = 1; - } - prior_c = c2; - } - c2 = cwal_utf8_read_char1(&zPattern); - } - if( c2==0 || (seen ^ invert)==0 ){ - return CWAL_RC_NOT_FOUND; - } - continue; - } - } - c2 = Utf8Read(zString); - if( c==c2 ) continue; - if( noCase && cwal_utf8_char_tolower(c)==cwal_utf8_char_tolower(c2) - && c<0x80 && c2<0x80 ){ - continue; - } - if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue; - return CWAL_RC_NOT_FOUND; - } - return *zString==0 ? CWAL_RC_OK : CWAL_RC_NOT_FOUND; -} - -#undef Utf8Read -#undef Utf8SkipChar - -char s2_glob_matches_str(const char *zGlob, - const char *z, - enum s2_glob_style policy){ - unsigned int esc; - struct s2_compareInfo const * ci; - if(!zGlob || !*zGlob) return 0; - switch(policy){ - case S2_GLOB_LIKE_NOCASE: - ci = &s2_compareInfo_likenc; - esc = 0; - break; - case S2_GLOB_WILDCARD: - ci = &s2_compareInfo_glob; - esc = '['; - break; - case S2_GLOB_LIKE: - ci = &s2_compareInfo_like; - esc = 0; - break; - default: - ci = 0; - esc = 0; - assert(!"invalid s2_glob_style"); - return 0; - } - return patternCompare((unsigned char const *)zGlob, - (unsigned char const *)z, - ci, esc) - ? 0 : 1; -} - -int s2_cb_glob_matches_str( cwal_callback_args const * args, - cwal_value ** rv ){ - int rc = 0; - cwal_int_t policy = S2_GLOB_WILDCARD; - char const * glob = - (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL; - char const * str = - (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL; - char const * sSelf = cwal_value_get_cstr(args->self, NULL); - if(sSelf){ - str = glob; - glob = sSelf; - if(args->argc>1) policy = cwal_value_get_integer(args->argv[1]); - }else{ - if(args->argc>2) policy = cwal_value_get_integer(args->argv[2]); - } - if(!glob || !str){ - rc = s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting (glob,string) arguments " - "OR globStringInstance.thisFunc(otherString) " - "usage."); - } - else if(!*glob){ - return s2_cb_throw(args, CWAL_RC_RANGE, - "Glob string may not be empty."); - } - else{ - *rv = s2_glob_matches_str(glob, str, - policy<0 - ? S2_GLOB_WILDCARD - : (policy>0 - ? S2_GLOB_LIKE - : S2_GLOB_LIKE_NOCASE)) - ? cwal_value_true() : cwal_value_false(); - } - return rc; -} - -int s2_errno_to_cwal_rc(int errNo, int dflt){ - switch(errNo ? errNo : errno){ - case 0: return CWAL_RC_OK; - case EACCES: return CWAL_RC_ACCESS; - case EEXIST: return CWAL_RC_ALREADY_EXISTS; - case EFBIG: return CWAL_RC_RANGE; - case EINTR: return CWAL_RC_INTERRUPTED - /* potential semantic conflict with - s2_engine::flags::interrupted */ - ; - case EINVAL: return CWAL_RC_MISUSE; - case EIO: return CWAL_RC_IO; - case EISDIR: return CWAL_RC_TYPE; - case ELOOP: return CWAL_RC_CYCLES_DETECTED; - case EMLINK: return CWAL_RC_RANGE; - case ENAMETOOLONG: return CWAL_RC_RANGE; - case ENFILE: return CWAL_RC_RANGE; - case ENOENT: return CWAL_RC_NOT_FOUND; - case ENOMEM: return CWAL_RC_OOM; - case ENOTDIR: return CWAL_RC_TYPE; - case ENOTTY: return CWAL_RC_TYPE; - case EMFILE: return CWAL_RC_RANGE; - case EPIPE: return CWAL_RC_IO; - case ERANGE: return CWAL_RC_RANGE; - case EROFS: return CWAL_RC_ACCESS; - case ESPIPE: return CWAL_RC_UNSUPPORTED; - default: return dflt; - } -} - -int s2_cb_tokenize_line(cwal_callback_args const * args, cwal_value ** rv){ - cwal_array * ar = 0; - cwal_value * arV = 0; - s2_ptoker pt = s2_ptoker_empty; - cwal_size_t lineLen = 0; - s2_engine * se = s2_engine_from_args(args); - char const * line = args->argc - ? cwal_value_get_cstr(args->argv[0], &lineLen) - : 0; - int rc; - if(!line) { - return s2_cb_throw(args, CWAL_RC_MISUSE, "Expecting a single string argument."); - } - rc = s2_ptoker_init_v2( args->engine, &pt, line, (cwal_int_t)lineLen, 0 ); - if(rc) goto toss; - pt.name = "tokenize line input"; -#if 0 - ar = cwal_new_array(args->engine); - if(!ar){ - rc = CWAL_RC_OOM; - goto toss; - } - arV = cwal_array_value(ar); - cwal_value_ref(arV); -#endif - /* pt.parent = se->currentScript; */ - while( !(rc=s2_next_token(se, &pt, 0, 0)) - && !s2_ptoker_is_eof(&pt) ){ - cwal_value * v = 0; - /* if(s2_ttype_is_junk(pt.token.ttype)) continue; */ -#if 1 - if(!ar){ - ar = cwal_new_array(args->engine); - if(!ar){ - rc = CWAL_RC_OOM; - goto toss; - } - arV = cwal_array_value(ar); - cwal_value_ref(arV); - } -#endif - v = s2_ptoken_is_tfnu(&pt.token); - if(!v){ - rc = s2_ptoken_create_value( se, &pt, &pt.token, &v ); - if(rc) break; - } - cwal_value_ref(v); - rc = cwal_array_append(ar, v); - cwal_value_unref(v); - if(rc) break; - } - if(rc) goto toss; - assert(!rc); - cwal_value_unhand(arV); - *rv = arV; - return rc; - toss: - cwal_value_unref(arV); - assert(rc); - switch(rc){ - case CWAL_RC_EXCEPTION: - case CWAL_RC_OOM: - case CWAL_RC_INTERRUPTED: - break; - default: - if(s2_ptoker_errtoken_has(&pt)){ - rc = s2_throw_ptoker(se, &pt, rc, - "Failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - }else{ - rc = s2_cb_throw(args, rc, "Failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - } - break; - } - s2_ptoker_finalize( &pt ); - return rc; -} - -int s2_eval_hold( s2_engine * se, cwal_value * v ){ - s2_scope * sc = s2__scope_current(se); - return (v && sc->evalHolder && !cwal_value_is_builtin(v)) - ? cwal_array_append(sc->evalHolder, v) - : 0; -} - -int s2_stash_hidden_member( cwal_value * who, cwal_value * what ){ - int rc = cwal_props_can(who) ? 0 : CWAL_RC_TYPE; - cwal_value * key; - cwal_engine * e = cwal_value_engine(who); - if(rc) return rc; - assert(e && "if who can props then who has an engine."); - if(!e) return CWAL_RC_MISUSE; - key = cwal_new_unique( e, 0 ); - if(!key) return CWAL_RC_OOM; - cwal_value_ref(key); - rc = cwal_prop_set_with_flags_v( who, key, what, - CWAL_VAR_F_HIDDEN - | CWAL_VAR_F_CONST ); - cwal_value_unref(key); - return rc; -} - -int s2_trigger_exit( cwal_callback_args const * args, cwal_value * result ){ - cwal_propagating_set(args->engine, - result ? result : cwal_value_undefined()); - return CWAL_RC_EXIT; -} - -cwal_value * s2_value_unwrap( cwal_value * v ){ - return cwal_value_is_unique(v) - ? cwal_unique_wrapped_get(v) - : v; -} - -cwal_value const * s2_value_unwrap_c( cwal_value const * v ){ - return cwal_value_is_unique(v) - ? cwal_unique_wrapped_get(v) - : v; -} - -char const * s2_value_cstr( cwal_value const * v, cwal_size_t * len ){ - if(cwal_value_is_unique(v)) v = cwal_unique_wrapped_get(v); - return cwal_value_get_cstr(v, len); -} - -int s2_seal_container( cwal_value * v, char sealIt ){ - if(!v) return CWAL_RC_MISUSE; - else if(!cwal_props_can(v)) return CWAL_RC_TYPE; - else{ - cwal_flags16_t const sealFlags = - CWAL_CONTAINER_DISALLOW_PROP_SET - | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET; - cwal_flags16_t flags = cwal_container_flags_get(v); - if(sealIt) flags |= sealFlags; - else flags &= ~sealFlags; - cwal_container_flags_set(v, flags); - } - return 0; -} - -int s2_cb_seal_object( cwal_callback_args const * args, - cwal_value ** rv ){ - uint16_t i = 0; - cwal_value * last = 0; - char doSeal = 1; - if(!args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting one or more Container-type arguments."); - } -#if 0 - /* This enables setting the seal/unseal flag by passing - a bool first argument, but allowing script code to unseal - objects will lead to madness. */ - if(cwal_value_is_bool(args->argv[0])){ - doSeal = cwal_value_get_bool(args->argv[0]); - i = 1; - } -#endif - for(; i < args->argc; ++i ){ - cwal_value * const c = args->argv[i]; - if(!cwal_props_can(c)){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "Argument #%d is not a Container type.", - (int)i); - } - s2_seal_container(c, doSeal); - last = c; - } - *rv = last; - return 0; -} - -int s2_tokenize_path_to_array( cwal_engine * e, cwal_array ** tgt, char const * path, - cwal_int_t pathLen ){ - int rc = 0; - cwal_array * ar = *tgt ? *tgt : cwal_new_array(e); - cwal_value * arV; - char const * t = 0; - cwal_size_t tLen = 0; - s2_path_toker pt = s2_path_toker_empty; - if(!ar){ - return CWAL_RC_OOM; - } - arV = cwal_array_value(ar); - cwal_value_ref(arV); - s2_path_toker_init(&pt, path, pathLen); - while(0==s2_path_toker_next(&pt, &t, &tLen)){ - cwal_value * const v = cwal_new_string_value(e, t, tLen); - if(!v){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(v); - rc = cwal_array_append(ar, v); - cwal_value_unref(v); - if(rc) break; - } - if(rc){ - if(ar == *tgt) cwal_value_unhand(arV); - else cwal_value_unref(arV); - }else{ - *tgt = ar /* maybe a no-op */; - cwal_value_unhand(arV); - } - return rc; -} - - -int s2_cb_tokenize_path( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_array * tgt = 0; - cwal_value * tgtV = 0; - char const * path; - cwal_size_t pathLen; - if(!args->argc || args->argc>2) goto misuse; - path = cwal_value_get_cstr(args->argv[0], &pathLen); - if(!path) goto misuse; - if(2==args->argc){ - tgtV = args->argv[1]; - tgt = cwal_value_get_array(tgtV); - if(!tgt) goto misuse; - }else{ - tgt = cwal_new_array(args->engine); - if(!tgt) return CWAL_RC_OOM; - tgtV = cwal_array_value(tgt); - } - cwal_value_ref(tgtV); - rc = pathLen - ? s2_tokenize_path_to_array(args->engine, &tgt, - path, (cwal_int_t)pathLen) - : 0; - if(rc){ - cwal_value_unref(tgtV); - }else{ - cwal_value_unhand(tgtV); - *rv = tgtV; - } - return rc; - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting (string path[, array target]) argument(s)."); -} - -char const * s2_home_get(s2_engine * se, cwal_size_t * len){ - char const * e = getenv("S2_HOME"); - S2_UNUSED_ARG(se); - if(e && len) *len = cwal_strlen(e); - return e; -} - -void s2_disable_set( s2_engine * se, cwal_flags32_t f ){ - se->flags.disable = f; -} - -int s2_disable_set_cstr( s2_engine * se, char const * str, cwal_int_t strLen, - cwal_flags32_t * result ){ - cwal_flags32_t f = 0; - if(strLen<0) strLen = (cwal_int_t)cwal_strlen(str); - if(strLen>0){ - char const * t = 0; - cwal_size_t tLen = 0; - s2_path_toker pt = s2_path_toker_empty; - s2_path_toker_init(&pt, str, strLen); - pt.separators = " ,"; - while(0==s2_path_toker_next(&pt, &t, &tLen)){ -#define CHECK(KEY,F) if(0==cwal_compare_cstr(KEY,tLen,t,tLen)){ f|=F; continue; }(void)0 - switch(tLen){ - case 4: - if(0==cwal_compare_cstr("none",tLen,t,tLen)){ - f = 0; - continue; - } - break; - case 5: - CHECK("fs-io", S2_DISABLE_FS_IO); - break; - case 6: - CHECK("fs-all", S2_DISABLE_FS_ALL); - break; - case 7: - CHECK("fs-read", S2_DISABLE_FS_READ); - CHECK("fs-stat", S2_DISABLE_FS_STAT); - break; - case 8: - CHECK("fs-write", S2_DISABLE_FS_READ); - break; - default: - break; - } -#undef CHECK - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Unknown feature-disable flag: %.*s", - (int)tLen, t); - } - } - se->flags.disable = f; - if(result) *result = f; - return 0; -} - - -cwal_flags32_t s2_disable_get( s2_engine const * se ){ - return se->flags.disable; -} - -/** - Returns the name of the first s2_disabled_features flag which - matches f, checking from lowest to highest value. -*/ -static char const * s2_disable_first_flag_name( cwal_flags32_t f ){ -#define CHECK(F) if(f & F) return #F - CHECK(S2_DISABLE_FS_STAT); - else CHECK(S2_DISABLE_FS_READ); - else CHECK(S2_DISABLE_FS_WRITE); - else return "???"; -#undef CHECK -} - -int s2_disable_check( s2_engine * se, cwal_flags32_t f ){ - int rc = 0; - if(f & se->flags.disable){ - rc = s2_engine_err_set(se, CWAL_RC_ACCESS, - "Feature flag(s) 0x%08x disallowed by " - "s2_disabled_features flag %s.", - (unsigned)f, - s2_disable_first_flag_name(f & se->flags.disable)); - } - return rc; -} - -int s2_disable_check_throw( s2_engine * se, cwal_flags32_t f ){ - int rc = s2_disable_check(se, f); - if(rc){ - rc = s2_throw_err(se, 0, 0, 0, 0); - assert(rc != 0); - } - return rc; -} - -int s2_cb_disable_check( cwal_callback_args const * args, cwal_flags32_t f ){ - return s2_disable_check_throw(s2_engine_from_args(args), f); -} - -#if S2_TRY_INTERCEPTORS -/** - Script usages: - - 1) f( obj, key, func ) - - Flags func as an interceptor and installs func as obj[key]. - - 2) Function f(func) - - Flags func as an interceptor. For the first form, it also adds the - function as a property of the given object. -*/ -int s2_cb_add_interceptor( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * tgt = 0; - cwal_value * key = 0; - cwal_value * fv = 0; - cwal_function * f = 0; - if(1==args->argc){ - fv = args->argv[0]; - }else if(3==args->argc){ - tgt = args->argv[0]; - key = args->argv[1]; - fv = args->argv[2]; - } - f = fv ? cwal_value_function_part(args->engine, fv) : 0; - if(!f || (tgt && !cwal_props_can(tgt))){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting (Function) or " - "(Object, mixed, Function) arguments."); - }else{ - s2_engine * se = s2_engine_from_args(args); - /* uint16_t const flags = cwal_container_client_flags_get(fv); */ - int const rc = key - ? s2_set_with_flags_v( se, tgt, key, fv, - CWAL_VAR_F_HIDDEN - | CWAL_VAR_F_CONST ) - : 0; - if(!rc){ -#if 0 - s2_func_state * fst = rc ? 0 : s2_func_state_for_func(f); - if(fst){ - /* A script func. Optimization: if it contains the text - se->cache.keyInterceptee then set a flag to tell downstream - code to set the se->cache.keyInterceptee var when calling - the interceptor. */ - char const * str = cwal_value_get_cstr(fst->vSrc, 0); - char const * key = cwal_value_get_cstr(se->cache.keyInterceptee, - 0); - if(strstr(str, key)){ - fst->flags |= S2_FUNCSTATE_F_INTERCEPTEE; - } - } -#endif - /* cwal_container_client_flags_set(fv, flags | S2_VAL_F_FUNC_INTERCEPTOR); */ - cwal_container_flags_set(fv, cwal_container_flags_get(fv) - | CWAL_CONTAINER_INTERCEPTOR); - *rv = 1==args->argc - ? fv - : cwal_function_value(args->callee); - } - return rc; - } -} -#endif/*S2_TRY_INTERCEPTORS*/ - -#undef MARKER -#undef HAS_DOTLIKE_FLAG -#undef HAS_ENUM_FLAG -#undef s2__scope_current -#undef s2__scope_for_level -#undef s2__err -#undef S2_USE_SIGNALS -/* end of file s2.c */ -/* start of file array.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \ - assert(se) - -#define THIS_ARRAY \ - cwal_array * self = 0; \ - ARGS_SE; \ - self = cwal_value_array_part(se->e, args->self); \ - if(!self){ \ - return s2_throw( se, CWAL_RC_TYPE, \ - "'this' is-not-an Array." ); \ - } (void)0 - -static int s2_cb_array_ctor( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_array * ar; - ar = cwal_new_array( args->engine ); - if(!ar) rc = CWAL_RC_OOM; - else if(args->argc){ - cwal_int_t i = ((cwal_int_t)args->argc)-1; - /* insert end-first to get all allocation out of the way - up front. */ - for( ; !rc && i >= 0; --i ){ - rc = cwal_array_set(ar, (cwal_size_t)i, args->argv[i]); - } - } - if(!rc) *rv = cwal_array_value(ar); - else cwal_value_unref(cwal_array_value(ar)); - return rc; -} - -static int s2_cb_array_clear( cwal_callback_args const * args, cwal_value **rv ){ - char clearProps; - THIS_ARRAY; - clearProps = (args->argc>0) - ? cwal_value_get_bool(args->argv[0]) - : 0; - cwal_array_clear(self, 0, clearProps); - *rv = args->self; - return 0; -} - -static int s2_cb_array_isempty( cwal_callback_args const * args, cwal_value **rv ){ - THIS_ARRAY; - *rv = cwal_new_bool( cwal_array_length_get(self) ? 0 : 1 ); - return 0; -} - -static int s2_cb_array_length( cwal_callback_args const * args, cwal_value **rv ){ - THIS_ARRAY; - if(args->argc){ - /* SET length */ - cwal_value * const index = args->argv[0]; - cwal_int_t len = cwal_value_get_integer(index); - int rc; - if((len<0) || !cwal_value_is_number(index)){ - return s2_throw(se, CWAL_RC_MISUSE, - "length argument must be " - "a non-negative integer."); - } - rc = cwal_array_length_set( self, (cwal_size_t)len ); - if(rc){ - return s2_throw(se, CWAL_RC_MISUSE, - "Setting array length to %"CWAL_INT_T_PFMT - " failed with code %d (%s).", - len, rc, cwal_rc_cstr(rc)); - } - *rv = args->self; - }else{ - *rv = cwal_new_integer( args->engine, - (cwal_int_t)cwal_array_length_get(self) ); - } - /* dump_val(*rv,"array.length()"); */ - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_array_reserve( cwal_callback_args const * args, cwal_value **rv ){ - static char const * usage = "Expecting a non-negative integer argument."; - THIS_ARRAY; - if(!args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, "%s", usage ); - } - else{ - cwal_value * const index = args->argv[0]; - cwal_int_t len = cwal_value_get_integer(index); - int rc; - if((len<0) || !cwal_value_is_number(index)){ - return s2_throw(se, CWAL_RC_MISUSE, "%s", usage); - } - rc = cwal_array_reserve( self, (cwal_size_t)len ); - if(rc){ - return s2_throw(se, rc, "cwal_array_reserve() failed!"); - } - *rv = args->self; - } - return 0; -} - -/** - Internal impl for array.push(), array.'operator+=', and - array.'operator+'. If isPush is true, it operators as push(), else - as += or binary +. -*/ -static int s2_cb_array_push_impl( cwal_callback_args const * args, - cwal_value **rv, int isPush ){ - THIS_ARRAY; - if(!args->argc){ - *rv = cwal_value_undefined(); - return 0; - } - else { - int rc; - cwal_size_t i = 0; - for( i = 0; i < args->argc; ++i ){ - rc = cwal_array_append( self, args->argv[i] ); - if(rc) break; - else if(isPush) *rv = args->argv[i]; - } - if(rc){ - rc = s2_throw(se, rc, "Appending to array failed."); - }else if(!isPush){ - *rv = args->self; - } - } - return 0; -} - - -static int s2_cb_array_push( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_array_push_impl(args, rv, 1); -} -static int s2_cb_array_operator_pluseq( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_array_push_impl(args, rv, 0); -} -#if 0 -/* Still unsure about whether (array + X)===array is really valid. */ -static int s2_cb_array_operator_plus( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_array_push_impl(args, rv, 0); -} -#endif - -static int s2_cb_array_pop( cwal_callback_args const * args, cwal_value **rv ){ - cwal_size_t aLen; - THIS_ARRAY; - aLen = cwal_array_length_get(self); - if(!aLen){ - *rv = cwal_value_undefined(); - }else{ - --aLen; -#if 1 - *rv = cwal_array_take(self, aLen); - cwal_array_length_set(self, aLen); -#else - *rv = cwal_array_get(self, aLen); - if(!*rv) *rv = cwal_value_undefined(); - cwal_value_ref(*rv); - cwal_array_length_set(self, aLen); - cwal_value_unhand(*rv); -#endif - - } - return 0; -} - - -static int s2_cb_array_slice( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t offset = 0, count = 0; - cwal_array * acp = NULL; - int rc; - THIS_ARRAY; - if(args->argc>0){ - offset = cwal_value_get_integer(args->argv[0]); - if(args->argc>1){ - count = cwal_value_get_integer(args->argv[1]); - } - } - if((offset<0) || (count<0)){ - return s2_throw(se, CWAL_RC_RANGE, - "Slice offset and count must both " - "be positive numbers."); - } - rc = cwal_array_copy_range( self, (cwal_size_t)offset, - (cwal_size_t)count, &acp ); - if(!rc){ - assert(acp); - *rv = cwal_array_value(acp); - } - return rc; -} - - -static int s2_cb_array_unshift( cwal_callback_args const * args, cwal_value **rv ){ - THIS_ARRAY; - if(!args->argc){ - *rv = cwal_value_undefined(); - return 0; - } - else { - int rc = 0; - cwal_size_t i = 0; - if(args->argc>1){ - rc = cwal_array_reserve(self, args->argc - + cwal_array_length_get(self)); - if(rc) return rc; - } - for( i = args->argc-1; - i < args->argc /* reminder to self: we're relying on underflow here. Ugly. */; - --i ){ - rc = cwal_array_prepend( self, args->argv[i] ); - if(rc) break; - } - if(!rc) *rv = args->self; - return rc; - } -} - -static int s2_cb_array_shift( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_int_t i = 0, n = 1; - THIS_ARRAY; - if(args->argc && cwal_value_is_integer(args->argv[0])){ - n = cwal_value_get_integer(args->argv[0]); - if(n<=0){ - return cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Integer argument to shift() must be >0."); - } - } - for( ; i < n; ++i ){ - *rv = 0; - rc = cwal_array_shift( self, rv ); - if(CWAL_RC_RANGE==rc){ - rc = 0; - *rv = cwal_value_undefined(); - }else if(rc) break; - else if(!*rv){ - *rv = cwal_value_undefined(); - } - } - return rc; -} - -static int s2_cb_array_sort( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_function * cmp; - THIS_ARRAY; - if(!args->argc || !(cmp = cwal_value_function_part(args->engine, - args->argv[0]))){ - rc = cwal_array_sort(self, cwal_compare_value_void); - }else{ - rc = cwal_array_sort_func( self, args->self, cmp); - if(!rc && cwal_exception_get(args->engine)){ - /* This is the only way to propagate an exception thrown from the - sort routine for the time being. */ - rc = CWAL_RC_EXCEPTION; - } - } - if(!rc) *rv = args->self; - return rc; -} - -static int s2_cb_array_reverse( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - THIS_ARRAY; - rc = cwal_array_reverse(self); - if(!rc) *rv = args->self; - return rc; -} - -static int s2_cb_array_each( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_function * f; - cwal_size_t i = 0; - cwal_size_t alen; - cwal_value * const vnull = cwal_value_undefined(); - cwal_value * av[2] = {NULL,NULL} /* Value, Index arguments for callback */; - cwal_value * ndx; - cwal_value * cbRv = NULL; - THIS_ARRAY; - f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL; - if(!f){ - return s2_throw(se, CWAL_RC_MISUSE, - "'eachIndex' expects a Function argument, " - "but got a %s.", - args->argc - ? cwal_value_type_name(args->argv[0]) - : "NULL"); - } - alen = cwal_array_length_get(self); - for( i = 0; !rc && i < alen; ++i ){ - av[0] = cwal_array_get( self, i ); - if(!av[0]) av[0] = vnull; - ndx = cwal_new_integer( args->engine, (cwal_int_t)i ); - if(!ndx){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(ndx); - av[1] = ndx; - cbRv = 0; - rc = cwal_function_call( f, args->self, &cbRv, 2, av ); - av[0] = av[1] = NULL; - /* Reminder ndx will always end up in the argv array, - which will clean it up before the call's scope returns. - No, it won't because the new argv is in a lower scope. - */ - cwal_value_unref(ndx) - /* If we unref without explicitly ref()'ing we will likely pull - the arguments out from the underlying argv array if a ref is - held to is. If we don't ref it we don't know if it can be - cleaned up safely. So we ref/unref it manually to force - cleanup _unless_ someone else got a reference. We'll re-use - it momentarily (next iteration) if recycling is on. - */; - if(!rc && rv && cwal_value_false()==cbRv/*literal false, not another falsy*/){ - /* Treat a "real" false return value as signal to end the - loop. */ - break; - } - if(ndx!=cbRv) cwal_refunref(cbRv); - } - if(!rc) *rv = args->self; - return rc; -} - -/** - Internal helper which looks for an integer argument at - args->argv[atIndex] and checks if it is negative. Returns non-0 - and triggers an exception in args->engine on error. Assigns the - fetched integer (if any) to *rv, even if it is negative (which also - results in a CWAL_RC_RANGE exception). -*/ -static int s2_array_get_index( cwal_callback_args const * args, - uint16_t atIndex, - cwal_int_t * rv ){ - ARGS_SE; - if(args->argcargv[atIndex]); - return (*rv<0) - ? s2_throw(se, CWAL_RC_RANGE, - "Array indexes may not be be negative.") - : 0; -} - -int s2_cb_array_get_index( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - cwal_int_t i = 0; - THIS_ARRAY; - rc = s2_array_get_index(args, 0, &i); - if(rc) return rc; - else{ - cwal_value * v; - assert(i>=0); - v = cwal_array_get(self, (cwal_size_t)i); - *rv = v ? v : cwal_value_undefined(); - return 0; - } -} - - -static int s2_cb_array_remove_index( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t ndx; - THIS_ARRAY; - if(!args->argc || (0>(ndx=cwal_value_get_integer(args->argv[0])))){ - *rv = cwal_value_false(); - }else { - cwal_size_t const max = cwal_array_length_get(self); - if((cwal_size_t)ndx>=max){ - *rv = cwal_value_false(); - return 0; - }else{ - int rc = 0; - cwal_size_t i = ndx; - for( ; i < max; ++i ){ - rc = cwal_array_set(self, i, - cwal_array_get(self, i+1)); - assert(!rc && "Cannot fail in this case."); - if(!rc) *rv = args->argv[i]; - else break; - } - if(!rc){ - cwal_array_length_set(self, max-1); - *rv = cwal_value_true(); - } - } - } - return 0; -} - - -int s2_cb_array_set_index( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - cwal_int_t i; - THIS_ARRAY; - rc = s2_array_get_index(args, 0, &i); - if(rc) return rc; - else{ - cwal_value * v = (args->argc>1) ? args->argv[1] : NULL; - assert(i>=0); - rc = cwal_array_set(self, (cwal_size_t)i, v); - if(!rc) *rv = v ? v : cwal_value_undefined(); - return rc; - } -} - -static int s2_cb_array_index_of( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_size_t ndx = 0; - cwal_int_t irc = 0; - char const strictCompare = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 1; - THIS_ARRAY; - if(!args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, "indexOf() requires an argument."); - } - rc = cwal_array_index_of(self, args->argv[0], &ndx, strictCompare); - if(CWAL_RC_NOT_FOUND==rc){ - rc = 0; - irc = -1; - }else if(!rc){ - irc = (cwal_int_t)ndx; - }else{ - assert(!"Should not be possible to fail like this with valid args."); - } - if(!rc){ - *rv = cwal_new_integer(args->engine, irc); - rc = *rv ? 0 : CWAL_RC_OOM; - } - return rc; -} - -static int s2_cb_array_join( cwal_callback_args const * args, cwal_value **rv ){ - char const * joiner = NULL; - cwal_size_t jLen = 0; - cwal_size_t i, aLen, oldUsed; - int rc = 0; - cwal_buffer * buf; - cwal_tuple * tp = 0; - cwal_array * ar = 0; - ARGS_SE; - ar = cwal_value_array_part(args->engine, args->self); - tp = ar ? 0 : cwal_value_get_tuple(args->self); - if(!ar && !tp){ - return s2_cb_throw( args, CWAL_RC_TYPE, - "'this' is-not-an Array or Tuple." ); - } - buf = &se->buffer; - oldUsed = buf->used; - if(!args->argc){ - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting string argument."); - } - joiner = cwal_value_get_cstr(args->argv[0], &jLen); - if(!joiner) goto misuse; - aLen = ar - ? cwal_array_length_get(ar) - : cwal_tuple_length(tp); - for( i = 0; !rc && i < aLen; ++i ){ - cwal_value * v = ar - ? cwal_array_get(ar, i) - : cwal_tuple_get(tp, (uint16_t)i); - if(i>0){ - rc = cwal_buffer_append(args->engine, buf, joiner, jLen); - } - if(v && !rc){ - rc = s2_value_to_buffer(args->engine, buf, v); - } - } - if(!rc){ - cwal_size_t const ln = buf->used - oldUsed; - assert(oldUsed <= buf->used); - *rv = cwal_new_string_value(args->engine, - (char const *)(buf->mem+oldUsed), - ln); - if(!*rv) rc = CWAL_RC_OOM; - } - buf->used = oldUsed; - return rc; -} - -cwal_value * s2_prototype_array( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_ARRAY ); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_ref(proto); - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_ARRAY, proto ); - cwal_value_unref(proto) /* on success ^^^, the core now owns a ref */; - if(!rc) rc = s2_prototype_stash(se, "array", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_ARRAY)); - /* MARKER(("Setting up OBJECT prototype.\n")); */ - - { - s2_func_def const funcs[] = { - S2_FUNC2("clear", s2_cb_array_clear), - S2_FUNC2("eachIndex", s2_cb_array_each), - S2_FUNC2("getIndex", s2_cb_array_get_index), - S2_FUNC2("indexOf", s2_cb_array_index_of), - S2_FUNC2("isEmpty", s2_cb_array_isempty), - S2_FUNC2("join",s2_cb_array_join), - S2_FUNC2("length",s2_cb_array_length), - S2_FUNC2("operator+=", s2_cb_array_operator_pluseq), - /*S2_FUNC2("operator+", s2_cb_array_operator_plus),*/ - S2_FUNC2("push", s2_cb_array_push), - S2_FUNC2("pop", s2_cb_array_pop), - S2_FUNC2("removeIndex", s2_cb_array_remove_index), - S2_FUNC2("reserve", s2_cb_array_reserve), - S2_FUNC2("reverse", s2_cb_array_reverse), - S2_FUNC2("setIndex", s2_cb_array_set_index), - S2_FUNC2("shift", s2_cb_array_shift), - S2_FUNC2("slice", s2_cb_array_slice), - S2_FUNC2("sort", s2_cb_array_sort), - S2_FUNC2("toString", s2_cb_value_to_string), - S2_FUNC2("unshift", s2_cb_array_unshift), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(!rc) rc = s2_ctor_callback_set(se, proto, - s2_cb_array_ctor); - if(rc) goto end; - } - - { - /* Array.filter() impl. */ - char const * src = - "proc(f,invert=false){" - "affirm typeinfo(iscallable f);" - "affirm typeinfo(islist this);" - "const a = [];" - "foreach(@this=>v) f(v) ? (invert ? 0 : a[]=v) : (invert ? a[]=v : 0);" - "return typeinfo(istuple this) ? [#@a] : a;" - "}"; - rc = s2_set_from_script(se, src, (int)cwal_strlen(src), - proto, "filter", 6); - if(rc) goto end; - } - - end: - return rc ? NULL : proto; -} - -static int s2_cb_tuple_ctor( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t n = (1==args->argc) ? cwal_value_get_integer(args->argv[0]) : -1; - if(1 != args->argc){ - goto misuse; - }else if(cwal_value_is_array(args->argv[0])){ - /* Tuple(array): Copy elements from the array. */ - cwal_array const * ar = cwal_value_get_array(args->argv[0]); - cwal_size_t an = cwal_array_length_get(ar); - uint16_t i; - int rc = 0; - cwal_tuple * tp; - if(an > (uint16_t)-1){ - goto toomany; - } - tp = cwal_new_tuple(args->engine, an); - if(!tp) rc = CWAL_RC_OOM; - else{ - *rv = cwal_tuple_value(tp); - for(i = 0; i < an; ++i ){ - cwal_value * v = cwal_array_get(ar, i); - rc = cwal_tuple_set(tp, i, v); - assert(!rc && "No possible error conditions here."); - } - } - return rc; - }else if(cwal_value_is_tuple(args->argv[0])){ - /* Tuple(tuple): Copy elements from the tuple. */ - cwal_tuple const * t1 = cwal_value_get_tuple(args->argv[0]); - uint16_t const an = cwal_tuple_length(t1); - uint16_t i; - int rc = 0; - cwal_tuple * tp = cwal_new_tuple(args->engine, an); - if(!tp) rc = CWAL_RC_OOM; - else{ - *rv = cwal_tuple_value(tp); - for(i = 0; i < an; ++i ){ - cwal_value * v = cwal_tuple_get(t1, i); - rc = cwal_tuple_set(tp, i, v); - assert(!rc && "No possible error conditions here."); - } - } - return rc; - }else if(n>=0){ - /* Tuple(integer size) */ - if(n > (cwal_int_t)((uint16_t)-1)){ - goto toomany; - } - *rv = cwal_new_tuple_value(args->engine, (cwal_size_t)n);; - assert(*rv - ? (s2_prototype_tuple(s2_engine_from_args(args)) - == cwal_value_prototype_get(args->engine, *rv)) - : 1); - - assert((cwal_size_t)n == cwal_tuple_length(cwal_value_get_tuple(*rv))); - return *rv ? 0 : CWAL_RC_OOM; - } - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting an (array | tuple | integer>=0)."); - toomany: - return s2_cb_throw(args, CWAL_RC_RANGE, "Too many items for a tuple!"); -} - -static int s2_cb_tuple_length( cwal_callback_args const * args, cwal_value **rv ){ - cwal_tuple const * tp = cwal_value_get_tuple(args->self); - if(!tp){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "'this' is-not-a Tuple."); - }else if(args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Tuple length cannot be changed after construction."); - }else{ - *rv = cwal_new_integer(args->engine, cwal_tuple_length(tp)); - return *rv ? 0 : CWAL_RC_OOM; - } -} - -/** - Impl for Tuple's operator!=, <=, <, >, >=, ==. - - direction: <0 means lt, >0 means gt, 0 means eq. - - eq: 0 for (<, >, !=), non-0 for (>=, <=, ==). - - TODO: we can refactor this in a generic routine which injects these - overloads for any type. Alternately, make the op overloading away - of the compare() method and use it (if availble) to implement these - ops. That would cost 6 fewer overloads. -*/ -static int s2_cb_tuple_cmp_impl( cwal_callback_args const * args, cwal_value **rv, - int direction, char eq ){ - cwal_value * rhs = args->argc ? args->argv[0] : 0; - assert(1==args->argc); - if(1!=args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Internal error: expecting operator " - "to be called with 1 argument."); - } - else if(!cwal_value_is_tuple(rhs)){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "Cannot compare a tuple to a non-tuple."); - }else{ - int const cmp = cwal_value_compare(args->self, rhs); - if(!direction){ - if(!eq){ /* != op */ - *rv = cmp ? cwal_value_true() : cwal_value_false(); - }else{ /* == op */ - *rv = cmp ? cwal_value_false() : cwal_value_true(); - } - }else if(!cmp){ - *rv = eq ? cwal_value_true() : cwal_value_false(); - }else{ - *rv = (direction<0 && cmp<0) - ? cwal_value_true() - : ((direction>0 && cmp>0) - ? cwal_value_true() - : cwal_value_false()) - ; - } - return 0; - } -} - -static int s2_cb_tuple_op_lt( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, -1, 0); -} - - -static int s2_cb_tuple_op_le( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, -1, 1); -} - -static int s2_cb_tuple_op_gt( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, 1, 0); -} - -static int s2_cb_tuple_op_ge( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, 1, 1); -} - -static int s2_cb_tuple_op_neq( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, 0, 0); -} - -static int s2_cb_tuple_op_eq( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_tuple_cmp_impl(args, rv, 0, 1); -} - -cwal_value * s2_prototype_tuple( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = 1 - ? s2_prototype_stashed(se, "tuple") - : cwal_prototype_base_get( se->e, CWAL_TYPE_TUPLE ); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_prototype_set(proto, NULL); - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_TUPLE, proto ); - if(!rc) rc = s2_prototype_stash(se, "tuple", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_TUPLE)); - - { - s2_func_def const funcs[] = { - S2_FUNC2("compare", s2_cb_value_compare), - S2_FUNC2("join",s2_cb_array_join), - S2_FUNC2("length", s2_cb_tuple_length), - S2_FUNC2("toJSONString", s2_cb_this_to_json_token), - S2_FUNC2("toString", s2_cb_value_to_string), - S2_FUNC2("operator<=", s2_cb_tuple_op_le), - S2_FUNC2("operator<", s2_cb_tuple_op_lt), - S2_FUNC2("operator>=", s2_cb_tuple_op_ge), - S2_FUNC2("operator>", s2_cb_tuple_op_gt), - S2_FUNC2("operator!=", s2_cb_tuple_op_neq), - S2_FUNC2("operator==", s2_cb_tuple_op_eq), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(!rc) rc = s2_ctor_callback_set(se, proto, s2_cb_tuple_ctor); - if(!rc){ - cwal_value * arrayProto = cwal_prototype_base_get( se->e, CWAL_TYPE_ARRAY ); - cwal_kvp * kvp; - assert(arrayProto && "Array prototype must have been set up already!"); - kvp = cwal_prop_get_kvp( arrayProto, "filter", 6, 0, NULL ); - assert(kvp && "array.filter() must have been installed already!"); - rc = cwal_prop_set_v( proto, cwal_kvp_key(kvp), cwal_kvp_value(kvp) ); - } - if(rc) goto end; - } - /* s2_dump_val(proto,"Pair prototype"); */ - assert(!rc); - end: - return rc ? NULL : proto; -} - -#undef MARKER -#undef ARGS_SE -#undef THIS_ARRAY -/* end of file array.c */ -/* start of file enum.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -#define HAS_ENUM_FLAG(ClientFlags) \ - ((S2_VAL_F_MODE_CLASS & ClientFlags) \ - && (S2_VAL_F_CLASS_ENUM & ClientFlags)) - -enum s2_enum_builder_state { -S2_ENUM_STATE_UNINITED = 0, -S2_ENUM_STATE_INITED = 1, -S2_ENUM_STATE_SEALED = 2 -}; - -int s2_enum_builder_init( s2_engine * se, s2_enum_builder * eb, - char const * typeName, - cwal_size_t entryCountHint){ - int rc = 0; - s2_enum_builder_cleanup(eb); - eb->se = se; - if(!entryCountHint) entryCountHint = 6; - entryCountHint = cwal_next_prime(2 * (entryCountHint - ? entryCountHint-1 : 6) ); - eb->entries = cwal_new_hash_value(se->e, entryCountHint); - if(eb->entries){ - s2_hash_dot_like_object(eb->entries, 1); - }else{ - return CWAL_RC_OOM; - } - cwal_value_ref(eb->entries) /* we'll hold this ref for the whole - enum build process */; - if(typeName && *typeName){ - cwal_value * key = cwal_new_string_value(se->e, typeName, - cwal_strlen(typeName)); - if(key){ - cwal_value_ref(key); - rc = cwal_prop_set_with_flags_v( eb->entries, se->cache.keyTypename, - key, - CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN ); - cwal_value_unref(key); - }else{ - rc = CWAL_RC_OOM; - } - if(rc) goto err; - } - eb->flags = S2_ENUM_STATE_INITED; - cwal_value_make_vacuum_proof(eb->entries,1); - return 0; - err: - s2_enum_builder_cleanup(eb); - assert(rc); - return rc; -} - -void s2_enum_builder_cleanup( s2_enum_builder * eb ){ - if(eb->entries){ - assert(eb->se); - assert(cwal_value_refcount(eb->entries)); - cwal_value_make_vacuum_proof(eb->entries,0); - cwal_value_unref(eb->entries); - } - *eb = s2_enum_builder_empty; -} - -int s2_enum_builder_append_v( s2_enum_builder * eb, - cwal_value * key, - cwal_value * wrappedVal ){ - cwal_value * uval; - int rc; - if(S2_ENUM_STATE_INITED != eb->flags){ - return CWAL_RC_MISUSE; - }else if(!key || !wrappedVal){ - return CWAL_RC_MISUSE; - } - assert(eb->flags>0); - assert(eb->entries); - uval = cwal_new_unique(eb->se->e, wrappedVal); - if(!uval){ - rc = CWAL_RC_OOM; - }else{ - cwal_value_ref(uval); - rc = s2_set_with_flags_v( eb->se, eb->entries, key, - uval, CWAL_VAR_F_CONST ); - if(!rc){ - rc = s2_set_with_flags_v( eb->se, eb->entries, uval, key, - CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN ); - } - cwal_value_unref(uval); - if(!rc){ - ++eb->entryCount; - } - } - return rc; -} - -int s2_enum_builder_append( s2_enum_builder * eb, - char const * entryName, - cwal_value * val){ - cwal_value * key = 0; - cwal_engine * e = eb->se->e; - if(S2_ENUM_STATE_INITED != eb->flags){ - return CWAL_RC_MISUSE; - }else if(!entryName){ - return CWAL_RC_MISUSE; - } - key = cwal_new_string_value(e, entryName, - cwal_strlen(entryName)); - if(!key) return CWAL_RC_OOM; - else{ - int rc; - cwal_value_ref(key); - rc = s2_enum_builder_append_v(eb, key, val); - cwal_value_unref(key); - return rc; - } -} - -int s2_enum_builder_seal( s2_enum_builder * eb, cwal_value **rv ){ - int rc = 0; - cwal_value * enumProto; - if(S2_ENUM_STATE_INITED != eb->flags){ - return CWAL_RC_MISUSE; - }else if(!eb->entryCount){ - return CWAL_RC_RANGE; - } - enumProto = s2_prototype_enum(eb->se); - if(!enumProto) return CWAL_RC_OOM; - assert(eb->entries); - assert(eb->flags > 0); -#if 0 - v = cwal_new_integer(eb->se->e, eb->entryCount); - if(!v) rc = CWAL_RC_OOM; - else{ - cwal_value_ref(v); - rc = cwal_prop_set_with_flags(eb->entries, "enumEntryCount", 14, v, - CWAL_VAR_F_CONST); - cwal_value_unref(v); - v = 0; - } - if(rc) goto end; -#endif - rc = cwal_value_prototype_set(eb->entries, enumProto); - if(!rc){ - cwal_container_flags_set(eb->entries, - CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES - | CWAL_CONTAINER_DISALLOW_PROP_SET - /* TODO:??? - | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET */ - ); - cwal_container_client_flags_set(eb->entries, S2_VAL_F_ENUM); - eb->flags = S2_ENUM_STATE_SEALED; - if(rv){ - *rv = eb->entries; - cwal_value_ref(*rv); - s2_enum_builder_cleanup(eb) /* will unref() and de-vacuum-safe - eb->entries */; - assert(!cwal_value_is_vacuum_proof(*rv)); - cwal_value_unhand(*rv); - } - } - /* end: */ - return rc; -} - - -/* in s2_protos.c */ -int s2_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ ); - -/* KVP Visitor for enums */ -static int s2_enum_visitor_each( cwal_kvp const * kvp, void * state_ ){ - /* Reminder: the (unique==>string) pairs are hidden, so they're - not iterated over. */ - if(cwal_value_is_unique(cwal_kvp_value(kvp))){ - return cwal_value_is_string(cwal_kvp_key(kvp)) - /* so we pass (V,K) to the callback, and only for (Unique==>Name) - pairs. */ - ? s2_kvp_visitor_prop_each(kvp, state_) - : 0; - }else{ - return 0; - } -} - -static int s2_cb_enum_each( cwal_callback_args const * args, cwal_value **rv ){ - cwal_function * f; - f = args->argc - ? cwal_value_function_part(args->engine, args->argv[0]) - : NULL; - if(!f){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a Function argument, " - "but got a %s.", - args->argc - ? cwal_value_type_name(args->argv[0]) - : ""); - }else{ - s2_kvp_each_state state = s2_kvp_each_state_empty; - int rc; - cwal_hash * h = cwal_value_hash_part(args->engine, args->self); - state.e = args->engine; - state.callback = f; - state.self = args->self; - /* state.valueArgFirst = 1; */ - rc = cwal_hash_visit_kvp( h, s2_enum_visitor_each, &state ); - if(S2_RC_END_EACH_ITERATION==rc) rc = 0; - if(!rc) *rv = args->self; - return rc; - } -} - -cwal_value * s2_value_enum_part( s2_engine * se, cwal_value * v ){ - do{ - if(HAS_ENUM_FLAG(cwal_container_client_flags_get(v))) return v; - else v = cwal_value_prototype_get(se->e, v); - }while(v); - return 0; -} - -int s2_value_is_enum( const cwal_value * v ){ - return HAS_ENUM_FLAG(cwal_container_client_flags_get(v)); -} - -/** Internal helper for s2_enum_from_object() (==> efo) */ -static int cwal_kvp_visitor_f_efo( cwal_kvp const * kvp, void * state ){ - s2_enum_builder * eb = (s2_enum_builder *)state; - return s2_enum_builder_append_v( eb, cwal_kvp_key(kvp), cwal_kvp_value(kvp) ); -} - -/** - UNTESTED! - - Copies all properties from src to a new enum value. - - The typeName parameter is interpreted as documented for - s2_enum_builder_init(). It may be NULL. - - On success, *rv is assigned to the new enum value. On error, *rv is - not modified and non-0 is returned. Returns CWAL_RC_MISUSE if any - pointer argument is NULL, CWAL_RC_RANGE if src has no properties - (empty enums are not allowed), CWAL_RC_OOM if an allocation fails, - and possibly other CWAL_RC_xxx codes from the underlying - s2_enum_builder API calls. - - On success, *rv becomes the caller's responsibility: it is a new - value with a refcount of 0. -*/ -int s2_enum_from_object( s2_engine * se, cwal_value * src, - char const * typeName, cwal_value **rv ){ - int rc; - cwal_value * enu = 0; - cwal_size_t nKeys = 0; - s2_enum_builder eb = s2_enum_builder_empty; - if(!se || !src || !rv) return CWAL_RC_MISUSE; - nKeys = cwal_props_count(src); - if(0==nKeys){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Empty enum is not permitted."); - } - rc = s2_enum_builder_init( se, &eb, typeName, nKeys ); - if(!rc){ - rc = cwal_props_visit_kvp( src, cwal_kvp_visitor_f_efo, &eb ); - if(!rc){ - rc = s2_enum_builder_seal( &eb, &enu ); - cwal_value_ref(enu); - } - } - s2_enum_builder_cleanup(&eb); - if(rc){ - cwal_value_unref(enu); - }else{ - assert(enu); - cwal_value_unhand(enu); - *rv = enu; - } - return rc; -} - -#define THIS_ENUM \ - s2_engine * se = s2_engine_from_args(args); \ - cwal_value * eObj = s2_value_enum_part(se, args->self); \ - cwal_hash * eHash = eObj ? cwal_value_get_hash(eObj) : 0; \ - if(!eHash) return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \ - "Expecting an enum as 'this'." ) - -/** - Specialized enum prop search which only searches in the enum part - of theEnum (which may be an enum-derived value). Returns the value - it finds or 0 if it does not find one. -*/ -static cwal_value * s2_enum_search( s2_engine * se, cwal_value * theEnum, - cwal_value * key){ - cwal_hash * h; - theEnum = s2_value_enum_part(se, theEnum); - h = theEnum ? cwal_value_get_hash(theEnum) : 0; - return h ? cwal_hash_search_v(h, key) : 0; -} - -/** - Internal helper for s2_cb_enum_keys(). -*/ -static int s2_kvp_visit_enum_keys_to_array( cwal_kvp const * kvp, void * state ){ - return cwal_value_is_unique(cwal_kvp_value(kvp)) - ? cwal_array_append((cwal_array *)state, cwal_kvp_key(kvp)) - : 0; -} - -/** - Returns an array containing all enum entry keys for - obj. -*/ -static int s2_cb_enum_keys( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_array * ar; - THIS_ENUM; - ar = cwal_new_array(args->engine); - if(!ar) return CWAL_RC_OOM; - rc = cwal_hash_visit_kvp( eHash, s2_kvp_visit_enum_keys_to_array, ar ); - if(!rc) *rv = cwal_array_value(ar); - else cwal_array_unref(ar); - return rc; -} - -static int s2_cb_enum_contains( cwal_callback_args const * args, cwal_value **rv ){ - THIS_ENUM; - if(1 != args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting exactly one argument."); - } - *rv = cwal_new_bool( !!s2_enum_search(se, eObj, args->argv[0]) ); - return 0; -} - -static int s2_cb_enum_op_arrow( cwal_callback_args const * args, cwal_value **rv ){ - THIS_ENUM; - assert(1==args->argc && "expecting operator call usage."); - if(!(*rv = s2_enum_search( se, eObj, args->argv[0] ))){ - *rv = cwal_value_undefined(); - } - return 0; -} - -static int s2_cb_enum_op_dotdot( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * entry; - cwal_value * key; - int rc = 0; - THIS_ENUM; - assert(1==args->argc && "expecting operator call usage."); - key = args->argv[0]; - if(!cwal_value_is_string(key) - /* && !cwal_value_is_number(key) */){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "Invalid type for enum 'operator::' RHS: " - "expecting a string/identifier"); - } - entry = s2_enum_search( se, eObj, key ); - if(entry){ - if(cwal_value_is_unique(entry)){ - if(!(*rv = cwal_unique_wrapped_get(entry))) *rv = cwal_value_undefined(); - }else{ - cwal_size_t len = 0; - char const * cKey = cwal_value_get_cstr(key, &len); - rc = s2_cb_throw(args, CWAL_RC_TYPE, - "Property '%.*s' is (somehow) not an enum entry.", - (int)len, cKey); - } - }else{ - cwal_size_t len = 0; - char const * cKey = cwal_value_get_cstr(key, &len); - rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND, - "No such enum entry: '%.*s'", - (int)len, cKey); - } - return rc; -} - - -cwal_value * s2_prototype_enum(s2_engine * se){ - static char const * protoKey = "Enum"; - cwal_value * proto = s2_prototype_stashed( se, protoKey ); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto) return 0; - else{ - int rc; - cwal_value_ref(proto); - cwal_value_prototype_set(proto, 0) - /* so it does not derive from Object */; - rc = s2_typename_set(se, proto, "enum", 4); - if(!rc){ - const s2_func_def funcs[] = { - S2_FUNC2("eachEnumEntry", s2_cb_enum_each), - S2_FUNC2("getEnumKeys", s2_cb_enum_keys), - S2_FUNC2("hasEnumEntry", s2_cb_enum_contains), - S2_FUNC2("operator->", s2_cb_enum_op_arrow), - S2_FUNC2("operator::", s2_cb_enum_op_dotdot), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, - CWAL_VAR_F_CONST); - } - if(rc || (rc=s2_prototype_stash(se, protoKey, proto))){ - cwal_value_unref(proto); - proto = 0; - }else{ - cwal_value_unhand(proto); - assert(cwal_value_refcount(proto) && "But... the s2_stash ref?"); - } - return proto; - } -} - - -#undef MARKER -#undef HAS_ENUM_FLAG -#undef THIS_ENUM -/* end of file enum.c */ -/* start of file eval.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d():\t",__FILE__,__LINE__); if(1) printf pfexp -/** - Dumps info about (s2_ptoken const*) TP, prefixed by the (char const *) lbl. -*/ -#define s2__dump_token(lbl,TP) \ - MARKER(("%s: token type %d/%s: %.*s\n", lbl, \ - (TP)->ttype, s2_ttype_cstr((TP)->ttype), \ - (int)s2_ptoken_len(TP), s2_ptoken_begin(TP))) -#else -#define MARKER(pfexp) (void)0 -#define s2__dump_token(lbl,TP) (void)0 -#endif - -#define s2__err(SE) (SE)->e->err - - -/** @internal - Internal representation of a stack trace entry. -*/ -struct s2_strace_entry { - /** - The next entry "up" (older) in the stack. - */ - s2_strace_entry * up; - /** - The next entry "down" (newer) in the stack. - */ - s2_strace_entry * down; - /** - The tokenizer active when this entry is created. - */ - s2_ptoker * pr; - /** - Active token when this entry is created. - */ - s2_ptoken pos; -}; - -/** - Empty-initilized s2_strace_entry object. -*/ -#define s2_strace_entry_empty_m { \ - 0/*up*/, 0/*down*/, 0/*pr*/, s2_ptoken_empty_m/*pos*/ \ - } - -/** - Empty-initilized s2_strace_entry object. -*/ -static const s2_strace_entry s2_strace_entry_empty = s2_strace_entry_empty_m; - - -const s2_func_state s2_func_state_empty = { -0/*vSrc*/,0/*vImported*/,0/*vName*/, -0/*keyScriptName*/, -0/*next*/, -0/*line*/,0/*col*/, -0/*flags*/ -}; - -#define s2__ukwd(SE) ((s2_ukwd*)se->ukwd) -#define s2__ukwd_key "s2_engine::ukwd" -static void s2_ukwd_free2(s2_engine * se, s2_ukwd * u){ - if(u){ - cwal_free2(se->e, u->list, u->alloced * sizeof(s2_ukwd)); - u->h = 0/* owned by s2 stash */; - cwal_free2(se->e, u, sizeof(s2_ukwd)); - } -} - -void s2_ukwd_free(s2_engine * se){ - s2_ukwd_free2(se, se->ukwd); - se->ukwd = 0; -} - - -cwal_hash * s2_fstash( s2_engine * se ){ - if(!se->funcStash){ - se->funcStash = cwal_new_hash(se->e, 53); - if(se->funcStash){ - cwal_value * v = cwal_hash_value(se->funcStash); - cwal_value_ref(v); - cwal_value_make_vacuum_proof(v, 1); - cwal_value_rescope( se->e->top, v ); - } - } - return se->funcStash; -} - -static int s2_strace_push_pos( s2_engine * se, - s2_ptoker * pr, - s2_ptoken const * srcPos, - s2_strace_entry * ent ){ - if(se->strace.max && se->strace.count == se->strace.max-1){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Stack depth too deep. Max is %"CWAL_SIZE_T_PFMT". " - "Potentially caused by infinite recursion.", - (cwal_size_t)se->strace.max); - } - if(!srcPos) srcPos = &pr->token; - /** - Reminder to self: we could potentially check for infinite - recursion by using a combination of strace.count limits and - comparing srcPos to prior entries in the list. It would turn this - into an O(N) algorithm, though, with N=stack depth. Note that - this only catches script-originated calls, not calls made into - Functions via native code. - */ - ent->pos = *srcPos; - ent->pr = pr; - if(se->strace.tail){ - assert(!ent->down); - se->strace.tail->down = ent; - ent->up = se->strace.tail; - se->strace.tail = ent; - }else{ - assert(!se->strace.head); - se->strace.head = se->strace.tail = ent; - } - ++se->strace.count; - return 0; -} - -void s2_strace_pop( s2_engine * se ){ - assert(se->strace.count); - assert(se->strace.tail); - if(se->strace.count){ - s2_strace_entry * x = se->strace.tail; - assert(!x->down); - if(x->up){ - assert(x->up->down == x); - se->strace.tail = x->up; - x->up->down = NULL; - x->up = NULL; - }else{ - se->strace.head = se->strace.tail = NULL; - } - --se->strace.count; - }else{ - s2_fatal( CWAL_RC_RANGE, "internal error: " - "s2_strace_pop() called on empty stack."); - } -} - - -int s2_strace_generate( s2_engine * se, cwal_value ** rv ){ - cwal_array * ar = 0; - int rc = 0; - s2_strace_entry * ent = se->strace.tail; - cwal_size_t const oldCount = se->strace.count; - /* MARKER(("se->strace.count=%u ent=%p\n", se->strace.count, (void const *)ent)); */ - if(!ent){ - *rv = 0; - return 0; - } - se->strace.count = 0 - /* Workaround for co-dependency via - s2_add_script_props() */; - for( ; !rc && ent; ent = ent->up ){ - /* Generate array of stack trace entries */ - cwal_value * c; - if(!ar){ - ar = cwal_new_array(se->e);; - if(!ar){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(cwal_array_value(ar)); -#if 0 - rc = cwal_array_reserve(ar, oldCount); - if(rc){ - break; - } -#endif - } - c = cwal_new_object_value(se->e); - if(!c){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(c); - rc = cwal_array_append(ar, c); - cwal_value_unref(c); - if(!rc){ - s2_ptoken const et = *s2_ptoker_errtoken_get(ent->pr); - s2_ptoker_errtoken_set(ent->pr, &ent->pos); - rc = s2_add_script_props(se, c, ent->pr); - s2_ptoker_errtoken_set(ent->pr, &et); - } - } - - se->strace.count = oldCount; - if(rc) cwal_array_unref(ar); - else{ - cwal_value_unhand(cwal_array_value(ar)); - *rv = cwal_array_value(ar); - } - return rc; -} - - -static s2_func_state * s2_func_state_malloc( s2_engine * se ){ - s2_func_state * rc = se->recycler.scriptFuncs.head; - ++se->metrics.funcStateRequests; - if(rc){ - assert(se->recycler.scriptFuncs.count>0); - se->recycler.scriptFuncs.head = rc->next; - --se->recycler.scriptFuncs.count; - rc->next = 0; - }else{ - rc = (s2_func_state *)cwal_malloc2(se->e, sizeof(s2_func_state)); - if(rc){ - se->metrics.funcStateMemory += sizeof(s2_func_state); - cwal_engine_adjust_client_mem(se->e, (cwal_int_t)sizeof(s2_func_state)); - ++se->metrics.funcStateAllocs; - *rc = s2_func_state_empty; - } - } - return rc; -} - -static void s2_func_state_free( s2_engine * se, s2_func_state * fs ){ - /* MARKER(("Finalizing function @ %p.\n", (void const *)fs)); */ - assert(!fs->next); - if(fs->vSrc){ - cwal_value_unref(fs->vSrc); - fs->vSrc = 0; - } - if(fs->vImported){ - cwal_value_unref(fs->vImported); - fs->vImported = 0; - } - if(fs->vName){ - cwal_value_unref(fs->vName); - fs->vName = 0; - } - if(se->funcStash){ - /* cwal_value_unref(cwal_hash_value(se->funcStash)); */ -#if 0 - /* - We have to leave the script names in the hashtable for the time being. - We only allocate each one once, though. */ - if(fs->keyScriptName){ - cwal_hash_remove_v( se->funcStash, fs->keyScriptName ); - } -#endif - /* if(fs->keyName) cwal_hash_remove_v( se->funcStash, fs->keyName ); */ - }else{ - /* Corner case: cleanup of se->e during s2_engine_finalize(). - se->funcStash might be gone by then, meaning our - pointers would be dangling. No big deal here. */ - /* assert(!fs->vSrc); */ - /* assert(!fs->keyName); */ - } - if(fs->keyScriptName){ - cwal_value_unref(fs->keyScriptName); - fs->keyScriptName = 0; - } - *fs = s2_func_state_empty; - if(se->recycler.scriptFuncs.max>0 - && se->recycler.scriptFuncs.count < se->recycler.scriptFuncs.max - ){ - fs->next = se->recycler.scriptFuncs.head; - se->recycler.scriptFuncs.head = fs; - ++se->recycler.scriptFuncs.count; - }else{ - cwal_free2(se->e, fs, sizeof(s2_func_state)); - cwal_engine_adjust_client_mem(se->e, - -((cwal_int_t)sizeof(s2_func_state))); - } -} - - -void s2_engine_free_recycled_funcs( s2_engine * se ){ - int const oldMax = se->recycler.scriptFuncs.max; - s2_func_state * fs; - se->recycler.scriptFuncs.max = 0; - for( ; (fs = se->recycler.scriptFuncs.head); ){ - se->recycler.scriptFuncs.head = fs->next; - fs->next = 0; - assert(se->recycler.scriptFuncs.count); - --se->recycler.scriptFuncs.count; - s2_func_state_free( se, fs ); - } - assert(!se->recycler.scriptFuncs.count); - se->recycler.scriptFuncs.max = oldMax; -} - -/** - cwal finalizer for Function state. m must be a (s2_func_state*). -*/ -static void cwal_finalizer_f_func_state( cwal_engine * e, void * m ){ - s2_func_state *fs = (s2_func_state*)m; - s2_engine * se = s2_engine_from_state(e); - assert(m); - assert(se); - s2_func_state_free( se, fs ); -} - -s2_func_state * s2_func_state_for_func(cwal_function * f){ - return (s2_func_state *)cwal_function_state_get(f, &s2_func_state_empty); -} - - -static int s2_keyword_f_FLC( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_assert( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_breakpoint( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_builtin_vals( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_continue( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_define( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_dowhile( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_echo( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_enum( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_exception( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_for( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_foreach( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_function( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_if( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_import( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_nameof( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_new( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_pragma( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_refcount( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_reserved( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_s2out( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_typeinfo( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_typename( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_unset( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_ukwd( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_using( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_var( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); -static int s2_keyword_f_while( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); - - -static const struct S2_KEYWORDS__ { -/* - Keep sorted on keyword name (string) so that we can binary-search it. - - When adding new keywords, update all of: - - - Add a S2_T_KeywordXXX entry for it. - - - Add an entry in this struct for it, as well as an initializer in - the array which follows. - - - Add it to the following functions: - - -- s2_ttype_cstr() - -- s2_ttype_keyword() - -- s2_ptoken_keyword() - - And to s2-keyword-hasher.s2 (to generate the body of s2_ptoken_keyword() - and test the keyword hash algo for collisions). -*/ - s2_keyword const _breakpoint; - s2_keyword const _col; - s2_keyword const _file; - s2_keyword const _filedir; - s2_keyword const _flc; - s2_keyword const _line; - s2_keyword const affirm; - s2_keyword const assert_; - s2_keyword const break_; - s2_keyword const catch_; - s2_keyword const class_; - s2_keyword const const_; - s2_keyword const continue_; - s2_keyword const define; - s2_keyword const defined_; - s2_keyword const delete_; - s2_keyword const doWhile; - s2_keyword const echo; - s2_keyword const enum_; - s2_keyword const eval; - s2_keyword const exception_; - s2_keyword const exit_; - s2_keyword const false_; - s2_keyword const fatal_; - s2_keyword const for_; - s2_keyword const foreach_; - s2_keyword const function_; - s2_keyword const if_; - s2_keyword const import; - s2_keyword const include; - s2_keyword const inherits; - s2_keyword const interface_; - s2_keyword const is_; - s2_keyword const isa_; - s2_keyword const nameof; - s2_keyword const new_; - s2_keyword const null_; - s2_keyword const pragma; - s2_keyword const private_; - s2_keyword const proc_; - s2_keyword const protected_; - s2_keyword const public_; - s2_keyword const refcount_; - s2_keyword const return_; - s2_keyword const s2out; - s2_keyword const scope; - s2_keyword const static_; - s2_keyword const throw_; - s2_keyword const true_; - s2_keyword const try_; - s2_keyword const typeInfo; - s2_keyword const typeName; - s2_keyword const undef_; - s2_keyword const unset; - s2_keyword const using; - s2_keyword const var; - s2_keyword const while_; - s2_keyword const _sentinel_; -} S2_KWDS = { -/*{ id word, wordLen, call(), allowEOLAsEOXWhenLHS } */ - { S2_T_KeywordBREAKPOINT, "__BREAKPOINT", 12, s2_keyword_f_breakpoint, 0 }, - { S2_T_KeywordCOLUMN, "__COLUMN", 8, s2_keyword_f_FLC, 0 }, - { S2_T_KeywordFILE, "__FILE", 6, s2_keyword_f_FLC, 0 }, - { S2_T_KeywordFILEDIR, "__FILEDIR", 9, s2_keyword_f_FLC, 0 }, - { S2_T_KeywordSRCPOS, "__FLC", 5, s2_keyword_f_FLC, 0 }, - { S2_T_KeywordLINE, "__LINE", 6, s2_keyword_f_FLC, 0 }, - - { S2_T_KeywordAffirm, "affirm", 6, s2_keyword_f_assert, 0 }, - { S2_T_KeywordAssert, "assert", 6, s2_keyword_f_assert, 0 }, - { S2_T_KeywordBreak, "break", 5, s2_keyword_f_eval, 0 }, - { S2_T_KeywordCatch, "catch", 5, s2_keyword_f_eval, 0 }, - { S2_T_KeywordClass, "class", 5, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordConst, "const", 5, s2_keyword_f_var, 0 }, - { S2_T_KeywordContinue, "continue", 8, s2_keyword_f_continue, 0 }, - { S2_T_KeywordDefine, "define", 6, s2_keyword_f_define, 0 }, - { S2_T_KeywordDefined, "defined", 7, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordDelete, "delete", 6, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordDo, "do", 2, s2_keyword_f_dowhile, 1 }, - { S2_T_KeywordEcho, "echo", 4, s2_keyword_f_echo, 0 }, - { S2_T_KeywordEnum, "enum", 4, s2_keyword_f_enum, 0 }, - { S2_T_KeywordEval, "eval", 4, s2_keyword_f_eval, 1 }, - { S2_T_KeywordException, "exception", 9, s2_keyword_f_exception, 0 }, - { S2_T_KeywordExit, "exit", 4, s2_keyword_f_eval, 0 }, - { S2_T_KeywordFalse, "false", 5, s2_keyword_f_builtin_vals, 0 }, - { S2_T_KeywordFatal, "fatal", 5, s2_keyword_f_eval, 0 }, - { S2_T_KeywordFor, "for", 3, s2_keyword_f_for, 1 }, - { S2_T_KeywordForEach, "foreach", 7, s2_keyword_f_foreach, 1 }, - { S2_T_KeywordFunction, "function", 8, s2_keyword_f_function, 0 }, - { S2_T_KeywordIf, "if", 2, s2_keyword_f_if, 1 }, - { S2_T_KeywordImport, "import", 6, s2_keyword_f_import, 0 }, - { S2_T_KeywordInclude, "include", 7, s2_keyword_f_reserved, 0 }, - { S2_T_OpInherits, "inherits", 8, 0 /* handled as an operator */, 0 }, - { S2_T_KeywordInterface, "interface", 9, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordIs, "is", 2, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordIsA, "isa", 3, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordNameof, "nameof", 6, s2_keyword_f_nameof, 0 }, - { S2_T_KeywordNew, "new", 3, s2_keyword_f_new, 0 }, - { S2_T_KeywordNull, "null", 4, s2_keyword_f_builtin_vals, 0 }, - { S2_T_KeywordPragma, "pragma", 6, s2_keyword_f_pragma, 0 }, - { S2_T_KeywordPrivate, "private", 7, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordProc, "proc", 4, s2_keyword_f_function, 0 - /* - 20171115: Interesting: while using require.s2 to import a proc - from a file which contained only that proc (and thus evaluates - to that proc), the allowEOLAsEOXWhenLHS handling here uncovered, - for the first time, that proc does not like a trailing EOL when - it's the left-most part of an expression. That usage never - happens except in the case of import()ing or - s2_set_from_script()'ing a proc, and was never witnessed until - today. If we enable allowEOLAsEOXWhenLHS for procs, though, - then we'll almost cerainly get bitten someday by something like: - - proc(){...} - .importSymbols(...) - - Which terminates the expression after the proc and leads to - "illegal operator '.' at start of expression" before the - next line. - - So... for that corner case we'll just have to use semicolons - after LHS-most procs or (if the context allows) add a return - statement before the proc keyword. An LHS proc which is - immediately called does not demonstrate this problem because the - proc itself is not both the LHS and final expression component: - - proc(){...}() - */ - }, - { S2_T_KeywordProtected, "protected", 9, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordPublic, "public", 6, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordRefcount, "refcount", 8, s2_keyword_f_refcount, 0 }, - { S2_T_KeywordReturn, "return", 6, s2_keyword_f_eval, 0 }, - { S2_T_KeywordS2Out, "s2out", 5, s2_keyword_f_s2out, 0 }, - { S2_T_KeywordScope, "scope", 5, s2_keyword_f_eval, 1 }, - { S2_T_KeywordStatic, "static", 6, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordThrow, "throw", 5, s2_keyword_f_eval, 0 }, - { S2_T_KeywordTrue, "true", 4, s2_keyword_f_builtin_vals, 0 }, - { S2_T_KeywordTry, "try", 3, s2_keyword_f_reserved, 0 }, - { S2_T_KeywordTypeinfo, "typeinfo", 8, s2_keyword_f_typeinfo, 0 }, - { S2_T_KeywordTypename, "typename", 8, s2_keyword_f_typename, 0 }, - { S2_T_KeywordUndefined, "undefined", 9, s2_keyword_f_builtin_vals, 0 }, - { S2_T_KeywordUnset, "unset", 5, s2_keyword_f_unset, 0 }, - { S2_T_KeywordUsing, "using", 5, s2_keyword_f_using, 0 }, - { S2_T_KeywordVar, "var", 3, s2_keyword_f_var, 0 }, - { S2_T_KeywordWhile, "while", 5, s2_keyword_f_while, 1 }, - - {/*_sentinel_*/0,0,0,0,0} -}; - -static s2_keyword const * s2_ttype_keyword( int ttype ){ - switch( ttype ){ -#define WORD(E,Member) case E: return &S2_KWDS.Member - WORD(S2_T_KeywordBREAKPOINT,_breakpoint); - WORD(S2_T_KeywordCOLUMN,_col); - WORD(S2_T_KeywordFILE,_file); - WORD(S2_T_KeywordFILEDIR,_filedir); - WORD(S2_T_KeywordSRCPOS,_flc); - WORD(S2_T_KeywordLINE,_line); - - WORD(S2_T_KeywordAffirm,affirm); - WORD(S2_T_KeywordAssert,assert_); - WORD(S2_T_KeywordBreak,break_); - WORD(S2_T_KeywordCatch,catch_); - WORD(S2_T_KeywordClass,class_); - WORD(S2_T_KeywordConst,const_); - WORD(S2_T_KeywordContinue,continue_); - WORD(S2_T_KeywordDefine,define); - WORD(S2_T_KeywordDefined,defined_); - WORD(S2_T_KeywordDelete,delete_); - WORD(S2_T_KeywordDo,doWhile); - WORD(S2_T_KeywordEcho,echo); - WORD(S2_T_KeywordEnum,enum_); - WORD(S2_T_KeywordEval,eval); - WORD(S2_T_KeywordException,exception_); - WORD(S2_T_KeywordExit,exit_); - WORD(S2_T_KeywordFalse,false_); - WORD(S2_T_KeywordFatal,fatal_); - WORD(S2_T_KeywordFor,for_); - WORD(S2_T_KeywordForEach,foreach_); - WORD(S2_T_KeywordFunction,function_); - WORD(S2_T_KeywordIf,if_); - WORD(S2_T_KeywordImport,import); - WORD(S2_T_KeywordInclude,include); - WORD(S2_T_OpInherits,inherits); - WORD(S2_T_KeywordInterface,interface_); - WORD(S2_T_KeywordIs,is_); - WORD(S2_T_KeywordIsA,isa_); - WORD(S2_T_KeywordNameof,nameof); - WORD(S2_T_KeywordNew,new_); - WORD(S2_T_KeywordNull,null_); - WORD(S2_T_KeywordPragma,pragma); - WORD(S2_T_KeywordPrivate,private_); - WORD(S2_T_KeywordProc,proc_); - WORD(S2_T_KeywordProtected,protected_); - WORD(S2_T_KeywordPublic,public_); - WORD(S2_T_KeywordRefcount,refcount_); - WORD(S2_T_KeywordReturn,return_); - WORD(S2_T_KeywordS2Out,s2out); - WORD(S2_T_KeywordScope,scope); - WORD(S2_T_KeywordThrow,throw_); - WORD(S2_T_KeywordTrue,true_); - WORD(S2_T_KeywordTry,try_); - WORD(S2_T_KeywordTypeinfo,typeInfo); - WORD(S2_T_KeywordTypename,typeName); - WORD(S2_T_KeywordUndefined,undef_); - WORD(S2_T_KeywordUnset,unset); - WORD(S2_T_KeywordUsing,using); - WORD(S2_T_KeywordVar,var); - WORD(S2_T_KeywordWhile,while_); - -#undef WORD - default: - return 0; - } -} - -#define s2__keyword_perfect_hash( PTOKEN ) \ - s2_hash_keyword(s2_ptoken_begin(PTOKEN), s2_ptoken_len(PTOKEN)) -uint32_t s2_hash_keyword( char const * input, cwal_size_t len ){ - cwal_size_t i = 0; - uint64_t h = 0; - char const * pos = input; - for( ; i < len; ++i, ++pos ){ - /*All of these variations have worked out so far... - h = (h << 1) + 100*i + i*pt->begin[i] - i*35; - h = (h << 1) + 100*i + i*pt->begin[i] - i*35; - h = (h << 1) + 45*i + pt->begin[i] - 35; - - Note that the magic 35==ASCII '$'-1, the lowest-numbered - character allowed in an s2 keyword. - - ************************************************************ - WARNING: this algo MUST be kept in sync with the one in - s2-keyword-hasher.s2, as that script generates the C code - for our keyword/typeinfo/pragma bits. - ************************************************************ - */ - if(*pos > 'z' || *pos < '$') return 0; - h = (h << 1) + (i+1) * (*pos - 35/*==>ASCII '$'-1*/); - while(h > (uint64_t)0x7fffffff) - h = h>>1 - /* With a uint64_t hash, trim hash to within 32 bits because - we need to be able to use these values in switch/case, and - 64 bits are not portable for that. We *have* to use 64-bit - integers for the calculation because they're also - calculated in script-space, where we use (by and large) - 64-bit builds. - */; - } - return (uint32_t)h; -} - - -/** - If pt's [begin,end) range corresponds to a keyword, its entry from - S2_KWDS is returned, else NULL is returned. - - This is an O(1) search, requiring, at most, generation of 1 hash - code and (on a hash match) 1 string comparison. -*/ -static s2_keyword const * s2_ptoken_keyword( s2_ptoken const * pt ){ - const cwal_size_t tlen = s2_ptoken_len(pt); - if(tlen > sizeof("__BREAKPOINT"/*must be the longest keyword!*/)-1) return NULL; - switch(s2__keyword_perfect_hash(pt)){ -#define W(X,M) return tlen==(cwal_size_t)sizeof(X)-1 && \ - 0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1) \ - ? &S2_KWDS.M : NULL - /* Generated by s2-keyword-hasher.s2 (or equivalent): */ - - case 0x0609ce: W("__BREAKPOINT",_breakpoint); - case 0x0061bc: W("__COLUMN",_col); - case 0x00170e: W("__FILE",_file); - case 0x00c013: W("__FILEDIR",_filedir); - case 0x000b0c: W("__FLC",_flc); - case 0x0017b2: W("__LINE",_line); - case 0x001f9a: W("affirm",affirm); - case 0x00225c: W("assert",assert_); - case 0x000f50: W("break",break_); - case 0x000f05: W("catch",catch_); - case 0x000f88: W("class",class_); - case 0x001059: W("const",const_); - case 0x008ee4: W("continue",continue_); - case 0x001f82: W("define",define); - case 0x0040cb: W("defined",defined_); - case 0x00200e: W("delete",delete_); - case 0x00011a: W("do",doWhile); - case 0x0006de: W("echo",echo); - case 0x00077c: W("enum",enum_); - case 0x000740: W("eval",eval); - case 0x011e4b: W("exception",exception_); - case 0x0007a0: W("exit",exit_); - case 0x000f46: W("false",false_); - case 0x000f39: W("fatal",fatal_); - case 0x000329: W("for",for_); - case 0x00448b: W("foreach",foreach_); - case 0x009058: W("function",function_); - case 0x000112: W("if",if_); - case 0x0022f4: W("import",import); - case 0x0044a2: W("include",include); - case 0x008cb6: W("inherits",inherits); - case 0x01211a: W("interface",interface_); - case 0x00012c: W("is",is_); - case 0x000312: W("isa",isa_); - case 0x0020ba: W("nameof",nameof); - case 0x000330: W("new",new_); - case 0x0007c2: W("null",null_); - case 0x0021e8: W("pragma",pragma); - case 0x0048f2: W("private",private_); - case 0x0007a8: W("proc",proc_); - case 0x012d65: W("protected",protected_); - case 0x002294: W("public",public_); - case 0x008bd2: W("refcount",refcount_); - case 0x0023b0: W("return",return_); - case 0x000da5: W("s2out",s2out); - case 0x001042: W("scope",scope); - case 0x00233c: W("static",static_); - case 0x001118: W("throw",throw_); - case 0x0007f4: W("true",true_); - case 0x000382: W("try",try_); - case 0x0098e2: W("typeinfo",typeInfo); - case 0x009884: W("typename",typeName); - case 0x011f6d: W("undefined",undef_); - case 0x001135: W("unset",unset); - case 0x001114: W("using",using); - case 0x000331: W("var",var); - case 0x00106a: W("while",while_); - - default: break; -#undef W - } - return NULL; -} - - -/** - Comparison for bsearch() which compares keywords - by name. -*/ -static int cmp_ukwd_kw(void const * lhs, void const * rhs){ - s2_keyword const * l = (s2_keyword const *)lhs; - s2_keyword const * r = (s2_keyword const *)rhs; - return cwal_compare_cstr(l->word, l->wordLen, - r->word, r->wordLen); -} - -/** - A variant of s2_ptoken_keyword() which first calls that function, - and if it returns NULL then this function looks for user-defined - keywords, returning one if found, else returning NULL. -*/ -static s2_keyword const * -s2_ptoken_keyword2( s2_engine * se, s2_ptoken const * pt ){ - s2_keyword const * rc = s2_ptoken_keyword(pt); - if(!rc){ - s2_keyword dummy; - cwal_size_t tLen = 0; - s2_ukwd * const uk = s2__ukwd(se); - if(!uk || !uk->count) return 0; - dummy.word = s2_ptoken_cstr(pt, &tLen); - if(tLen>(unsigned short)-1/*overflow*/) return 0; - dummy.wordLen = (unsigned short)tLen; - ++se->metrics.ukwdLookups; - if(1==uk->count){ - /* Fast-track it! */ - rc = 0==cmp_ukwd_kw(&uk->list[0], &dummy) - ? &uk->list[0] - : 0; - }else{ - rc = (s2_keyword const*)bsearch(&dummy, uk->list, uk->count, - sizeof(s2_keyword), cmp_ukwd_kw); - } - if(rc) ++se->metrics.ukwdHits; - } - return rc; -} - - - -#if 0 -/** - If ttype matches a keyword's token type, ttype is returned, else 0 - is returned. Note that this returns non-0 for S2_T_OpInherits: the - "inherits" keyword is partially a keyword, partially an operator. - */ -int s2_ttype_is_keyword( int ttype ); -int s2_ttype_is_keyword( int ttype ){ - return s2_ttype_keyword(ttype) ? ttype : 0; -} -#endif - -/** - Internal-only flags for s2_eval_expr() and friends. -*/ -enum s2_eval_flags3_t { -/* - Maintenance reminder: flags must be greater than the highest flag - in s2_eval_flags. -*/ -/** - Tells s2_eval_expr() to treat the _first_ unresolved identifier as - the undefined value instead of an error. Used by 'typename' and - typeinfo(name ...). -*/ -S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED = 1 << S2_EVAL_flag_bits, -/** - Tells s2_eval_expr() not to skip the first EOL token. -*/ -S2_EVAL_NO_SKIP_FIRST_EOL = 2 << S2_EVAL_flag_bits, -/** - Unused/untested: tells s2_eval_expr() to sweep up before - evaluation. -*/ -S2_EVAL_PRE_SWEEP = 4 << S2_EVAL_flag_bits, -/** - Tells s2_eval_expr() that an empty parens group - is legal. Only used to support allow return(), - so it's a candidate for removal. -*/ -S2_EVAL_EMPTY_GROUP_OK = 8 << S2_EVAL_flag_bits, -/** - Experimental, doesn't work/do anything useful. -*/ -S2_EVAL_KEEP_STACK = 0x10 << S2_EVAL_flag_bits, -/** - To assist in the 'new' keyword. Tells s2_eval_expr_impl() to stop - at a call-like operation, as new() handles the arguments itself. -*/ -S2_EVAL_STOP_AT_CALL = 0x20 << S2_EVAL_flag_bits, -/** - Experimental: tells s2_eval_expr_impl() to not clear - s2_dotop_state() before returning (which it normally does unless a - fromLhsOp argument is passed to it). Used by (unset x.y) so that it - can get ahold of the 'this' part of the expression. -*/ -S2_EVAL_RETAIN_DOTOP_STATE = 0x40 << S2_EVAL_flag_bits, -/** - Tells eval that a trailing semicolon is not allowed. This is - primarily to allow [array] and {object} literals to catch - semicolons after the value parts of their inner expressions. - - e.g. without this then: - - [1;,2] evals to [1,2] because the semicolon legally terminates the - 1. Similarly, {a:1;, b:2;} is legal without this flag. - - Whether or not this is really a fix is arguable, as a semicolon - legally ends any expression. It was added more for pedandicness' - sake than to fix a problem. (It was only noticed by accident one - day that a trailing semicolon in an array literal was not being - caught as a syntax error.) -*/ -S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED = 0x80 << S2_EVAL_flag_bits -}; - - -static int s2_eval_expr_impl( s2_engine * se, - s2_ptoker * st, - s2_op const * fromLhsOp, - int evalFlags, - cwal_value ** rv); - -/** - An eval-level helper to check for interruption, which should trump - any result of just-performed evaluation. - - If rc is not 0... - - - if rv then *rv is set to 0. - - - potential todo: if *rv then *rv is passed to cwal_refunref(), - which may or may not clean it up immediately. - - If rc is 0, it rescopes *rv (if both rv and *rv are not NULL) to sc - (if not NULL). - - Always returns rc except when se->flags.interrupted trumps it, - in which case that non-0 code is returned. -*/ -static int s2_rv_maybe_accept( s2_engine * se, cwal_scope * sc, - int rc, cwal_value ** rv ){ - rc = s2_check_interrupted(se, rc); - switch(rc){ - case 0: - assert(se->e->current); - if(rv && *rv && sc) cwal_value_rescope(sc, *rv); - break; - default: - break; - } - if(rc && rv){ -#if 0 - if(*rv){ - /* This can theoretically (if all *rv-setters are well-behaved) - only happen when s2_check_interrupted() trumps an otherwise - successful operation. This block is not triggering in test - code, and would be difficult to trigger for testing, so it's - currently disabled. */ - assert(!"untested"); - cwal_refunref(*rv); - } -#endif - *rv = 0; - } - return rc; -} - - -int s2_err_ammend_flc(s2_engine * se, s2_ptoker const * st) /* Declared in s2.c */; - -/** - Internal helper for ammending stack-machine-level errors with - file/line/column info. The 3rd argument is the error code of the op - which just failed. - - Preconditions: - - - A stack-processing op must just have failed. - - - rc must be non-0. - - Preferably, se->err.msg is not empty. - - If !pr, se->currentScript is used. It asserts() that at least one - of those is non-NULL. - - Returns, on success, se->err.code, and some other non-0 value if - ammending the FLC info led to a more serious error (e.g. out of - memory or an interrupt). It will only return 0 if it is called when - se->err.code is 0, and in that case it will assert() in debug - builds. -*/ -static int s2_ammend_op_err(s2_engine * se, s2_ptoker const * pr, int rc){ - int const rcInterrupt = s2_check_interrupted(se,0); - assert(pr || se->currentScript); - if(rcInterrupt) return rcInterrupt; - switch(rc){ - case CWAL_RC_ASSERT: - case CWAL_RC_EXCEPTION: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_INTERRUPTED: - case CWAL_RC_OOM: - break; - case CWAL_SCR_SYNTAX: /* Not QUITE the condition i want, but it - keeps existing tests running for the time - being. */ - assert(s2__err(se).code); - rc = s2_err_ammend_flc(se, pr ? pr : se->currentScript); - break; - default: - /* Translate it to an exception... */ - assert(s2__err(se).code); - /* MARKER(("opErrPos=%p\n", (void const *)se->opErrPos)); */ - rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript); - break; - } - se->opErrPos = 0 - /* Avoid a stale pointer in some rare cases (namely s2sh REPL loop). - Stale pointer led to an assert() in s2_t10n's line-counting code - via this s2sh session: - - var e = enum e{a} - e#'a' // (1) op error here - e = enum e{#a} - catch e = enum e{#a} - catch { e = enum e{#a} } // syntax error here referenced (1). - */; - return rc; - -} - - -void s2_dotop_state( s2_engine * se, cwal_value * self, - cwal_value * lhs, cwal_value * key ){ - -#if 0 - /* Leave this around in case we have to go ref hunting - sometime. */ - se->dotOp.self = self; - se->dotOp.lhs = lhs; - se->dotOp.key = key; -#else - /* Reminder: the validity checks here point to, essentially, - misuse in s2, where we've not cleaned up this state before it - goes stale. - - A potential future problem is vacuuming-up of these pointers, - which case we can't solve without a per-s2_scope - array/container to hold these and make them vacuum-proof. - */ - cwal_value * oldSelf = se->dotOp.self; - cwal_value * oldLhs = se->dotOp.lhs; - cwal_value * oldKey = se->dotOp.key; - /** - Assert that all of them appear to still be valid references - (because it's easy to mess that up). These assertions are not - guaranteed to trigger in all error cases, but they catch the - most common one that we've prematurely unref'd a value and he - have a pointer to its cwal-side recycling bin. - */ - /* MARKER(("Setting se->dotOpXXX...\n")); */ - if(oldSelf){ - assert( cwal_value_scope(oldSelf) - || cwal_value_is_builtin(oldSelf) ); - } - if(oldLhs){ - assert( cwal_value_scope(oldLhs) - || cwal_value_is_builtin(oldLhs)); - } - if(oldKey){ - assert( cwal_value_scope(oldKey) - || cwal_value_is_builtin(oldKey) ); - } - /** - Because any of self/lhs/key can refer to or contain/own any - other, as well as be the same instance of oldSelf/oldLhs/oldKey - (in any combination!), we have to ref them all before we unref - any of them. - */ - if(self) cwal_value_ref(self); - if(lhs) cwal_value_ref(lhs); - if(key) cwal_value_ref(key); - - se->dotOp.self = self; - se->dotOp.lhs = lhs; - se->dotOp.key = key; - - if(oldSelf) cwal_value_unref(oldSelf); - if(oldLhs) cwal_value_unref(oldLhs); - if(oldKey) cwal_value_unref(oldKey); - /* MARKER(("Done setting se->dotOpXXX\n")); */ -#endif -} - -/** - Internal helper for s2_eval_expr(). - - Possibly processes pending operators in se's stack, depending on op - and its precedence in relation to the operator (if any) to the left - (i.e. in s2's operator stack). Returns 0 on success (which includes - it doing nothing of note). - - Specifically: if se->st has a pending operator (and operand(s)) - with a higher priority than op, or the same priority but the - operator is left-associative, then that pending operator is - processed. This repeats, if needed, to resolve all pending LHS ops - until se->st is out of ops or we hit an op with a lower precedence - than the given op (or equal precedence but not left-associative). -*/ -static int s2_eval_lhs_ops(s2_engine * se, s2_ptoker const * pr, s2_op const * op){ - s2_stoken * topOpTok = s2_engine_peek_op(se); - s2_op const * topOp = topOpTok ? s2_stoken_op(topOpTok) : 0; - int rc = s2_check_interrupted(se, 0); - assert(op); - if(rc) return rc; - else if(topOp - && topOp->placement!=0 - && topOp->arity>0 - && (s2_ttype_is_assignment(op->id) || s2_ttype_is_assignment_combo(op->id)) - ){ - return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Invalid operator '%s' preceeding '%s'.", - topOp->sym, op->sym); - /* This is admittedly a workaround for the X.Y=Z family of ops, - which do not get a chance to set se->dotLhsOp and se->dotLhsKey - before the ++ is run. It turns out that JavaScript disallows - such ops on the LHS of an assignment, too. - */ - } - while(topOp && - ((op->prec < topOp->prec) - || (op->assoc<0 && (op->prec==topOp->prec))) - ){ - if(se->flags.traceTokenStack){ - MARKER(("Processing ge-precedent op '%s' to " - "the left of '%s'.\n", - topOp->sym, op->sym)); - s2_dump_val(se->dotOp.lhs,"se->dotOp.lhs"); - s2_dump_val(se->dotOp.key,"se->dotOp.key"); - } - rc = s2_process_top(se); - if(rc){ - rc = s2_ammend_op_err(se, 0, rc); - assert(rc); - break; - } - assert(se->st.vals.size>0); - topOpTok = s2_engine_peek_op(se); - topOp = topOpTok ? s2_stoken_op(topOpTok) : 0; - } - return rc; -} - -/** - Evaluates the contents of pr->token, treating it like a sub-parser. - - If asExpr is true it uses s2_eval_expr_impl() to process one - expression, otherwise it uses s2_eval_ptoker() to parse it all as a - collection of expressions. -*/ -static int s2_eval_current_token( s2_engine * se, s2_ptoker * pr, - char asExpr, - int evalFlags, - cwal_value **rv ){ - int rc = s2_check_interrupted(se, 0); - s2_ptoker_errtoken_set(pr, 0); - /* assert(!name || nameLen>0); */ - if(rc) return rc; - else if(!s2_ptoken_has_content(&pr->token)){ - /* we know it's empty, so don't bother.*/ - if(rv) *rv = 0 - /* not the undefined value, so that the caller can - differentiate empty expressions/bodies (which might - not be legal in a given context). */; - return 0; - }else{ - s2_ptoker sub = s2_ptoker_empty; - int rc = s2_ptoker_sub_from_toker(pr, &sub); - assert(sub.parent == pr); - /* 20200902: breaks stuff: assert(pr->e); */ - assert(sub.e == pr->e); - if(rc){ - s2_ptoker_errtoken_set(pr, &pr->token) /* TODO: remove this (and test it). */; - goto end; - } - else if(asExpr){ - rc = s2_eval_expr_impl( se, &sub, 0, evalFlags, rv ); - } - else{ - s2_subexpr_savestate save = s2_subexpr_savestate_empty_m; - s2_engine_subexpr_save(se, &save) - /* Saves/resets ternary level */; - rc = s2_eval_ptoker( se, &sub, - evalFlags ? evalFlags : S2_EVALP_F_PROPAGATE_RETURN, - rv ); - s2_engine_subexpr_restore(se, &save); - } - if(rc) s2_ptoker_errtoken_set(pr, s2_ptoker_errtoken_get(&sub)); - pr->capture = sub.capture; - end: - s2_ptoker_finalize(&sub); - return rc; - } -} - -/** - Intended to be called at the end of a subexpression if: - - a) that subexpression generated an error which _might_ be one of - CWAL_RC_BREAK/RETURN/CONTINUE. - - b) wants to report that as an error, using the tokenizer's current - location information. - - Alternately, they can let it propagate and hope that it's handled - higher up. - - The 2nd parameter is the tokenizer in which (or through which) the - error was triggered. - - The 3rd parameter is the result code of the subexpression which - failed. If it is one of CWAL_RC_BREAK/RETURN/CONTINUE then this - function clears any propagating value (if needed) and triggers a - tokenizer error. All other values of rc have no side-effects. - - The 4th parameter is a string snippet used for the error report. - It should be a brief description of the context, suitable for - appending to a string in the form "Unhandled 'break' in ...". - - The "new" rc is returned, which will be either rc itself or, on - allocation error while setting the error state, CWAL_RC_OOM. -*/ -static int s2_check_brc( s2_engine * se, - s2_ptoker * pr, - int rc, - char const * contextDescr){ - s2_keyword const * kword = 0; - switch(rc){ - case CWAL_RC_RETURN: - kword = s2_ttype_keyword(S2_T_KeywordReturn); - assert(kword); - s2_propagating_set(se, 0); - /* s2_engine_err_reset(se); */ - break; - case CWAL_RC_BREAK: - kword = s2_ttype_keyword(S2_T_KeywordBreak); - assert(kword); - s2_propagating_set(se, 0); - /* s2_engine_err_reset(se); */ - break; - case CWAL_RC_CONTINUE: - kword = s2_ttype_keyword(S2_T_KeywordContinue); - assert(kword); - /* s2_engine_err_reset(se); */ - break; - default: break; - } - if(kword){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - /* if we pass on the same rc, error reporting - won't DTRT. */ - "Unhandled '%s' in %s.", - kword->word, contextDescr); - } - return rc; -} - -/** - Like s2_eval_current_token(se, pr, 0, 0, rv), but pushes a scope and - sets a local "this" variable to the self value. It is intended to - be called only for the context of the code block in: - - new X() {this block} - - pr->token MUST of type S2_T_SquigglyBlock: this function assert()s - it (only to simplify skip-mode handling, though we could extend it - to handle other block-level constructs as well). - - This function treats script-propagated return/break/continue - conditions as an error. - - ACHTUNG: self must not be a temporary value: it MUST have a ref - or this will kill it. Exception: in skip-mode, self is not - evaluated. -*/ -static int s2_eval_current_token_with_this( s2_engine * se, s2_ptoker * pr, - cwal_value * self, - cwal_value **rv ){ - assert( S2_T_SquigglyBlock==pr->token.ttype ); - if(se->skipLevel){ - if(rv) *rv = cwal_value_undefined(); - return 0; - }else{ - cwal_scope sc = cwal_scope_empty; - cwal_value * xrv = 0; - int rc = cwal_scope_push2(se->e, &sc); - if(rc) return rc; - rc = s2_var_decl_v(se, se->cache.keyThis, self, 0); - if(!rc){ - rc = s2_eval_current_token(se, pr, 0, - 0, rv ? &xrv : NULL); - } - cwal_scope_pop2(se->e, rc ? 0 : xrv); - if(!rc && rv) *rv = xrv - /* will be NULL on an empty expr except in - skip-mode */; - if(rc){ - rc = s2_check_brc( se, pr, rc, - "'new' post-ctor script block" ); - } - return rc; - } -} - - -int s2_eval_expr( s2_engine * se, s2_ptoker * st, - int evalFlags, - cwal_value ** rv){ - return s2_eval_expr_impl(se, st, 0, evalFlags, rv); -} - -/** - Expects pr to be a comma-separated list of expressions, until its - EOF. Empty expressions and a stray trailing comma are not - allowed. Evaluates each token and appends it to dest. Returns 0 on - success. Some errors are thrown as exceptions, but syntax errors - are not and propagated errors might not be exceptions. - - If allowAtExpansion is true then it allows a minor syntax expansion: - - Any _array value_ (including deriving from Array) in the list may - start with the @ symbol. Its slot in the array is replaced with the - entries from the array value. e.g. - - print(@[1,2,@[3]]) ==> print(1,2,3) - - If a non-array comes after a @, an exception is triggered. An empty - array causes no slots to be filled. -*/ -static int s2_eval_to_array( s2_engine * se, s2_ptoker * pr, cwal_array * dest, - int allowAtExpansion){ - int rc = s2_check_interrupted(se, 0); - s2_op const * opComma = s2_ttype_op(S2_T_Comma); - s2_ptoken prev = s2_ptoken_empty; - char const * errMsg = 0; - assert(!se->skipLevel); - /* s2_dotop_state(se, 0, 0, 0); */ - while( !rc ){ - s2_ptoken next = s2_ptoken_empty; - cwal_value * v = 0; - char hadAtPrefix = 0; - /* if((int)'@' == pr->token; */ - if(allowAtExpansion){ -#if 0 - rc = s2_ptoker_next_token_skip_junk(pr) - /* ^^^ cheaper than s2_next_token() */; - /** - 20190820... it turns out that the cheap option breaks when we do: - - func(a, b, c, - @blah) - - with a newline. Hmm. Interestingly, i only found the solution - so quickly because of the comment above that - s2_ptoker_next_token_skip_junk() is "cheaper than - s2_next_token()". Had that comment not been there, i'd have - been searching for years. - */ -#else - rc = s2_next_token( se, pr, 0, 0 ); -#endif - if(rc) break; - else if(S2_T_At==pr->token.ttype){ - hadAtPrefix = 1; - }else{ - s2_ptoken const tmp = pr->token; - s2_ptoker_putback(pr) /* rewind to just before non-junk - token */; - s2_ptoker_next_token_set(pr, &tmp) - /* this one has a relatively large effect (~20% of the 10k - nextToken hits in the current tests) */; - } - } - if( (rc = s2_eval_expr_impl( se, pr, opComma, - S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED, - &v)) ){ - break; - } - else if(!v){ - if(s2_ptoker_is_eof(pr)){ - if(S2_T_Comma==prev.ttype){ - s2_ptoker_errtoken_set(pr, &prev); - errMsg = "Unexpected, comma at end, of list,"; - goto bad_comma; - } - else break; - } - else if(/*(S2_T_Comma==pr->token.ttype) - &&*/ (!prev.ttype || (S2_T_Comma == pr->token.ttype)) - ){ - errMsg = "Unexpected, comma in, list."; - goto bad_comma; - } - else{ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected empty expression result."); - break; - } - }else{ /* v === non-NULL result from eval */ - if(hadAtPrefix){ - /* Expand arrays/tuples prefixed with '@' into the destination array... */ - cwal_size_t i, n; - cwal_array * av = 0; - cwal_tuple * tp = 0; - cwal_value_ref(v); - av = cwal_value_array_part(se->e, v); - if(!av) tp = cwal_value_get_tuple(v); - if(!av && !tp){ - cwal_value_unref(v); - return s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Illegal (non-array/tuple) value following '@'."); - } - n = av ? cwal_array_length_get(av) : cwal_tuple_length(tp); - for( i = 0; !rc && i < n; ++i ){ - rc = av - ? cwal_array_append(dest, cwal_array_get(av, i)) - : cwal_array_append(dest, cwal_tuple_get(tp, (uint16_t)i)); - } - cwal_value_unref(v); - }else{ - cwal_value_ref(v); - rc = cwal_array_append(dest, v); - cwal_value_unref(v); - } - if(rc) break; - else if( (rc = s2_next_token( se, pr, 0, &next )) ) break; - else if(s2_ttype_is_eof(next.ttype)){ - if(S2_T_Comma==pr->token.ttype){ - /* s2_ptoker_errtoken_set(pr, &next); */ - errMsg = "Unexpected, comma at end, of list,"; - goto bad_comma; - } - else{ - /*???*/ /*pr->nextToken = next;*/ - break; - } - } - else if(S2_T_Comma != next.ttype){ - errMsg = "Expecting ',' after list element."; - goto bad_comma; - }else{ - prev = next; - s2_ptoker_token_set(pr, &next) /* consume comma */; - } - } - rc = s2_check_interrupted(se, rc); - } - return rc; - bad_comma: - assert(errMsg); - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, "%s", errMsg); -} - -/** - Parser for [array, literals] and [#tuple literals]. Requires - pr->token.ttype to be S2_T_BraceGroup. In skip mode, *rv is set to - the undefined value and 0 is returned, else the [...] block is - parsed as a comma-separated list of expressions and (on success) - *rv is set to a new Array or Tuple value. - - TODO: the tuple impl is rather inefficient because it first creates - an array. We've got to do that (or traverse the tokens twice) so - that we have the size of the tuple (required for construction). We - could think about adding a tuple factory to cwal which transfers - list memory from an array to a tuple. -*/ -static int s2_eval_array_literal( s2_engine * se, s2_ptoker * pr, cwal_value ** rv ){ - s2_ptoker sub = s2_ptoker_empty; - int rc = s2_check_interrupted(se, 0); - assert(S2_T_BraceGroup==pr->token.ttype); - if(rc) return rc; - else if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - }else{ - cwal_array * ar = 0; - cwal_value * av = 0; - cwal_scope scope = cwal_scope_empty; - int gotTuple = 0; - if(!s2_ptoken_has_content(&pr->token)){ - return (*rv = cwal_new_array_value(se->e)) ? 0 : CWAL_RC_OOM; - } - else if((rc = cwal_scope_push2(se->e, &scope))) return rc; - rc = s2_ptoker_init_v2( se->e, &sub, s2_ptoken_adjbegin(&pr->token), - (int)s2_ptoken_len2(&pr->token), 0 ); - if(rc) goto end; - sub.parent = pr; - gotTuple = s2_ptoker_next_is_ttype(se, &sub, S2_NEXT_NO_POSTPROCESS, S2_T_OpHash, 1); - if((rc = s2_check_interrupted(se, 0))) goto end; - else if(!(ar = cwal_new_array(se->e))){ - rc = CWAL_RC_OOM; - goto end; - } - av = cwal_array_value(ar); - cwal_value_ref(av); - rc = s2_eval_to_array( se, &sub, ar, 1 ); - if(!rc && gotTuple){ - cwal_size_t const nA = cwal_array_length_get(ar); - uint16_t const nT = (uint16_t)nA; - cwal_tuple * tp = 0; - if((cwal_size_t)nT != nA){ - s2_ptoker_errtoken_set(pr, &sub.token); - rc = s2_throw_ptoker(se, pr, CWAL_RC_RANGE, "Too many entries for a tuple."); - }else{ - tp = cwal_new_tuple(se->e, nT); - if(!tp){ - rc = CWAL_RC_OOM; - }else{ - uint16_t i = 0; - for( ; i < nT && !rc; ++i ){ - rc = cwal_tuple_set(tp, i, cwal_array_get(ar, i)); - assert(!rc && "No known error conditions here. All memory has been allocated."); - } - ar = 0; - cwal_value_unref(av); - av = cwal_tuple_value(tp); - cwal_value_ref(av); - } - } - } - rc = s2_check_interrupted(se, rc); - if(rc) rc = s2_check_brc(se, &sub, rc, - gotTuple - ? "tuple literal" - : "array literal"); - if(!rc){ - assert(av); - cwal_value_unhand(av); - *rv = av; - }else if(av){ - cwal_value_unref(av); - } - end: - s2_ptoker_finalize(&sub); - cwal_scope_pop2(se->e, rc ? 0 : *rv); - return rc; - } -} - -/** Internal helper for s2_eval_to_object() */ -static int s2_eval_object_prop_set( s2_engine * se, s2_ptoker * pr, - cwal_value * dest, - cwal_value * key, cwal_value * val, - cwal_flags16_t flags ){ - int rc; - cwal_function * f = 0; - if(0==cwal_value_compare(key, se->cache.keyCtorNew) && - (f = cwal_value_function_part(se->e, val))){ - /* If the key is '__new' and v is call()able, set it as - hidden/const, simply for consistency across classes (as - opposed to it being hidden/const in C-created cases but not - script-created). We should arguably do this in - s2_set_with_flags_v(), but that code is complex enough as - it is :/. Maybe someday. - - 2020-02-20: with the addition of {@anObject} property copying - syntax, the flagging of constructors as hidden *might* be - considered a misbehaviour, since {@x} will not copy a ctor - property to the target object. Feature or bug? - */ - rc = s2_ctor_method_set(se, dest, f); - }else{ - /* use s2_set_v() for the 'prototype' and object-mode-hash - handling, as well as se->err error reporting. */ - rc = s2_set_with_flags_v(se, dest, key, val, flags); - } - return s2_handle_set_result(se, pr, rc); -} - -/** State for use with cwal_kvp_visitor_props_copy_s2() */ -typedef struct { - s2_engine * se; - s2_ptoker * ptoker; - cwal_value * store; -} S2CopyPropsState; - -/** Internal helper for s2_eval_to_object() */ -static int cwal_kvp_visitor_props_copy_s2( cwal_kvp const * kvp, - void * state ){ - S2CopyPropsState * const sps = (S2CopyPropsState*)state; - return s2_eval_object_prop_set(sps->se, sps->ptoker, sps->store, - cwal_kvp_key(kvp), cwal_kvp_value(kvp), - cwal_kvp_flags(kvp)); -} - - -/** - Expects pr to contain, until EOF, JavaScript-style object key/value pairs: - - k1: v1, k2: v2 ... - - Returns 0 on success, throws or propagates an error on error. - - The resulting container is written to *rv on success. On error - *rv is not modified. - - If propCount is not 0 then it is set to the number of properties - parsed on success. - - If inputHasHashMarker is not 0 then: - - - *inputHasHashMarker is set to 0 before starting. - - - the syntax is extended a little bit: it allows, at the start - (only) one # symbol, *inputHasHashMarker to be incremented by - one. If one is seen then this functon creates a cwal_hash, - otherwise it creates a cwal_object. If a # is encountered while - inputHasHashMarker is NULL then it is a syntax error. - - Extensions to conventional JS-style syntax: - - 1) { [expression-as-a-key]: value }, where the expression's value - becomes the key. - - 2) { @otherContainer } is essentially the same as JS's spread - syntax, importing the iterable (non-hidden) properties from - @otherContainer into the literal. This syntax is not yet permitted - for hashes because of potential semantic ambiguities in handling of - hash vs. object properties. - -*/ -static int s2_eval_to_object( s2_engine * se, s2_ptoker * pr, cwal_value ** rv, - cwal_size_t * propCount, - int *inputHasHashMarker){ - s2_ptoken prev = s2_ptoken_empty; - s2_op const * opComma = s2_ttype_op(S2_T_Comma); - cwal_size_t count = 0; - char seenHash = 0; - cwal_value * store = 0; - int rc = s2_check_interrupted(se, 0); - cwal_value * v = 0; - cwal_value * vKey = 0; - assert(!se->skipLevel); - assert(opComma); - if(inputHasHashMarker) *inputHasHashMarker = 0; - while( !rc && !s2_ptoker_is_eof(pr)){ - s2_ptoken ident = s2_ptoken_empty; - s2_ptoken startPos = pr->token; - cwal_flags16_t fSetFlags = 0 /* flags for cwal_prop_set_with_flags_v() - and friends. */; - char gotAtSign = 0 /* true if we get a @-style key */; - assert(!v); - assert(!vKey); - /*pr->flags |= S2_T10N_F_IDENTIFIER_DASHES; - ^^^ potentially interesting. */ - rc = s2_next_token(se, pr, 0, 0); - /*pr->flags &= ~S2_T10N_F_IDENTIFIER_DASHES;*/ - if( rc ) break; - else if((S2_T_Comma == pr->token.ttype) - && (!prev.ttype || (S2_T_Comma == prev.ttype)) - ){ - goto bad_comma; - } - else if(s2_ptoker_is_eof(pr)){ - if(S2_T_Comma == prev.ttype){ - s2_ptoker_errtoken_set(pr, &prev); - goto bad_comma; - } - else break; - }else if(inputHasHashMarker && !seenHash - && !count && (int)'#'==pr->token.ttype){ - ++seenHash; - ++*inputHasHashMarker; - continue; - }else if((int)'#'==pr->token.ttype){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Extra '#' in input."); - break; - } -#if 0 - MARKER(("OBJ KEY: %s %.*s\n", s2_ttype_cstr(pr->token.ttype), - (int)s2_ptoken_len(&pr->token), - s2_ptoken_begin(&pr->token))); -#endif - else if(S2_T_BraceGroup==pr->token.ttype){ - /* 20191117: check for {[key expr]: value} - a.k.a. Expression-as-a-Key (EaaK, pronounced like "eek"). - */ - s2_ptoker sub = s2_ptoker_empty; - rc = s2_ptoker_sub_from_toker(pr, &sub); - if(rc){ - s2_ptoker_finalize(&sub); - break; - } - rc = s2_eval_ptoker( se, &sub, 0, &vKey ); - s2_ptoker_finalize(&sub); - if(rc){ - assert(!vKey); - break; - } - else if(!vKey){ - rc = s2_err_ptoker(se, pr, CWAL_RC_MISUSE, - "Unexpected NULL eval result while processing " - "[...] object literal key."); - break; - } - cwal_value_ref(vKey); - /*s2_dump_val(vKey, "vKey");*/ - } - else if(S2_T_At == pr->token.ttype){ - gotAtSign = 1; - } - else if(!s2_ttype_is_object_keyable(pr->token.ttype)){ - s2_ptoker_errtoken_set(pr, &startPos); - rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting identifier or 'simple' " - "value as object key."); - break; - } - ident = pr->token; - /*MARKER(("ident =%.*s\n", (int)s2_ptoken_len(&pr->token), s2_ptoken_begin(&pr->token)));*/ - if(!gotAtSign){ - rc = s2_next_token(se, pr, 0, 0); - if(rc) break; - else if(S2_T_OpColonEqual == pr->token.ttype){ - fSetFlags = CWAL_VAR_F_CONST; - }else if(S2_T_Colon != pr->token.ttype){ - /* check for {standaloneIdentifier} ... */ - if(vKey){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "{[object key]} syntax requires a " - "following colon."); - break; - } - else if(S2_T_Identifier==ident.ttype - && (S2_T_Comma == pr->token.ttype - || s2_ptoker_is_eof(pr/* virtual EOF at end of object: {a:1,...,b} */)) - ){ - /* JS-like {a,b} ==> {a: a, b: b} */ - s2_ptoker_putback(pr) /* comma/eof will be handled downstream */; - v = s2_var_get(se, -1, s2_ptoken_begin(&ident), s2_ptoken_len(&ident)); - if(!v){ - rc = s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, - "Could not resolve identifier '%.*s'.", - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - break; - } - }else{ - rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting ':' after object key."); - break; - } - } - } - if(!v && (rc = s2_eval_expr_impl( se, pr, opComma, - S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED, - &v)) ){ - break; - } - else if(!v){ - rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX, - "An empty expression is not allowed here."); - break; - }else if( gotAtSign ){ - if(!cwal_props_can(v) || s2_value_is_enum(v)){ - /** - The "problem" with enums is that they can use either object - or hash storage internally, so we would need to - differentiate between the two in the @ expansion. We don't - currently do that, but maybe someday will. - */ - rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Value after @ must be a non-enum " - "container type."); - break; - } - assert(!vKey); - vKey = v /* simplifies handling below */; - cwal_value_ref(vKey); - } - cwal_value_ref(v); - { - s2_ptoken next = s2_ptoken_empty; - /*s2_dump_val(v, "object entry value");*/ - if( (rc = s2_next_token( se, pr, 0, &next )) ) break; - else if(!s2_ttype_is_eof(next.ttype) && S2_T_Comma != next.ttype){ - rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Got token type %s, but expected ',' or " - "end-of-object after object entry.", - s2_ttype_cstr(next.ttype)); - break; - } - - if(!store){ - /* Create the property storage: Object or Hash */ - store = seenHash - ? cwal_new_hash_value(se->e, 7) - : cwal_new_object_value(se->e); - if(!store){ - rc = CWAL_RC_OOM; - break; - }else{ - cwal_value_make_vacuum_proof(store, 1); - cwal_value_ref(store); - if(seenHash){ - s2_hash_dot_like_object(store, 1 - /* Needed for remainder of the loop - so that s2_set_v() will DTRT. - Might get overwritten afterwards*/); - } - } - } - - /* And finally... insert the key/value... */ - if( !vKey ){ - assert(!gotAtSign); - if((rc = s2_ptoken_create_value(se, pr, &ident, &vKey))) break; - assert(vKey); - /* s2_dump_val(vKey, "object key"); */ - cwal_value_ref(vKey); - } - if( gotAtSign ){ - if(seenHash){ - rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "@ property expansion is not currently " - "permitted for hash literals."); - break; - }else{ - S2CopyPropsState sps; - assert(vKey == v); - sps.se = se; - sps.ptoker = pr; - sps.store = store; - rc = cwal_props_visit_kvp( v, cwal_kvp_visitor_props_copy_s2, - &sps ); - } - }else{ - rc = s2_eval_object_prop_set(se, pr, store, vKey, v, fSetFlags); - } - cwal_value_unref(vKey); - vKey = 0; - cwal_value_unref(v); - v = 0; - if(rc) break; - ++count; - prev = next; - s2_ptoker_token_set(pr, &next) /* consume comma */; - } - assert(!rc); - rc = s2_check_interrupted(se, rc); - }/* loop over object body */ - - if(!rc && seenHash){ - /* Resize the hashtable if it doesn't appear adequately sized... */ - cwal_hash * h = cwal_value_get_hash(store); - if(!h){ - assert(!count); - cwal_value_unref(store); - store = cwal_new_hash_value(se->e, 5); - if(!store){ - rc = CWAL_RC_OOM; - }else{ - cwal_value_ref(store); - } - }else{ - rc = cwal_hash_grow_if_loaded( h, 0.75 ); - } - } - - end: - cwal_value_unref(v); - cwal_value_unref(vKey); - if(store){ - cwal_value_make_vacuum_proof(store, 0); - } - if(rc){ - cwal_value_unref(store); - rc = s2_check_brc( se, pr, rc, seenHash - ? "hash literal" - : "object literal" ); - }else{ - if(propCount) *propCount = count; - if(!store){ - assert(!count); - store = seenHash - ? cwal_new_hash_value(se->e, 5) - : cwal_new_object_value(se->e); - if(!store){ - rc = CWAL_RC_OOM; - }else{ - cwal_value_ref(store); - } - } - if(store){ - assert(!rc); - cwal_container_client_flags_set(store, seenHash>1 - ? S2_VAL_F_DOT_LIKE_OBJECT - : 0); - cwal_value_unhand(store); - *rv = store; - }else{ - assert(rc); - } - } - return rc; - bad_comma: - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected comma in %s literal.", - seenHash ? "hash" : "object"); - goto end; -} - -/** - Evaluator for object literal blocks. Asserts that - S2_T_SquigglyBlock ==pr->token.ttype. - - On success, *rv will hold the parsed object. - - Returns 0 on success, of course. -*/ -static int s2_eval_object_literal( s2_engine * se, s2_ptoker * pr, - cwal_value ** rv ){ - int rc = s2_check_interrupted(se, 0); - assert( S2_T_SquigglyBlock == pr->token.ttype ); - if(rc) return rc; - else if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - }else{ - cwal_value * ov = 0; - cwal_scope scope = cwal_scope_empty; - if(!s2_ptoken_has_content(&pr->token)){ - return (*rv = cwal_new_object_value(se->e)) ? 0 : CWAL_RC_OOM; - } - else if((rc = cwal_scope_push2(se->e, &scope))) return rc; - else{ - s2_ptoker sub = s2_ptoker_empty; - cwal_size_t propCount = 0; - int doHash = 0; - rc = s2_ptoker_sub_from_toker(pr, &sub); - if(!rc){ - rc = s2_eval_to_object( se, &sub, &ov, &propCount, &doHash ); - } - s2_ptoker_finalize(&sub); - } - if(!rc){ - assert(ov); - *rv = ov; - } - cwal_scope_pop2(se->e, rc ? 0 : *rv); - return rc; - } -} - - -/** - Iternal impl for ternary op: (IF ? THEN : ELSE). Expects the top of - the value stack to hold the LHS value (the IF part). On success it - replaces that value with the result of either its THEN or ELSE - parts. On success, pr->token will be set up so that its next token - will be the one after the ELSE expression. If rv is not 0 then the - result is also placed in *rv. -*/ -static int s2_eval_ternary( s2_engine * se, s2_ptoker * pr, - cwal_value **rv){ - int rc; - cwal_value * lhs /* the IF part of (IF ? THEN : ELSE) */; - cwal_value * rhs1 = 0 /* the THEN part */, - * rhs2 = 0 /* the ELSE part */; - cwal_value ** rhs = 0 /* bool(lhs) ? &rhs1 : &rhs2 */; - char buul; - s2_ptoken pos = pr->token; - s2_ptoken const origin = pos; - s2_op const * fromLhsOp = s2_ttype_op(S2_T_Question); - int const oldTernaryLevel = se->ternaryLevel; - assert(fromLhsOp); - assert(S2_T_Question==pr->token.ttype); - - rc = s2_eval_lhs_ops(se, pr, s2_ttype_op(S2_T_Question)); - if(rc) return rc; - lhs = s2_engine_peek_value(se); - if(!lhs){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting value before X?Y:Z op"); - } - buul = cwal_value_get_bool(lhs); - assert(cwal_value_is_builtin(lhs) - || cwal_value_refcount(lhs)/*expecting eval holder ref*/); - cwal_value_ref(lhs); - rhs = buul ? &rhs1 : &rhs2; - ++se->ternaryLevel; - - /* Eval the THEN part... */ - rc = s2_eval_expr_impl(se, pr, fromLhsOp/*0?*/, - buul ? 0 : S2_EVAL_SKIP, - &rhs1); - /*MARKER(("Got rhs1 rc=%d: [[[%.*s]]\n", rc, - (int)(s2_ptoken_begin(&pr->capture.end)-s2_ptoken_end(&pr->capture.begin)), - s2_ptoken_begin(&pr->capture.begin)));*/ - cwal_value_ref(rhs1); - if(rc) goto end; - rc = s2_next_token(se, pr, 0, 0); - if( !rc && (S2_T_Colon != pr->token.ttype) ){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting ':' after the " - "Y part of ternary expression " - "(X ? Y : Z)"); - } - if(rc) goto end; - - /* Eval the ELSE part... */ - pos = pr->token; - rc = s2_eval_expr_impl(se, pr, fromLhsOp, - buul ? S2_EVAL_SKIP : 0, - &rhs2); - cwal_value_ref(rhs2); - /*MARKER(("Got rhs2 rc=%d: [[[%.*s]]\n", rc, - (int)(s2_ptoken_begin(&pr->capture.end)-s2_ptoken_begin(&pr->capture.begin)), - s2_ptoken_begin(&pr->capture.begin)));*/ - end: - if(!rc){ - assert(s2_engine_peek_value(se)==lhs); - s2_engine_pop_token(se, 0) /* ==> lhs (still in the eval holder) */; - assert(se->ternaryLevel == oldTernaryLevel+1); - } - se->ternaryLevel = oldTernaryLevel; - assert(cwal_value_is_builtin(lhs) - || cwal_value_refcount(lhs)>1/*expecting eval holder ref and ours*/); - cwal_value_ref(*rhs); - cwal_value_unref(lhs); - cwal_value_unref(rhs1); - cwal_value_unref(rhs2); - rc = s2_check_interrupted(se, rc); - if(!rc && (!rhs1 || !rhs2)){ - static char const * emptyMarker = ""; - s2_ptoker_errtoken_set(pr, &pos); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected empty expression " - "in (X ? %s : %s)", - rhs1 ? "Y" : emptyMarker, - rhs2 ? "Z" : emptyMarker) - /* Reminder: rhs1/rhs2 pointers are semantically invalid now - (unref'd above), but their addresses are still usable - here */; - } - if(rc){ - cwal_value_unref(*rhs); - }else{ - if(rv) *rv = *rhs; - cwal_value_unhand(*rhs); - /* s2_dump_val(*rhs,"Ternary result"); */ - s2_engine_push_val(se, *rhs)->srcPos = origin - /* Trivia: that push cannot fail as long as token recycling is - enabled, because the pop (above) just freed one up for us. - - The main eval loop will stuff that *rhs into the eval holder. - */; - } - return rc; -} - -#if 0 -static int s2_next_is_assignment( s2_engine * se, s2_ptoker * pr ){ - s2_ptoken tgt = s2_ptoken_empty; - int rc; - s2_next_token( se, pr, 0, &tgt ); - if(!(rc = s2_ttype_is_assignment(tgt.ttype))){ - rc = s2_ttype_is_assignment_combo(tgt.ttype); - } - if(!rc) pr->nextToken = tgt; - return rc; -} -#endif - -static int s2_next_wants_identifier( s2_engine * se, s2_ptoker * pr ){ - s2_ptoken tgt = s2_ptoken_empty; - int rc; - rc = s2_next_token( se, pr, S2_NEXT_NO_POSTPROCESS, &tgt ); - if(rc) return 0; - s2_ptoker_next_token_set(pr, &tgt) - /* This pr->nextToken adjustment appears to have the - single-biggest effect in triggering the s2_ptoker_next_token() - nextToken optimization: ~75% of the 10k hits in the current - tests. */; - /* MARKER(("Next-wants-identifier? ttype=%s\n", s2_ttype_cstr(tgt.ttype))); */ - switch(tgt.ttype){ - case S2_T_OpIncr: - case S2_T_OpDecr: - return tgt.ttype; - } - if( (rc = s2_ttype_is_assignment(tgt.ttype)) ) return rc; - else if((rc = s2_ttype_is_assignment_combo(tgt.ttype))) return rc; - else rc = s2_ttype_is_identifier_prefix(tgt.ttype); - return rc; -} - -/** - Returns ttype if it refers to a "dereferencing" operator, - else returns 0. - - If this returns non-0: - - - assignment operators translate the stack into a ternary - operation. - - - In skip mode, a call operator after a dereferenced - value is assumed to be valid, and skipped. - - - It is assumed (and may be assert()ed) that the operator sets - s2_dotop_state( se, lhs, lhs, rhs ) when run, so that assignments - can work and 'this' can get bound if the following operation is - a call() on the lhs[rhs] result. - - i.e. the token type must be, or behave just like, the dot operator, - S2_T_OpDot. - -*/ -static int s2_ttype_is_deref( int ttype ){ - switch(ttype){ -#if 0 - case S2_T_OpArrow: - case S2_T_OpDotDot: -#endif -#if 0 - case S2_T_OpHash: -#endif - case S2_T_OpDot: - /*case S2_T_OpColon2:*/ - /*case S2_T_OpDotPrototype:*/ - return ttype; - default: - return 0; - } -} - -/** - If op->id is of a type which should treat its RHS identifier as a - property key, rather than immediately resolving the identifier, - op->id is returned, else 0 is returned. -*/ -static int s2_op_rhs_as_propkey( s2_op const * op ){ - switch(op ? op->id : 0){ - /*case S2_T_OpDotDot:*/ - case S2_T_OpColon2: - case S2_T_OpDot: - return op->id; - default: - return 0; - } -} - -/** - Returns ttype if it is a dot-op-like id or (special case) parens - group, else returns 0. -*/ -static int s2_ttype_is_dotish( int ttype ){ - switch(ttype){ - case S2_T_OpArrow: - case S2_T_OpDotDot: - case S2_T_OpColon2: - case S2_T_OpHash: - case S2_T_OpDot: - /*case S2_T_OpDotPrototype:*/ - case S2_T_BraceGroup: - case S2_T_BraceOpen: - case S2_T_ParenGroup: - case S2_T_ParenOpen: - /* 20170323: ParenGroup/Open added so that: - - ++f().x - - can work. - */ - return ttype; - default: - return 0; - } -} - -/** - Returns the token type id of the next token in pr is a dot-op-like - token, else returns 0. -*/ -static int s2_next_is_dotish( s2_engine * se, s2_ptoker * pr ){ - s2_ptoken tgt = s2_ptoken_empty; - int const rc = s2_next_token( se, pr, S2_NEXT_NO_POSTPROCESS, &tgt ) - ? 0 - : s2_ttype_is_dotish(tgt.ttype); - if(!rc){ - s2_ptoker_next_token_set(pr, &tgt); - } - return rc; -} - -/** - Internal helper for s2_eval_expr_impl(). Requires pr->token to be - a S2_T_ParenGroup and prevOp to be the operator associated with - the previous expression token (may be 0). This function inspects - prevOp and se's stack to determine if this paren group looks - like it should be function call arguments. - - Returns 0 if the parens do not look like a function call, else - non-0. Assigns *rc to 0 on success, non-0 if it detects a syntax - error. If *rc is non-0 upon returning, evaluation must stop and - the error must be propagated. - - If allowNonCallableContainer then this function returns true if the - LHS is a Container type, regardless of whether or not it is - callable. This is intended for use with the 'new' keyword. - - It is only okay to call s2_eval_fcall() if this returns non-0 and - sets *rc to 0, and that call (if made) must be made before doing any - further tokenization or stack processing. - - BUG: - - var f = proc(){return o} using {o:{a:1}}; - f().a += 1 // OK - f().a++ // OK - ++f().a // not OK: Exception: Non-function value type 'string' before a call() operator. - - Where the 'string' is really the identifier 'f'. - - That was fixed by adding S2_T_ParenGroup to - s2_ttype_is_dotish(). Probably not the best fix, but the simplest - and most expedient (and doesn't cause any regressions, curiously - enough). -*/ -static char s2_looks_like_fcall( s2_engine * se, s2_ptoker * pr, - int allowNonCallableContainer, - s2_op const * prevOp, int * rc ){ - assert(S2_T_ParenGroup==pr->token.ttype); - *rc = 0; - if(prevOp) return 0; - else{ - if( se->skipLevel>0 ){ - return s2_engine_peek_value(se) ? 1 : 0; - }else{ - cwal_value * v = s2_engine_peek_value(se); - s2_stoken const * opT = v ? s2_engine_peek_op(se) : 0; - if(!v){ - return 0; - }else if(opT && s2_ttype_is_dotish(opT->ttype)){ - return 1; - /* We'll assume a function is possible and fail - after evaluating the top op if it isn't one. */ - } - else if(se->dotOp.lhs && se->dotOp.key){ - /* So that (obj.prop)(...) can work */ - return 1; - }else if(allowNonCallableContainer - ? !cwal_props_can(v) - : !cwal_value_function_part(se->e, v)){ - /* s2_dump_val(v, "non-function before call()"); */ - *rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Non-function value type '%s' " - "before a call() operator.", - cwal_value_type_name(v)); - return 0; - }else{ - return 1; - } - } - } -} - -/** - Internal helper for s2_eval_expr_impl(). Requires that s2_looks_like_fcall() - has just succeeded, else results are undefined. - - Treats the current S2_T_ParenGroup token as function call arguments and - calls the function, which may be either of these forms on the stack: - - FUNC - or - OBJ.FUNC - - In the former form, the 'this' value for the call() will be the - function itself. - - Remember that OBJ[PROP] gets translated at parse time to OBJ.PROP. - */ -static int s2_eval_fcall( s2_engine * se, s2_ptoker * pr, cwal_value ** rv ){ - int rc = 0; - s2_stoken const * opTok; - s2_op const * op; - s2_ptoken const origin = pr->token; - cwal_value * fv = 0; - cwal_value * xrv = 0; - cwal_value * vSelf = 0; - cwal_scope scope = cwal_scope_empty; - s2_strace_entry strace = s2_strace_entry_empty; - cwal_function * theFunc; - s2_func_state * fst = 0; - char reachedTheCall = 0 - /* will be set to 1 if we reach the actual call() part. This - distinction is needed to handle stray continue/break - properly. */; - assert(S2_T_ParenGroup==pr->token.ttype); - if(!se->skipLevel){ - rc = s2_scope_push_with_flags(se, &scope, S2_SCOPE_F_IS_FUNC_CALL); - if(rc) return rc; - } - - /* Check if this is a FUNC() or OBJ.FUNC() call... */ - opTok = s2_engine_peek_op(se); - op = opTok ? s2_ttype_op( opTok->ttype ) : 0; - - /* - To consider: if dot is not a dot op but se->dotOp.lhs - is set, then some LHS (possibly a (subexpr)) possibly - has given us a 'this' to work with. It's not yet clear - whether we can really know if that's the proper 'this', - though. - - (obj.func)(...) // OK - - obj.prop.func(...) // OK b/c the stack will never have more than 1 dot op; - - obj.prop, func(...) // not OK - - What if we just reset se->dotOp.lhs on each new operator? - - obj.prop['x'] ==> obj.prop.x ==> OK - - obj.prop + 1 (...) // invalid - not-a Function ==> OK - - obj[obj.prop](...) // [...]==>dot op ==> OK - */ - if(op && -#if 1 - s2_ttype_is_dotish(op->id) -#else - s2_ttype_is_deref(op->id) -#endif - ){ - /* If a dot-like operator is pending, run it - to get the function and 'this'. */ - rc = s2_process_top(se); - switch(rc){ - case 0: - break; - case CWAL_RC_EXCEPTION: - goto end; - break; - default: - assert(s2__err(se).code); - rc = s2_ammend_op_err(se, pr, rc); - assert(rc); - goto end; - } - } - if(se->dotOp.lhs){ - /* - To allow (obj.func)(...) to work... - - Reminder to self: we should be able(?) to do this in the - assignment ops as well, so that (x.y)=3 could work. The problem, - it seems, with that, is that (A) we'd end up having to special-case - clearing of the dotop state for (...) groups and (B) we _might_ - end up leaking over dotop state like this: - - var x = (y.z); - - On the assignment, "this" _might_ (not certain) get picked up as - 'y' and se->dotOp.key as 'z'. No, it won't - the stack machine - will only push 2 ops, and 'this' will get cleared at the end of - that expression. Hmmm. - */ - /* MARKER(("Possibly stealing a 'this'\n")); */ - assert(!fv); - assert(!vSelf); - vSelf = se->dotOp.self; - /* s2_dump_val(vSelf, "se->dotOpThis"); */ - if(vSelf) cwal_value_ref(vSelf); - fv = s2_engine_pop_value(se) /* presumably a dot-op result */; - assert(fv); - cwal_value_ref( fv ); - }else{ - fv = s2_engine_pop_value(se) /* hopefully a function */; - cwal_value_ref( fv ); - } - s2_dotop_state(se, 0, 0, 0); - - /* In skip mode, we're done. We had to get the values off the stack, - though, so we couldn't do this first. */ - if(se->skipLevel>0){ - xrv = cwal_value_undefined(); - goto end; - } - - rc = s2_strace_push_pos(se, pr, &origin, &strace ) - /* we have to do this fairly late so that the - the (still pending) call doesn't confuse the - the trace. */ - ; - if(rc) goto end; - - if(!fv || !(theFunc = cwal_value_function_part(se->e, fv))) { - rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Non-function (%s) LHS before " - "a call() operation.", - fv ? cwal_value_type_name(fv) - : ""); - goto end; - } - /* MARKER(("Function call args: %.*s\n", (int)s2_ptoken_len(&pr->token), - s2_ptoken_begin(&pr->token))); */ - /* s2_dump_val(vSelf,"call() vSelf"); */ - /* s2_dump_val(fv,"call() func"); */ - - fst = s2_func_state_for_func(theFunc); - { - /* Process the args and call() the func... */ - s2_ptoker sub = s2_ptoker_empty; - cwal_value * arV = 0; - cwal_array * oldArgV; - s2_func_state const * oldScriptFunc = se->currentScriptFunc; - cwal_array * ar = cwal_new_array(se->e); - if(!ar){ - rc = CWAL_RC_OOM; - goto end; - } - arV = cwal_array_value(ar); - cwal_value_ref(arV); - cwal_value_make_vacuum_proof(arV, 1); - rc = s2_ptoker_sub_from_toker(pr, &sub); - if(rc){ - cwal_value_unref(arV); - arV = 0; - ar = 0; - goto end; - } - assert(pr==sub.parent); - /* sub.name = "call(args)"; */ - rc = s2_eval_to_array( se, &sub, ar, 1 ); - s2_ptoker_finalize(&sub); - if(rc){ - cwal_value_unref(arV); - arV = 0; - ar = 0; - goto end; - } - oldArgV = se->callArgV; - se->callArgV = 0; - se->currentScriptFunc = 0 /* if this is a script-side function call, - s2_callback_f(), which is about to trigger, - will set this to the corresponding function. */; - s2_ptoker_errtoken_set(pr, &origin) /* just in case, location for error reporting */; - if(0){ - MARKER(("Function call()...\n")); - s2_dump_val(vSelf ? vSelf : fv, "vSelf ? vSelf : fv"); - s2_dump_val(cwal_array_value(ar), "argv"); - } - - reachedTheCall = 1; - /* Reminder to self: - cwal makes fv sweep/vacuum-safe for the life of the call, - if it was not already. - */ - if(fst - && (fst->flags & S2_FUNCSTATE_F_EMPTY_BODY) - && (fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS) - ){ - /* No function body and no possibility of default parameter - values triggering side effects, so we don't need to call() it - (which involves setting up imported symbols, "argv", and - "this"). We need to ensure that se->callArgV is 0, though, - as that would normally be taken care of via the pre- and/or - post-call() hooks. - - It's hypothetically possible that default parameter values - want to trigger side-effects via imported symbols (including - "this" and the function's call()-local name - (fst->vName)). For that to work, we need to go through the - whole call() process if the body is empty but the params list - is not. It's hard to envision a real use case for that, but - the language allows for such constructs, so let's behave - properly if someone actually constructs such a weird thing. - */ - assert(!se->callArgV); - }else{ - se->callArgV = ar /* needed by internals we're about to pass - through via the call() */; - rc = cwal_function_call_array( &scope, theFunc, - vSelf ? vSelf : fv, &xrv, ar ) - /* Still wavering on this point: when called without a propery - access (vSelf===0), should the function's "this" be the - undefined value or itself? i don't want "this" propagating in - from an older scope. - - One argument for passing fv instead of undefined: - - var obj = { prototype: proc(x){ this.x = argv.x } }; - - obj(1); - - 'this' is undefined in that call. - - Another argument: it seems that i already wrote a chapter on - how to use/abuse it in the docs. - */; - } - se->currentScriptFunc = oldScriptFunc; - /* assert(!se->callArgV && "Gets unset via the post-call() hook"); - - interesting problem: on certain types of errors, first made - possible with the introduction of interceptors, the pre() - hook might not be called, and thus the post-hook won't. */ - if(se->callArgV){ - assert(rc && "Only expecting this in certain error cases."); - se->callArgV = 0; - /* s2_dotop_state( se, 0, 0, 0 ); necessary!!! ? */ - } - se->callArgV = oldArgV; - /* assert(0==oldArgV); */ - cwal_value_make_vacuum_proof(arV, 0); - if(!rc && xrv){ - /* we have to ref xrv before destroying arV for the case that xrv==arV - or arV contains (perhaps indirectly) xrv. */ - cwal_value_ref(xrv); - } - cwal_value_unref(arV); - } - end: - rc = s2_check_interrupted(se, rc); - switch(rc){ - case 0: - break; - case CWAL_RC_EXCEPTION: - rc = s2_exception_add_script_props(se, pr) - /* Needed to decorate calls to native functions. */; - if(rc){ - /* lower-level error, e.g. CWAL_RC_OOM, while adding script - location state. */ - break; - } - rc = CWAL_RC_EXCEPTION; - CWAL_SWITCH_FALL_THROUGH; - /* case CWAL_SCR_SYNTAX: */ - case CWAL_RC_OOM: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_INTERRUPTED: - case CWAL_RC_ASSERT: - /* do we want assertion errors to translate to exceptions - if they go up the call stack? Much later: no.*/ - break; - case CWAL_RC_BREAK: - case CWAL_RC_CONTINUE: - /* - If a break/continue are triggered while handling the - arguments, that's okay: let them propagate so they can be - caught by upstream code. If they passed through the call() - itself, that's not okay: stop them from propagating further. - */ - if(!reachedTheCall) break; - CWAL_SWITCH_FALL_THROUGH; - default: - if(s2__err(se).code){ - /* Likely a syntax-related error pending */ - /*MARKER(("[rc=%s] se->err.msg=%s\n", cwal_rc_cstr(se->err.code), (char *)se->err.msg.mem));*/ - rc = s2_throw_err(se, 0, 0, 0, 0); - }else{ - if(cwal_exception_get(se->e)){ - /* s2_exception_add_script_props(se, pr); why is this not needed? */ - rc = CWAL_RC_EXCEPTION /* keep propagating it and - assume/hope that rc is encoded in - exception.code. */; - }else{ - rc = s2_throw_ptoker(se, pr, rc, - "Function call returned non-exception " - "error code #%d (%s).", - rc, cwal_rc_cstr(rc)); - } - } - break; - } - if(strace.pr){ - s2_strace_pop(se); - } - if(!rc && xrv){ - assert(cwal_value_refcount(xrv)>0 /* we ref'd it above */ - || cwal_value_is_builtin(xrv)); - } - if(vSelf) cwal_value_unref(vSelf); - if(fv) cwal_value_unref(fv); - if(!rv){ - cwal_value_unref(xrv); - xrv = 0; - } - if(rc){ - cwal_value_unref(xrv); - xrv = 0; - }else{ - cwal_value_unhand(xrv); - if(rv) *rv = xrv ? xrv : cwal_value_undefined(); - else{ - assert(!xrv && "was zeroed out above."); - } - } - if(scope.parent){ - assert(&scope == se->scopes.current->cwalScope); - cwal_scope_pop2(se->e, rc ? 0 : xrv); - } - return rc; -} - -static int s2_eval_is_eox(s2_engine const * se, - s2_ptoker const * pr){ - int rc; - assert(pr); - rc = s2_ttype_is_eox(pr->token.ttype); -#if 1 - if(!rc && se->ternaryLevel>0 - && S2_T_Colon==pr->_nextToken.ttype ){ - /* - huge kludge for: - - 0 ? foreach(@[1]=>k,v) eval x : 1 - */ - rc = S2_T_Colon; - } -#endif - return rc; -} - -/** - If true, s2_eval_expr_impl() internally uses a local - array to keep references active and vacuum-proofed. - - HOLY COW... when recycling is disabled, this causes alloc counts - and total (not peak) memory to skyrocket (nearly 60% increase in - the 20160131 unit tests). With recycling on alloc counts go up by - roughly 3% but peak memory goes up anywhere from 1k to 7k (as the - recycling bins accumulate) in those same unit tests. - - After changing this, MAKE SURE to run all the valgrind tests with - various combinations of recycling and string interning!!! - - Interestingly... if we'd done the right thing at the start of s2's - development and told the s2_stoken_stack to hold/release refs, we'd - probably have no problems with refs wrt string interning and how - s2_engine::dotOpXXX are managed. At the time, i had too much faith - in temp values because i didn't fully understand the implications - of some of the interactions :/. There, i've admitted it. IIRC, we - also didn't yet have cwal_value_unhand(), which is what makes some - of the current ref handling possible/sane. - - 20171112 re-enabling EVAL_USE_HOLDER to work around a serious bug: - - ./s2sh --S --a -v --R crash.s2 - - with this crash.s2: - - if((var d=s2.getenv('HOME')) && (d = s2.fs.realpath(d+'/fossil'))) 0; - - It's crashing in the && operator, and it seems to be a lifetime - problem related to the 'd' value (possibly getting hosed via the - 2nd assignment?). Running s2sh with recycling ON (-R instead of - --R) hides the problem but potentially causes Weird Broken Stuff to - happen later (as is being witnessed in the CGI plugin, which was - where this was initially triggered). This is the first serious - lifetime-related bug we've seen the core in a couple years :`(. A - decent solution, aside from the eval holder, is as yet unknown :`(. - - Other formulations and permutations: - - (Forewarning: these are often dependent on the exact recycling - options used and the internal state of the recycling system(s)!) - - var d; if((d = s2.getenv('HOME')) && (d = s2.fs.realpath(d+'/fossil'))) 0; - // ^^^ crashes - - var d = s2.getenv('HOME'); if((d) && (d = s2.fs.realpath(d+'/fossil'))) 0; - // ^^^ works - - (var d = 'a b c d e f g') && (d = d + 'h i j k') // crashes - (var a = 30) && (a = a + 10) // works - (var a = '30') && (a = a + '10') // crashes - (var d = 'a b c d e f g') && (d = 'h i j k') // crashes - (var a = 'x') && (a = 'x') // crashes - (var a = 'x') + (a = 'y') // crashes - (var a = 'x'), (a = 'y'), a // works - (var a = 'x'), (a = a + 'y'), a // crashes - (var a = 30), (a = a + 20), a // works - - So... the common element appears to be assigning to the same string - value on the LHS and RHS of a binary operator. - - With the "eval holder" enabled, all of the above work as expected - because the holder keeps refs to all of the values created during - those expressions, unref'ing them at the very end of the expression - (taking care to not kill the expression's final result value, if - it's needed). - - The problem can (at times) be triggered both with and without - string interning and/or the chunk recycler, which rules out the - easy solution of getting rid of string interning. - - As of 20171112, don't disable this until/unless a resolution - for the above problem is implemented. - - 2020-02-20: the above story disregards the fact that even if the - stack machine held refs to everything, we'd still need the eval - holder to ensure vacuum-safety of in-progress expressions. It is a - seemingly unavoidable feature of the engine. -*/ -#define EVAL_USE_HOLDER 1 - -/** - Internal helper for s2_eval_expr_impl(), which performs - a sub-eval on keywords. - - Returns 0 on success. - - *nextTokenFlags will be set to 0 or to S2_NEXT_NO_SKIP_EOL, which - must be respected in the next iteration of the main eval loop, - treating an immediately-subsequent EOL as an EOX. - - *tVal will be set to the result of the keyword. - - st will be advanced to the last of the keyword's tokens. - - fromPt must be the currently-eval'ing token, and this function - may change its ttype. - - fromLhsOp needs to be the operation taking place on the LHS, if - any. If not NULL, then EOL is _never_ treated like an EOX. This is - almost always null, but some few callers will want to pass - s2_ttype_op(S2_T_Comma) so that their (sub)expression stops eval'ing - at the first comma. -*/ -static int s2_eval_keyword_call( s2_keyword const * kword, - s2_ptoken * fromPt, - s2_op const * fromLhsOp, - s2_engine * se, s2_ptoker * st, - cwal_value ** tVal, - int * nextTokenFlags ){ - int rc; - assert(kword->call); - fromPt->ttype = st->token.ttype = kword->id; - rc = kword->call(kword, se, st, tVal); - s2_dotop_state( se, 0, 0, 0 ); - rc = s2_check_interrupted(se, rc); - *nextTokenFlags = 0; - if(rc) return rc; - else if(!tVal){ - s2_ptoker_errtoken_set(st, fromPt); - return s2_err_ptoker(se, st, CWAL_RC_ASSERT, - "Keyword '%s' eval'd to , " - "which is currently verboten", - kword->word); - /* Reminder to self: maybe we could abuse a NULL keyword - result value for keywords which should expand to nothing, - e.g. __sweep and __BREAKPOINT. - */ - } - else if((S2_T_SquigglyBlock==st->token.ttype - || S2_T_Heredoc==st->token.ttype - /* ^^ heredocs handling here is historic */) - && !fromLhsOp && !se->st.vals.size - /* ^^ keyword is the only thing on the stack */){ - /* If the keyword ends in a {script} and its the only - expression (so far), then possibly treat a trailing - newline as EOX. This is primarily so that - scope/if/while/for/etc do not require a semicolon, - but it might bleed into some other potentially - unexpected cases. - */ - if(kword->allowEOLAsEOXWhenLHS){ - *nextTokenFlags = S2_NEXT_NO_SKIP_EOL; - }else{ - /* In order to avoid breaking certain cases, we have - to check the next token. Heuristic: if the next - token is an infix or postfix op, assume that it's - to be applied to this result and treat EOL as - normal, otherwise treat EOL as an implicit EOX. - - e.g.: - - print(catch{...} - .message); - - If it fails tokenizing, ignore it - we'll catch - it again in a moment. - */ - s2_op const * opCheck = 0; - s2_ptoken check = s2_ptoken_empty; -#if 0 - /* why doesn't this work the same? */ - s2_ptoker stMod = *st; - do{ - rc = s2_ptoker_next_token_skip_junk(&stMod); - }while(!rc && s2_ttype_is_eol(stMod.token.ttype)); - if(!rc){ - check = stMod.token; - } -#else - rc = s2_next_token( se, st, 0, &check ); -#endif - if(!rc - && S2_T_ParenGroup!=check.ttype - /* avoid that: proc (){} - (1,2,3) - unduly fails! This might come around and - bite us, though. */ - && (!(opCheck = s2_ttype_op(check.ttype)) - || (opCheck->placement<0 /* prefix op */)) - ){ - *nextTokenFlags = S2_NEXT_NO_SKIP_EOL; - /* MARKER(("Squiggly workaround for %s\n", kword->word)); */ - s2_ptoker_next_token_set(st, &check); - } - } - } - assert(!rc); - return rc; -} - -/** - Internal impl of s2_eval_expr(). - - If fromLhsOp is not 0, this call is assumed to be the RHS of that - operator. Upon encountering an operator of that precedence or less, - that operator is put back into the tokenizer and evaluation - finishes. This is used in implementing short-circuit logic. - - TODO: break this beast down into smaller chunks! It's over 700 lines - long! -*/ -int s2_eval_expr_impl( s2_engine * se, - s2_ptoker * st, - s2_op const * fromLhsOp, - int evalFlags, - cwal_value ** rv){ - s2_ptoken pt = s2_ptoken_empty; - s2_ptoken prevTok = s2_ptoken_empty; - s2_ptoken const pOrigin = st->token; - s2_ptoken const pbOrigin = *s2_ptoker_putback_get(st); - int tlen = 0; - int const oldSkipLevel = se->skipLevel; - s2_op const * op = 0; - s2_op const * prevOp = 0; - s2_estack priorStack = s2_estack_empty; - s2_ptoken captureBegin = s2_ptoken_empty - /* starting point of capture (first non-junk token) */, - captureEnd = s2_ptoken_empty - /* one-after-the-end point of capture. It turns out that using a - range of [captureBegin,captureEnd) simplifies handling notably - over [captureBegin,captureEnd]. This is typically an - expression's EOX, but will be the same as captureBegin for an - empty expression (including one comprised solely of an EOX - token, in which case both captureBegin and captureEnd will - point to that EOX token). */; - char const consumeIt = (evalFlags & S2_EVAL_NO_CONSUME) ? 0 : 1; - char const evalIt = (evalFlags & S2_EVAL_SKIP) ? 0 : 1; - s2_keyword const * kword = 0 /* keyword for the token, if any */; - s2_ptoker const * oldScript = se->currentScript; - char const ownStack = -#if 1 - (S2_EVAL_KEEP_STACK & evalFlags) ? 0 : 1 -#else - /* - Bug: currently leads to extra stack items when short-circuit - logic and sub-parses activate. Caused by how we unwind the - stack (in full) at the end. - */ - (fromLhsOp ? 0 : 1) -#endif - ; - int totalValCount = 0 /* just for some error checks */; - int nextTokenFlags = /* The s2_next_token() flags for the very next token */ - (evalFlags & S2_EVAL_NO_SKIP_FIRST_EOL/* fromLhsOp && S2_T_RHSEvalNoEOL!=fromLhsOp->id */) - ? S2_NEXT_NO_SKIP_EOL - : 0; - cwal_scope scope = cwal_scope_empty /* cwal stack, if (flags&S2_EVAL_PUSH_SCOPE) */; -#if EVAL_USE_HOLDER - cwal_array * holder = 0 /* holds pending expression values to keep - them referenced and vacuum-safe. */; - cwal_size_t holderLen = 0 /* length of - se->scopes.current->evalHolder at the - time this function is called */; -#endif -#ifdef DEBUG - /* Just for sanity checking */ - int const oldValCount = se->st.vals.size; - int const oldOpCount = se->st.ops.size; -#endif - int rc = s2_check_interrupted(se, 0); - - if( rc ) return rc; - else if(!fromLhsOp - /* Explicitly NOT honoring !(S2_EVAL_RETAIN_DOTOP_STATE & - evalFlags) at the start of eval, only at the end. */ - ) s2_dotop_state(se, 0, 0, 0); - if(S2_EVAL_PUSH_SCOPE & evalFlags){ - MARKER(("Oh, this scope-pushing bit in eval_expr_impl IS used.\n")); - assert(!"This isn't used, is it?"); - rc = cwal_scope_push2(se->e, &scope); - if(rc) return rc; - assert(scope.parent); - assert(0==se->scopes.current->sguard.sweep); - assert(0==se->scopes.current->sguard.vacuum); - /* se->scopes.current->sguard.sweep = 0; */ - /* REMINDER: we cannot blindly disable se->sguard->vacuum because it - can nuke non-script-visible values in use by the client. Been there, - done that. - */ - } - - if(++se->metrics.subexpDepth > se->metrics.peakSubexpDepth){ - se->metrics.peakSubexpDepth = se->metrics.subexpDepth; - } - - s2_ptoker_errtoken_set(st, 0); - - /* if(rv) *rv = 0; */ - if((S2_EVAL_PRE_SWEEP & evalFlags) - && !fromLhsOp - && !se->st.vals.size - && !se->st.ops.size - ){ - /* MARKER(("SWEEPING from eval_expr_impl\n")); */ - /* We cannot know if a vacuum is safe from here :/, - and it certainly isn't if s2_eval_ptoker() is waiting - on a pending EOX. */ - assert(!"unused/untested"); - ++se->scopes.current->sguard.vacuum; - s2_engine_sweep(se); - --se->scopes.current->sguard.vacuum; - } - if(!scope.parent){ - /* reminder to self: needed only when no scope has been pushed by - this routine. If this routine pushed a scope (that feature is - currently unused/untested) then there cannot be pending LHS - expressions which could be affected by a sweep in the - newly-pushed scope. - */ - ++se->scopes.current->sguard.sweep; - } - - se->currentScript = st; - - s2_engine_err_reset(se); - if(!evalIt) ++se->skipLevel; - if(ownStack){ - s2_engine_stack_swap(se, &priorStack); - } - - -#if EVAL_USE_HOLDER -# define eval_hold_this_value(ARRAY, VALUE) (cwal_value_is_builtin(VALUE) \ - ? 0 \ - : cwal_array_append((ARRAY), (VALUE))) -# define eval_hold(V) if( holder && (V) \ - && (rc = eval_hold_this_value(holder, (V)))) goto end - /* - Create se->scopes.current->evalHolder to hold refs to our - being-eval'd values. As an optimization, if we're in skip mode, - don't bother... and let's hope that this function is never called - in such a way that skip mode is disabled by a downstream sub-eval - (because this optimization will hose us if that happens). That - case shouldn't be possible, though. - */ - if(!se->skipLevel && !(holder = se->scopes.current->evalHolder)){ - holder = cwal_new_array(se->e); - if(!holder){ - rc = CWAL_RC_OOM; - goto end; - }else{ - cwal_value * hv = cwal_array_value(holder); - cwal_value_ref(hv); -#if 0 - /* array_reserve() costs us inordinate amounts of memory - when recycling is disabled. Tried values 0, 10, 15, 20, - and 0 is the clear winner.*/ - rc = cwal_array_reserve(holder, 0/*guess!*/); - if(rc){ - cwal_value_unref(hv); - holder = 0; - goto end; - } -#endif - se->scopes.current->evalHolder = holder; - cwal_value_make_vacuum_proof(hv,1); - cwal_value_rescope(se->scopes.current->cwalScope, hv) - /* in case a cwal-level API has pushed a scope without us - knowing */; - } - }else if(holder){ - holderLen = cwal_array_length_get(holder) - /* Get the older (pre-eval) length so that we can trim holder - back to that length when we're done, releasing any refs this - invocation of eval() is holding. */; - } -#else -# define eval_hold(V) -#endif -/* ^^^^ EVAL_USE_HOLDER */ - - pt = st->token; - - /* - Adapted from: - - https://en.wikipedia.org/wiki/Stack_(data_structure)#Expression_evaluation_and_syntax_parsing - - https://en.wikipedia.org/wiki/Shunting-yard_algorithm - - https://en.wikipedia.org/wiki/Operator-precedence_parser - - For later reference (but not used here): Pratt Parsing: - https://en.wikipedia.org/wiki/Pratt_parser - https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ - */ - for( ; !rc && - ( - (prevTok.ttype - && !op - && s2_ttype_is_eox(st->token.ttype) - /* - ^^^^ translation: if we had a previous token which was - not an operator and we're currently at an EOX, then stop - processing without an error (though we may trigger one in - an up-coming step). - - Sub-tokenizations can end on an EOX and still produce a - value. i would love to be able to do without this check - here (and it might be a side-effect of older EOL rules?). - - This case appears once keywords and short-circuiting - enter this mix (i.e. sub-parses/sub-evals). - - Note that we allow EOX's through in some cases just so - that we can do some more error checking/reporting. - */) - ? 0 - : (0 == (rc = s2_next_token(se, st, nextTokenFlags, 0))) - ) - ; - prevOp = op, prevTok = pt - ){ - cwal_value * tVal = 0 /* Value for the token, if any */; - int doBreak = 0 /* set to non-0 to force a non-error multi-level - break in certain places below. */; - if((rc=s2_check_interrupted(se, rc))) break; - if(!s2_ptoken_begin(&captureBegin)){ - captureBegin = st->token; - } - - if(s2_ttype_is_eox(st->token.ttype)){ - if(/*!fromLhsOp && <--- 20171205: why did we do that? - having that triggers an assert() via: new X( y. ) - (note the stray '.'). */ - prevOp - && ((prevOp->placement<0 && prevOp->arity>0)/*prefix op*/ - ||(prevOp->placement==0 && prevOp->arity>1)/*infix op*/) - ){ - /* infix/prefix operator preceeding EOX */ - /* s2_ptoker_errtoken_set(st, s2_ptoken_begin(&prevTok) ? &prevTok : &st->token); */ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Invalid operator '%s' before " - "end of expression", - prevOp->sym); - } - else if(s2_ttype_is_eof(pt.ttype) - /* yes, checking previous token, not current one */){ - if(prevOp - && prevOp->arity>0 - && prevOp->placement<=0 - ){ - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Non-nullary operator '%s' " - "before EOF", prevOp->sym); - }else if(fromLhsOp){ - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Expecting RHS but got EOF"); - } - } - else if(S2_T_EOX == st->token.ttype - && (evalFlags & S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED)){ - /* Bugfix: catch these as syntax errors: - - [1,2,3;] - [1,2,3;,4] - {a:b, c:d ;} - - Without this check, those semicolons are silently ignored - because they're the tail end of the values' expressions - (and thus officially end the expressions). That's arguably - not a bug, given the eval engine's nature, but we'll go - ahead and flag it as an error just to be pedantic (even - though it means adding more code just to catch this).. - */ - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Unexpected semicolon."); - } - /* - Would like to do this but is currently endlessly looping - in s2_eval_ptoker(): - - s2_ptoker_putback(st); - */ - break; - }else if(kword - && kword->allowEOLAsEOXWhenLHS - && (S2_NEXT_NO_SKIP_EOL & nextTokenFlags) - && s2_ttype_is_eol(st->token.ttype) - ){ - /* Special case for keywords with a {script} operand. - Treat this EOL as EOX */ - break; - } - nextTokenFlags = 0; - kword = 0; - pt = st->token; - /*captureEnd = pt; Reminder to self: setting captureEnd here does - not work with break/continue/return handling (and similar): it - captures the keyword but not the optional expression after it - (which arrives back in this loop via error handling). For most - cases we have to wait until after this loop to set - captureEnd. */ - tlen = s2_ptoken_len(&pt); - assert(tlen>0); - if(se->flags.traceTokenStack){ - MARKER(("Token: fromLhsOp=%s type=%s [%.*s]\n", - fromLhsOp ? fromLhsOp->sym : "none", - s2_ttype_cstr(pt.ttype), tlen, s2_ptoken_begin(&pt))); - } - switch(pt.ttype){ /* Some basic sanity checks */ - case S2_T_SquigglyOpen: - case S2_T_HeredocStart: - case S2_T_BraceOpen: - case S2_T_ParenOpen: - assert(!"Cannot happen - gets handled by s2_next_token()"); - /* But in case it does, fall through... */ - CWAL_SWITCH_FALL_THROUGH; - case S2_T_SquigglyClose: - case S2_T_BraceClose: - case S2_T_ParenClose: - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Mismatched '%.*s' token", - tlen, s2_ptoken_begin(&pt)); - break; - case S2_T_At: - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "'@' is not allowed here."); - break; - case S2_T_Question:{ - cwal_value * lhs; - if(fromLhsOp && (S2_T_OpAnd==fromLhsOp->id - || S2_T_OpOr==fromLhsOp->id - || S2_T_OpOr3==fromLhsOp->id - || S2_T_OpElvis==fromLhsOp->id) - ){ - /* - Workaround to avoid short-circuiting the RHS of a ternary - when its LHS is a short-circuiting logical op which is - skipping over its own RHS: - - 0 && 1 ? a : b; - 1 || 0 ? a : b; - - We put back this token and come back to it after the - logical op resolves. - - This is a bit of a hack, admittedly, but i haven't got a - better approach for the time being. - */ - captureEnd = st->token; - s2_ptoker_putback(st); - doBreak = S2_T_Question; - break; - } - lhs = prevOp && prevOp->placement<=0 ? 0 : s2_engine_peek_value(se); - if(!lhs){ - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Unexpected LHS (%s%s) for %s operator.", - prevOp ? prevOp->sym - : (lhs ? cwal_value_type_name(lhs) - : ""), - prevOp ? " operation" : (lhs ? " value" : ""), - s2_ttype_op( S2_T_Question )->sym); - break; - } - lhs = 0; - rc = s2_eval_ternary(se, st, &lhs); - if(rc) break; - else{ - op = 0 - /* 20180507: avoid confusion in the next iteration. - - var a, b; - a++ ? 1 : 2; b - - ==> Unexpected token after postfix operator: (OpIncrPost) ==> (Identifier) - - That triggers after the ternary expr completes, but - adding a second semicolon after the ternary expr fixes - it. - - Other postfix ops can also trigger it. - */; - assert(lhs); - eval_hold(lhs); - continue; - } - } - case S2_T_Identifier:{ - /* If an Identifier is the RHS of a dot operator, change its - type to PropertyKey so that identifier expansion is not - done for this property access. The raw token bytes - will be converted to a string value later on in this - routine. Implications: - - obj.prop ==> Identifier DOT LiteralString - - obj.'prop' ==> effectively the same thing - - obj.(prop) ==> Identifier DOT ( Identifier ) - - Seems reasonable to me. - */ -#if 0 - s2_stoken const * topOpTok = s2_engine_peek_op(se); - if(topOpTok && (S2_T_OpDot==topOpTok->ttype)){ - pt.ttype = S2_T_PropertyKey; - } -#else - if(prevOp && s2_op_rhs_as_propkey(prevOp)){ - pt.ttype = S2_T_PropertyKey; - } -#endif - break; - } - case S2_T_OpIncr: - case S2_T_OpDecr: - /* Convert -- and ++ to either prefix or postfix form, depending - on the state of the stack... */ - if(prevOp || !se->st.vals.size){/* xxxx misdiagnosis as post-op on mis-continued lines */ - pt.ttype = (S2_T_OpIncr==pt.ttype) - ? S2_T_OpIncrPre - : S2_T_OpDecrPre; - }else{ - pt.ttype = (S2_T_OpIncr==pt.ttype) - ? S2_T_OpIncrPost - : S2_T_OpDecrPost; - } -#if 0 - op = s2_ttype_op(pt.ttype); - assert(op); - MARKER(("Changing token '%s' to %s form.\n", op->sym, - op->placement>0 ? "postfix" : "prefix")); -#endif - break; - default: - break; - } - rc = s2_check_interrupted(se,rc); - if(rc || doBreak) break; - - op = s2_ttype_op( pt.ttype ); -#if 0 - if(op && !fromLhsOp){ - s2_dotop_state( se, 0, 0, 0 ) - /* If these get stale, Very Bad Things happen in the cwal core - (assertions, if we're lucky). */; - MARKER(("op=%s, dotop state?\n",op->sym)); - /* se->dotOpId = 0 */ /* keep Other Bad Things from happening */; - /* Reminder: this means that assignment ops cannot tell - which dot-like op triggered them. */ - } -#else - /* - We need a better heuristic, as the above makes this fail: - - x.y++; // fine - x.y++ >= 2; // "Invalid LHS..." from the incrdecr op because dotop state is cleared. - */ - if(op && !fromLhsOp){ - if(!prevOp){ - /* MARKER(("op=%s, prevOp=%s, resetting dotop state\n",op->sym, prevOp ? prevOp->sym : "")); */ - s2_dotop_state( se, 0, 0, 0 ); - }else{ - /* MARKER(("op=%s, prevOp=%s, keeping dotop state\n",op->sym, prevOp->sym)); */ - } - } -#endif - -#if 0 - if(fromLhsOp){ - MARKER(("fromLhsOp=%s, pt.type=%s\n", s2_ttype_cstr(fromLhsOp->id), s2_ttype_cstr(pt.ttype))); - } -#endif - - if(S2_T_Colon==pt.ttype){ - if(se->ternaryLevel>0){ - /* s2_eval_ternary() will deal with it */ - captureEnd = st->token; - s2_ptoker_putback(st) - /* This putback causes certain grief for foreach(). */; - s2_ptoker_next_token_set(st, &pt) /* part of a kludge in s2_eval_is_eox(). - Also incidentally useful here. */; - }else{ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unexpected ':' token (no %s op in progress).", - s2_ttype_op(S2_T_Question)->sym); - } - break; - }else if(fromLhsOp - /* This eval_expr() call is fetching the RHS of a pending - expression. */ - && op - && (op->prec < fromLhsOp->prec - || (op->prec==fromLhsOp->prec - /* So (a=b=c=d) can work. */ - && op->assoc<0))){ - /* This invocation was catching an RHS, but we - can stop now. */ - /*MARKER(("This '%s' is where a sub(ish)-eval for RHS " - "'%s' skipping would break off.\n", - op->sym, fromLhsOp->sym));*/ - captureEnd = st->token; - s2_ptoker_putback(st); - break; - } - - /* Fiddle with consecutive ops for some cases... */ - if(op && (prevOp || !s2_engine_peek_token(se))){ - /* Leading operator or two consecutive operators. */ - switch(op->id){ - case S2_T_OpPlus: - case S2_T_OpMinus: - /* See if op can be made into a unary op. */ - if( - !prevOp || s2_ttype_may_precede_unary(prevOp->id) - ){ - /* MARKER(("Changing token '%s' to unary form.\n", op->sym)); */ - pt.ttype = (S2_T_OpPlus==op->id) - ? S2_T_OpPlusUnary - : S2_T_OpMinusUnary; - op = s2_ttype_op(pt.ttype); - } - break; - case S2_T_OpHash: - if(prevOp && S2_T_OpDot==prevOp->id){ - /* Convert the dot op in X.# to S2_T_OpDotLength. */ - s2_stoken * topTok = s2_engine_peek_op(se); - assert(S2_T_OpDot==topTok->ttype); - assert(topTok->ttype == prevOp->id); - topTok->ttype = S2_T_OpDotLength; - op = s2_ttype_op(topTok->ttype) - /* so that prevOp gets set for the next iteration */; - assert(op); - continue; - } - break; - default: - break; - } - if(!rc - && op - && prevOp && prevOp->arity>0 && prevOp->placement<=0 - && op->placement>=0 /* && op->assoc<=0 */ && op->arity!=0 - /* e.g. OpPlus ==> OpMultiply */){ - /* consecutive non-nullary operators */ - if(op->assoc<=0){ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unexpected consecutive operators: " - "'%s' ==> '%s'", - prevOp->sym, op->sym); - } - } - }/*op vs prevOp check*/ - - if(rc) break; - else if(!op){ - /* A non-operator ("value") token */ - kword = s2_ptoken_keyword2(se, &pt); - if(prevOp - && (!kword || (kword && kword->call/*non-operator-like keyword*/)) - && prevOp->placement>0/*postfix*/ && prevOp->arity>0/*non-nullary*/){ - /* e.g. a++a */ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unexpected token after postfix operator: " - "(%s) ==> (%s)", - s2_ttype_cstr(prevOp->id), - s2_ttype_cstr(pt.ttype)); - break; - } - if(!prevOp - && /*prevTok.ttype*/ s2_engine_peek_value(se) - && (S2_T_BraceGroup!=pt.ttype) /* for obj[prop] access */ - && (S2_T_ParenGroup!=pt.ttype) /* so func() can work */ - && (!kword/* || kword->call*/ /* for 'inherits' and potential - future operator-like keywords */) - ){ -#if 0 - /* who is setting se->opErrPos to this (correct) location? */ - if(se->opErrPos){ - MARKER(("opErrPos=[%.30s...]\n", se->opErrPos)); - st->errPos = se->opErrPos; - }else{ - se->opErrPos = st->errPos = s2_ptoken_begin(&pt); - } -#else - /* se->opErrPos = 0; resetting this here causes 'catch' to misreport - line numbers in some cases! */ - s2_ptoker_errtoken_set(st, &pt); - se->opErrPos = s2_ptoken_begin(&pt) - /* se->opErrPos trumps st->errPos in s2_err_ptoker() - and is currently (for unknown reasons) being relied - upon by a containing 'catch' for proper behaviour. - That's a bug. */; -#endif - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unexpected consecutive non-operators: " - "#%d (%s) ==> #%d (%s)" /* " [%.*s]" */, - prevTok.ttype, - s2_ttype_cstr(prevTok.ttype), - pt.ttype, s2_ttype_cstr(pt.ttype) - /* tlen, s2_ptoken_begin(&pt) */ - ); - break; - } - } - - /* Some (more) checks and pre-processing... */ - switch(pt.ttype){ - case S2_T_OpOr: - case S2_T_OpOr3: - case S2_T_OpElvis: - case S2_T_OpAnd:{ - /* - For short-circuit logic we need both this loop and the - stack to be in skip mode, but we also have to know when - to back out of skip mode. - - Evaluate RHS expression in skip mode when: - - - op==OpOr or OpOr3 and topVal is truthy, - - op==OpElvis and topVal !== undefined - - op==OpAnd and topVal is falsy - */ - cwal_value * lhs = 0; - char buul = 0; - char shortIt = 0; - int const theOpId = pt.ttype; - rc = s2_eval_lhs_ops(se, st, op); - if( rc ) break; - lhs = s2_engine_peek_value(se); - if(!lhs) break /* will error out below */; - else if(se->skipLevel>0) break; - if(S2_T_OpElvis==pt.ttype){ - shortIt = (cwal_value_undefined() == lhs) ? 0 : 1; - }else{ - buul = cwal_value_get_bool(lhs); - if(!buul){ - if(pt.ttype==S2_T_OpAnd){ - shortIt = 1; - } - }else{ - if(pt.ttype!=S2_T_OpAnd){ - assert(S2_T_OpOr==pt.ttype - || S2_T_OpOr3==pt.ttype); - shortIt = 1; - } - } - } - if(shortIt){ - /* - short-circuit the RHS by calling back into this function - and passing it the current op as a cut-off precedence. - */ - cwal_value * rhs = 0; - /* int const oldOpSize = se->st.ops.size; */ - /* MARKER(("op %s should short circuit RHS here.\n", op->sym)); */ - rc = s2_eval_expr_impl(se, st, op, - S2_EVAL_SKIP /* | S2_EVAL_KEEP_STACK */, - &rhs) - /* This causes the following to parse incorrectly: - - 0&&1 ? 2 : 3; ===> false instead of 3 - - _Effectively_ parses as: - - 0 && (1 ? 2 : 3) - - Because of the sub-eval. It actually parses right, but the - skipLevel is still set after the &&, so the end effect is that - the whole ?: gets skipped. - - Contrast with: - - (0&&1) ? 2 : 3; ===> 3 - - which works because of the expression boundary there. - - Anyway: worked around up above in the S2_T_Question - handling. - */ - ; - /* MARKER(("after %d op: skipLevel=%d\n", pt.ttype, se->skipLevel)); */ - assert(lhs == s2_engine_peek_token(se)->value); - if(rc){ - /* - Reminder: lhs is now an errant value, possibly a temp. - It's potentially orphaned for the time being. - (Later: except that it's currently in the eval-holder.) - */ - break; - } - /*MARKER(("rhs-eval rc=%s. capture=%.*s\n", cwal_rc_cstr(rc), - (int)(s2_ptoken_begin(&captureEnd)-s2_ptoken_begin(&captureBegin)), - s2_ptoken_begin(&captureBegin) - ));*/ - /*if( (rc = s2_check_interrupted(se, rc)) ) break; - else */ - if(!rhs){ - s2_ptoker_errtoken_set(st, &pt); - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Empty RHS for logic operator"); - }else{ - s2_stoken * topTok = s2_engine_pop_token(se, 1) /* the short-circuited value */; - assert(topTok->value); - assert(lhs == topTok->value); - topTok->value = 0; - s2_stoken_free(se, topTok, 1); - assert(cwal_value_undefined()==rhs - && "Violation of current API convention for skip-mode evals"); - s2_engine_push_val(se, (S2_T_OpOr3==theOpId || S2_T_OpElvis==theOpId) - ? lhs - : cwal_new_bool(buul) - )->srcPos = pt - /* that allocation cannot fail b/c it will - recycle the popped one unless someone foolishly - disables the stack token recycler. */; - /* s2_dump_val(lhs, "new LHS"); */ - /* assert(lhs == s2_engine_peek_value(se)); */ - op = 0 /* so prevOp does not expose our trickery to the next - iteration */; - if(S2_T_OpOr3 != pt.ttype && S2_T_OpElvis != pt.ttype){ - /* - Clean this up: - s2> x = 0; for( var i = 0; i < 100; (i++, x++ || 1, ++i) ); - MARKER: s2.c:296:s2_engine_sweep(): Swept up 48 value(s) in sweep mode - */ - cwal_refunref(lhs); - }else{ - eval_hold(lhs); - } - continue; - } - }/*if(shortIt)*/ - break; - }/* end &&, ||, ||| */ - case S2_T_Identifier:{ - /* - Identifiers on the RHS of a dot are re-tagged (above) as - PropertyKey, which will cause them not to be seen as - identifiers or keywords here. - - Try a keyword... - */ - if(kword){ - if(!kword->call){ - /* Transform keywords which get treated as operators - into those operators... */ - s2_op const * kOp = s2_ttype_op(kword->id); - assert(kOp); - if(/* check for !inherits (two tokens) and transform - them into a single operator, !inherits. */ - S2_T_OpInherits == kOp->id - && prevOp - && S2_T_OpNot==prevOp->id){ - assert(s2_engine_peek_op(se) - && s2_engine_peek_op(se)->ttype == prevOp->id); - s2_engine_pop_op(se,0); - pt.ttype = S2_T_OpNotInherits; - kOp = s2_ttype_op(pt.ttype); - assert(kOp); - assert(S2_T_OpNotInherits == kOp->id); - prevOp = 0 /* avoid dowstream confusion */; - }else{ - pt.ttype = kword->id; - } - op = kOp; - }/* end operator-like words*/ - else{ - rc = s2_eval_keyword_call(kword, &pt, fromLhsOp, - se, st, &tVal, &nextTokenFlags); - } - }/* end keywords */ - else if(se->skipLevel>0){ - /* - In skip mode, we are obligated to resolve as much as we - optimally can, and we resolve everything to undefined in - that case. - */ - tVal = cwal_value_undefined(); - }else{ - /* Non-keyword identifier, not in skip mode, so resolve - it... */ - s2_stoken const * opT = s2_engine_peek_op(se); - s2_op const * topOp = opT ? s2_ttype_op(opT->ttype) : 0; - assert(opT ? !!topOp : 1); - tVal = s2_var_get(se, -1, s2_ptoken_begin(&pt), (cwal_size_t)tlen); - if(!tVal){ - if(S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED & evalFlags){ - evalFlags &= ~S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED; - tVal = cwal_value_undefined(); - }else{ - rc = s2_throw_ptoker(se, st, CWAL_RC_NOT_FOUND, - "Could not resolve identifier " - "'%.*s'", - tlen, s2_ptoken_begin(&pt)); - } - } - else if(topOp && s2_ttype_is_identifier_prefix(topOp->id)){ - /* Previous token was prefix ++/-- (or similar) */ - if(s2_next_is_dotish(se, st)){ - /* we'll put the resolved tVal in the stack. */ - pt.ttype = S2_T_Value; - }else{ - /* put the unresolved identifier on the stack as a string-type - value. */ - rc = s2_ptoken_create_value(se, st, &pt, &tVal); - if(!tVal){ - assert(rc); - /* rc = CWAL_RC_OOM; */ - } - } - } - else if(s2_next_wants_identifier(se, st)){ - /* Required so that assignment gets the identifier's string value, - but we let it get validated above so that we get precise - error location info. We just hope that pending ops don't - invalidate it (potential corner case?). - */ - tVal = 0; - rc = s2_ptoken_create_value(se, st, &pt, &tVal); - /* - Potential problem here: tVal might return to a shared/stashed - string and we cannot add a ref here. If we later assume it's - new and simply unref it, we will hose a valid Value being used - somewhere else (e.g. in se->stash). - */ - if(!rc){ - assert(tVal); - } - }else{ - pt.ttype = S2_T_Value /* So as to not confuse - the result with its identifier */; - } - } - break; - }/*end Identifier*/ - case S2_T_SquigglyBlock:{ - rc = s2_eval_object_literal( se, st, &tVal ); - if( !rc ){ - assert(tVal); - pt.ttype = S2_T_Value; - } - break; - } -#if 0 - /* - The DotPrototype op (..). While the code below works just - fine, after having used this in scripts i find myself deleting - it and typing ".prototype" to improve the readability. i'm - going to disable the .. op for now and reserve it for something - more useful (don't yet know what, except that numeric ranges - come to mind). - */ - case S2_T_OpDotDot:{ - /* - Transform this token to: - - DOT "prototype" - - We do this here, instead of at the operator level, because - this approach (A) required much less code and (B) works out - of the box with assignment: - - x.. = bar; - - and, by extension, the ++/-- ops, without a lot of extra - fiddling in the operator code. - */ - if(prevOp && prevOp->arity>0 && prevOp->placement<=0){ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Invalid operator '%s' preceeding '%s'.", - prevOp->sym, op->sym); - }else if(0==(rc = s2_eval_lhs_ops(se, st, s2_ttype_op(S2_T_OpDot)))){ - s2_stoken * synthTok = s2_engine_push_ttype( se, S2_T_OpDot ); - if(!synthTok) rc = CWAL_RC_OOM; - else{ - synthTok->srcPos = pt; - pt.ttype = S2_T_Identifier; - tVal = se->skipLevel ? cwal_value_undefined() : se->cache.keyPrototype; - prevOp = 0; - op = 0; - /* ^^^^ that setup will cause "prototype" to be pushed - onto the value stack as the RHS. The injected dot op - will do the LHS checking. It turns out that .prototype - is valid for the majority of expression results. - */ - } - } - break; - } -#endif -/* S2_T_OpDotDot */ - case S2_T_BraceGroup:{ - /* - Here's how we do VALUE[...] ... - - Sub-parse the [...] exactly like for a (...) block, but also - (on success) push a Dot operator onto the stack. That will - resolve to (VALUE DOT RESULT), which is actually exactly - what we want. - */ - cwal_value * lhs; - s2_stoken const * topOpTok = s2_engine_peek_op(se); - s2_op const * topOp = s2_stoken_op(topOpTok); - if(!prevOp /* prev part (if any) was a value */ - && topOp && s2_ttype_is_deref(topOp->id) /* last op is a pending dot */ - ){ - /* Need to process any pending (single) LHS dot operator - to support chaining. */ - /* MARKER(("Processing pending(?) LHS dot op\n")); */ - rc = s2_process_top(se); - if( rc ){ - rc = s2_ammend_op_err(se, st, rc); - assert(rc); - } - topOpTok = 0; - } - if(rc) break; - lhs = s2_engine_peek_value(se); -#if 0 - /* Reminder to self: string interning can bite us here (again) - with missing refs on lhs unless we eval_hold() lhs. Because - this is a more general problem, that hold() was added to - s2_process_op_impl() on 20171115. A case which can (but - doesn't always) trigger a crash without this eval_hold() - is: 'abc'[2][0][0][0][0][0] - */ - eval_hold(lhs); -#endif - if(prevOp || !totalValCount){ - /* Treat as array literal... */ - rc = s2_eval_array_literal( se, st, &tVal ); - if( !rc ){ - pt.ttype = S2_T_Value; - } - break /* avoid ParenGroup fall-through below */; - } - else if(!se->skipLevel - && (!lhs - || prevOp - || (/* !prevOp && */ - !cwal_value_is_unique(lhs) /* for EnumEntry['value'] */ && - !cwal_value_container_part(se->e, lhs))) - ){ - /* Syntax error */ - if(prevOp) s2_ptoker_errtoken_set(st, &prevTok); - rc = s2_throw_ptoker( se, st, CWAL_RC_TYPE, - "Invalid LHS (%s %s) " - "preceeding [...] block.", - s2_ttype_cstr(prevTok.ttype), - prevOp - ? prevOp->sym - : (lhs - ? cwal_value_type_name(lhs) - : "") - ); - } - if(rc) break; - CWAL_SWITCH_FALL_THROUGH; - } - case S2_T_ParenGroup:{ - /* - We expand these into Value tokens by doing a recursive eval - on the byte range enclosed by the group. Call arguments for - functions and 'new' are evaluated separately by - s2_eval_fcall() resp. s2_keyword_f_new(), and arguments to - function-like keywords are handled by the respective - keywords. Thus some uses of parens never land here. - */ - int const braceType = pt.ttype; - char const braceOpen = *s2_ptoken_begin(&pt); - char const braceClose = *(s2_ptoken_end(&pt)-1); - char emptySet = 1; - assert(S2_T_ParenGroup==pt.ttype || S2_T_BraceGroup==pt.ttype); - assert(S2_T_ParenGroup==pt.ttype - ? ('('==braceOpen && ')'==braceClose) - : ('['==braceOpen && ']'==braceClose)); - assert(s2_ptoken_adjbegin(&pt)); - assert(s2_ptoken_adjend(&pt)); - assert(s2_ptoken_adjend(&pt) >= s2_ptoken_adjbegin(&pt)); - if(S2_T_ParenGroup==pt.ttype){ - doBreak = 0; - if(s2_looks_like_fcall(se, st, - (S2_EVAL_STOP_AT_CALL & evalFlags), - prevOp, &rc) && !rc){ - if(S2_EVAL_STOP_AT_CALL & evalFlags){ - doBreak = S2_T_ParenGroup; - }else{ - /* MARKER(("Looks like func call? rc=%s prevOp=%s\n", - cwal_rc_cstr(rc), prevOp?prevOp->sym:"")); */ - rc = s2_eval_fcall(se, st, &tVal); - } - break; - } - else if(rc) break; - } - if(s2_ptoken_adjend(&pt) > s2_ptoken_adjbegin(&pt)){ - /* In order to know whether it's really empty, and fail - consistently across both skipping and non-skipping mode, - we have to parse the subexpr regardless of skip level - :/. - - 2021-06-24: we need to push a scope for this to avoid: - - var o = {}; o[var x = 'hi'] = 1; assert 'hi'===x; - - But we want standalone (...) to run in the current - scope. We have script code which relies on that: - - 1 && (var y = 3); - assert 3 === y; - */ - cwal_scope scope = cwal_scope_empty; - assert(s2_ptoken_begin(&pt) == s2_ptoken_begin(&st->token)); - s2_ptoker_token_set(st, &pt) /* consume it */; - if(!se->skipLevel){ - char const isParens = S2_T_ParenGroup==pt.ttype ? 1 : 0; - s2_stoken const * topOpTok = isParens ? s2_engine_peek_op(se) : 0; - s2_op const * topOp = topOpTok ? s2_stoken_op(topOpTok) : 0; - if(S2_T_BraceGroup==pt.ttype - || (topOp && s2_ttype_is_deref(topOp->id) - /* last op is a pending dot */)){ - rc = cwal_scope_push2(se->e, &scope); - } - } - if(!rc){ - rc = s2_eval_current_token(se, st, 0, 0, &tVal) - /* Reminder to self: passing true as the 3rd arg - breaks this arguable feature: (a;b). - */; - } - if(scope.level){ - cwal_scope_pop2(se->e, rc ? 0 : tVal); - if(rc) tVal = 0 /*was possibly cleaned up by scope pop*/; - } - if(!rc){ - emptySet = tVal ? 0 : 1; - /* s2_dump_val(tVal, "post-() tVal"); */ - } - }else{ - emptySet = 1; - } - if(rc) break; - else if(emptySet){ - assert(!tVal); - if(se->skipLevel - || (!prevOp - && S2_T_BraceGroup==braceType - && cwal_value_array_part(se->e, s2_engine_peek_value(se)))){ - /* - PHP-style array-append operator: - - If LHS val is-a array and braceOpen=='[' then push a - an ArrayAppend op and assert that the next token - is an assignment op. Alternately, we could just assume - an assignment op and elide one if it appears. But we'll - go ahead and be strict for now. - - Minor bug (20160106): - - var v = []; v.blah = 1; - assert v.blah === (v[] = v.blah); - - The parens should not be required. What happens is - that this: - - assert v.blah === v[] = v.blah; - - thinks the LHS of the []= op is a boolean, i.e. it - sees: - - (v.blah === v)[] = v.blah - - 20160107: turns out this applies to all(?) assignments - on the RHS of a binary comparison or logical op. - - Oh, wait... JavaScript fails in the exact same way. :-D - Now i remember commenting about this elsewhere long - ago. - */ - s2_ptoken next = s2_ptoken_empty; - op = s2_ttype_op( pt.ttype = S2_T_ArrayAppend ); - s2_next_token( se, st, - /* this flag is only safe so long as the - up-coming logic using 'next' is - compatible. */ - S2_NEXT_NO_POSTPROCESS, - &next ); - if((rc=s2_check_interrupted(se,rc))){ - /* next-token might have set - error state we which want to ignore... - except for an interruption. */; - break; - } - if(s2_ttype_is_assignment(next.ttype)){ - s2_ptoker_token_set(st, &next) /* consume/skip it */; - /* Fall through and push this op */ - }else{ - s2_ptoker_errtoken_set(st, &pt); - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Missing assignment op " - "after '%s' operator.", - op->sym); - break; - } - }else if(S2_T_ParenGroup==pt.ttype - && !se->st.vals.size - && !se->st.ops.size - && (evalFlags & S2_EVAL_EMPTY_GROUP_OK)){ - /* We're doing this for the sake of return(). */ - evalFlags &= ~S2_EVAL_EMPTY_GROUP_OK; - tVal = cwal_value_undefined(); - }else{ - /* - We (generally) disallow empty () and [] groups because - they would otherwise be ambiguous with (undefined). We - do indeed special-case them in places. - */ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Empty '%c%c' block is not " - "allowed here.", - braceOpen, braceClose); - break; - } - }else{ - assert(!op); - assert(tVal || se->skipLevel); - pt.ttype = S2_T_Value; - /* FALL THROUGH and be picked up as a VALUE via tVal */ - if(S2_T_BraceGroup==braceType){ - /* - OBJ[KEY] is transformed to (OBJ DOT KEY)... - */ - s2_stoken * topOpTok = s2_engine_push_ttype( se, S2_T_OpDot ); - if(!topOpTok){ - rc = CWAL_RC_OOM; - } - else topOpTok->srcPos = pt; - /* MARKER(("Added DOT OP for [] group.\n")); */ - } - } - /* - Hypothetical problem with _not_ having a '(' operator token in the - stack: - - pending LHS ops _might_ not be evaluated in the proper order. - - Something to consider doing here: - - op = s2_ttype_op(S2_T_ParenOpen) - - Then, in the if(op) block below, recognize that particular - token and do _NOT_ push it onto the stack. Instead, set op=0 - and jump into the is-a-Value block. - - Then again, parens are supposed to have amongst the highest - precedence, so there can be no pending LHS operators to the - left with with >= precedence, can there? Pending LHS groups, - OTOH, which have == precedence have already been evaluated - left-to-right, so the effect would seem to be the same. - */ - break; - }/*end parens/brace groups*/ - case S2_T_OpAssign: - case S2_T_OpPlusAssign: - case S2_T_OpMinusAssign: - case S2_T_OpXOrAssign: - case S2_T_OpAndAssign: - case S2_T_OpOrAssign: - case S2_T_OpMultiplyAssign: - case S2_T_OpDivideAssign: - case S2_T_OpModuloAssign: - case S2_T_OpShiftLeftAssign: - case S2_T_OpShiftRightAssign: - case S2_T_OpColonEqual - /* op := is a special case: only supports ternary, not binary */:{ - /** - The binary assign works without us assisting it here, but in - the case of a property assignment, we need to adjust the - operator from the binary assignment (ident=expr) to the - ternary assignment (obj DOT prop = expr), which we translate - to (obj prop .= expr), where '.=' is the member assignment - operator. - - Reminder to self: for compound assignments, e.g. += - - we can translate (A+=B) to (A = A + B) and (A.B+=C) - to (A.B = A.B+C). (But we don't do it that way.) - - Prefix/postfix incr/decr can be done similarly: - --A ==> (var tmp=A; A = A-1; tmp) - Except that we want overloaded ops to know if - they're being called in unary form. Hmm. - */ - s2_stoken * topOpTok = s2_engine_peek_op(se); - if(prevOp /* && prevOp->arity>0 && prevOp->placement<=0 */ - /* How about postfix ++/--? Can we reasonably chain those - with assignments?*/ - ){ - s2_ptoker_errtoken_set(st, &pt); - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Invalid adjacent operators: " - "'%s' ==> '%s'", prevOp->sym, - op->sym); - } - - if(topOpTok && s2_ttype_is_deref(topOpTok->ttype)){ - /* - Assume the stack has (lhs,rhs) operands for the dot - operator, then replace the dot op with a ternary - assignment op: X.Y = Z. - */ - int newOp = 0; - switch(pt.ttype){ -#define CASE(T) case T: newOp = T##3; break - CASE(S2_T_OpAssign); - CASE(S2_T_OpPlusAssign); - CASE(S2_T_OpMinusAssign); - CASE(S2_T_OpXOrAssign); - CASE(S2_T_OpAndAssign); - CASE(S2_T_OpOrAssign); - CASE(S2_T_OpMultiplyAssign); - CASE(S2_T_OpDivideAssign); - CASE(S2_T_OpModuloAssign); - CASE(S2_T_OpShiftLeftAssign); - CASE(S2_T_OpShiftRightAssign); -#undef CASE - case S2_T_OpColonEqual: newOp = S2_T_OpAssignConst3; break; - default: - assert(!"Missing operator mapping."); - s2_fatal(CWAL_RC_FATAL, "Missing operator mapping: %s", - s2_ttype_cstr(pt.ttype)); - } - assert(newOp); - op = s2_ttype_op( pt.ttype = newOp ); - assert(op); - s2_engine_pop_op(se,0)/*==topOpTok, a.k.a. OpDot */; - /* Continue on and allow new op to be pushed to the stack */ - }else if(S2_T_OpColonEqual == pt.ttype){ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "The const-assign operator only works in " - "ternary form (X.Y:=Z)."); - } - break; - }/* /Assignment */ - case S2_T_LiteralInt: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntHex: - case S2_T_LiteralIntOct: - case S2_T_LiteralIntBin: - case S2_T_LiteralDouble: - case S2_T_LiteralStringDQ: - case S2_T_LiteralStringSQ: - case S2_T_LiteralString: - case S2_T_Heredoc: - case S2_T_PropertyKey: - case S2_T_Value: - /* These will be picked up as Values below. */ - break; - default: - /* - 20171130: discovered by accident that a backtick character - in a script was getting parsed as a string value (via - s2_ptoken_create_value()). It was slipping through eval as - a value token. s2_next_token() tags their ttype with their - ASCII value, many of which overlap with various S2_T_OpXXX - and such. It is only at this late point in the evaluation - that we can really take note of them. - */ - if(!op){ - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unhandled token type %d (%s): %.*s", - pt.ttype, s2_ttype_cstr(pt.ttype), - (int)s2_ptoken_len(&pt), s2_ptoken_begin(&pt)); - } - break; - }/*switch(pt.ttype)*/ - - /************************************************************/ - /** - We're done with the handling of the token. We now either have a - value (via tVal) or an operator (via op) to put onto the - stack... - */ - /************************************************************/ - if(rc || doBreak) break; - else if(op){ - if(/* Script starts with an invalid operator. "Invalid" - basically means any which do not look like they can - legally be used at the start of an expression. */ - op->arity>0 - && op->placement>=0 - /* && !fromLhsOp */ - && se->st.vals.size < op->arity-1 /*no values the op can work with*/ - ){ - rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX, - "Illegal operator '%s' at start " - "of expression", op->sym); - }else{ - rc = s2_eval_lhs_ops(se, st, op); - if(!rc){ - s2_stoken * topOpTok = s2_engine_push_ttype(se, pt.ttype); - if(!topOpTok){ - rc = CWAL_RC_OOM; - } - else{ - topOpTok->srcPos = pt; - if(op->placement==0 && op->arity>1){ - /* - Reminder: i do not really want EOL-skipping for - prefix/postfix ops. Prefix should cause a syntax - error if they exist at EOL and postfix already has its - operand on the stack. - */ - } - } - } - } - }/* /if(op) */ - else{/* a Value token */ - if(!tVal){ - if(se->skipLevel>0) tVal = cwal_value_undefined(); - else{ - rc = s2_ptoken_create_value(se, st, &pt, &tVal); - if(!rc){ - assert(tVal); - } - } - } - if(!rc){ - s2_stoken * vtok; - assert(tVal); - if((se->flags.traceTokenStack>0) && (S2_T_Identifier==pt.ttype)){ - MARKER(("Identifier token: %.*s\n", tlen, s2_ptoken_begin(&pt))); - } - vtok = s2_engine_push_tv(se, pt.ttype, tVal); - if(!vtok){ - rc = CWAL_RC_OOM; - }else{ - eval_hold(tVal); - ++totalValCount; - vtok->srcPos = pt; - if(se->skipLevel>0){ -#if 1 - if(cwal_value_undefined()!=tVal){ - s2_dump_val(tVal, "what is this???"); - assert(!"current internal convention for " - "skip-mode evaluation was violated"); - s2_fatal( CWAL_RC_ASSERT, "Internal convntion for " - "skip-mode evaluation was violated." ); - - } -#endif - } - } - }/* /if(!rc) */ - }/* /if op else value */ - }/* /foreach token */ - - if(rc && !s2_ptoker_errtoken_has(st)){ - s2_ptoker_errtoken_set(st, (s2_ptoken_begin(&pt) - && (s2_ptoken_begin(&pt) != s2_ptoken_begin(&pt))) - ? &pt : &prevTok); - }else if(!rc){ - assert(s2_ptoken_begin(&captureBegin)); - } - if(!s2_ptoken_begin(&captureBegin)) captureBegin = st->token; - if(!s2_ptoken_begin(&captureEnd)){ - captureEnd = st->token; - /* - 20200110: Capturing historically elides the eox token, but - before today we captured the expression's contents as a simple - [begin,end) byte range. For EOX-eliding to work with the - two-token capture approach, we have 2 options: - - 1) We need to massage the capture end if (and only if) st->token - is-a EOX. - - 2) We define the capture end token as the one-after-the-end, - rather than the end token. - - It turns out that 2 is easier to implement here, easier to deal - with downstream, and ever-so-slightly more efficient (a handful - fewer CPU instructions). - - When capturing a completely empty expression with an EOX, - e.g. ";", captureBegin will unavoidably point to that semicolon, - which goes against our age-old policy of *not* including the - semicolon in the capture, but captureEnd will *also* point to - that token, so well-behaved code will not include the ';' in any - handling. - */ - } - assert(s2_ptoken_begin(&captureEnd) >= s2_ptoken_begin(&captureBegin) - && s2_ptoken_end(&captureEnd) >= s2_ptoken_end(&captureBegin)); - st->capture.begin = captureBegin; - st->capture.end = captureEnd; - /*MARKER(("captured: <<<%.*s>>>\n", (int)(captureEnd.begin - captureBegin.begin), - captureBegin.begin));*/ - /* MARKER(("Stack sizes: op=%d, token=%d\n", se->st.ops.size, se->st.vals.size)); */ - if(rc) goto end; - - /* - Now process what's left of the stack... - */ - if(!rc && ownStack){ - while(!rc && se->st.ops.size){ - rc = s2_process_top(se); - } - if(rc){ - rc = s2_ammend_op_err(se, st, rc); - assert(rc); - goto end; - } - } - /* MARKER(("Stack sizes: op=%d, token=%d\n", se->st.ops.size, se->st.vals.size)); */ - if(!rc && ownStack && (1 != se->st.vals.size)){ /* too many or too few items in stack */ - if(!se->st.vals.size - && s2_ttype_is_eox(st->token.ttype) - /* ==> empty expression */ - ){ - /*MARKER(("Truly empty expression? totalValCount=%d %.*s\n", - totalValCount, (int)(capEnd - capBegin), capBegin));*/ -#if 0 - if(!totalValCount){ - /* A truly empty expression with no expected value part - before the EOX. */ - /* if(evalIt) */xrv = 0; - } -#endif - /*else{ - - Leave previous expression value, if any, because we - currently need it for: - - X; ==> X - - but it also means that (X;;; ==> X), which we really - don't want. Catching that has to be done from the code - calling this - it needs to remember seeing (or not) - multiple EOX tokens. - }*/ - }else if((se->st.vals.size > 0) && totalValCount){ - s2_ptoker_errtoken_set(st, &pt); - rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX, - "Unexpected stack size " - "(%d) after expression. " - "Normally caused by a missing ';' " - "in the previous line.", - se->st.vals.size); - } - }/* /stack size error checking. */ - - if(!rc){ - cwal_value * xrv = s2_engine_pop_value(se); - /* assert(xrv); */ - if(se->skipLevel>0){ -#if 1 - /* s2_dump_val(xrv, "what is this"); */ - assert((!xrv || (xrv == cwal_value_undefined())) - && "current internal convention for " - "skip-mode evaluation was violated"); -#endif - } - if(se->flags.traceTokenStack>0 && !se->skipLevel){ - s2_dump_val(xrv, "eval_expr result"); - } - if(rv) *rv = xrv; - else cwal_refunref(xrv); - } - - end: -#undef eval_hold -#undef eval_hold_this_value - if(rc){ - rc= 1 ? rc : 0 /* put breakpoint here (the ?: is to avoid - assignment-to-self warning from clang).*/; - } - /* Clean up... */ - - if(rc && !s2__err(se).code && st->errMsg - && (CWAL_RC_EXCEPTION!=rc)){ - /* Error from the tokenizer. */ - rc = s2_err_ptoker(se, st, rc, 0); - } - - rc = s2_rv_maybe_accept(se, scope.parent /* 0 is okay */, rc, rv); - -#if EVAL_USE_HOLDER - if(holder){ - assert(!se->skipLevel); - /* --se->sguard->vacuum; */ - assert(1 == cwal_value_refcount(cwal_array_value(holder))); - if(!rc && rv) cwal_value_ref(*rv); - cwal_array_length_set( holder, holderLen ) - /* truncate to its old length, potentially freeing any refs this - eval() added */; - holder = 0; - if(!rc && rv) cwal_value_unhand(*rv); - } -#endif -/* ^^^^ EVAL_USE_HOLDER */ - - if(/* !fromLhsOp && */ !(S2_EVAL_RETAIN_DOTOP_STATE & evalFlags)){ - /* We must clear these to avoid picking them up after they're - stale. However, they's needed by, e.g. (unset x.y). - - Reminder: this ref/unhand of *rv is critical to avoid a crash - caused by too many unrefs here: - - scope { {## a: 3}.a } - - where the temporary LHS hash is in se->dotOp.self and and the - result value of the expression will get destroyed by it when - s2_dotop_state() resets it. Thus we need to ref it. Right here: - */ - if(rv && *rv) cwal_value_ref(*rv); - s2_dotop_state( se, 0, 0, 0 ); - if(rv && *rv) cwal_value_unhand(*rv); - } - if(!evalIt){ - assert(se->skipLevel==1+oldSkipLevel); - se->skipLevel = oldSkipLevel; - } - if(ownStack){ - s2_engine_stack_swap(se, &priorStack); - s2_estack_clear(se, &priorStack, 1); -#ifdef DEBUG - assert(oldOpCount == se->st.ops.size); - assert(oldValCount == se->st.vals.size); -#endif - } - if(!consumeIt){ - s2_ptoker_token_set( st, &pbOrigin ); - s2_ptoker_putback_set(st, &pbOrigin); - }else{ - s2_ptoker_putback_set(st, &pOrigin) - /* yes, pOrigin (not pbOrigin) - we want to be able to putback - the whole expression */; - } - --se->metrics.subexpDepth; - - se->currentScript = oldScript; - if(S2_EVAL_PUSH_SCOPE & evalFlags){ - assert(scope.parent); - assert(s2_scope_current(se)->cwalScope==&scope); - cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0)); - }else{ - if(!scope.parent){ - --se->scopes.current->sguard.sweep; - } - } - return rc; -} - -#undef EVAL_USE_HOLDER - -int s2_eval_buffer( s2_engine * se, - char newScope, - char const * name, - cwal_buffer const * buf, - cwal_value **rv ){ - return s2_eval_cstr( se, newScope, name, - buf->used ? (char const *)buf->mem : "", - (int)buf->used, - rv ); -} - -int s2_eval_cstr( s2_engine * se, - char newScope, - char const * name, - char const * src, int srcLen, - cwal_value **rv ){ - s2_ptoker pt = s2_ptoker_empty; - int rc = s2_ptoker_init_v2( se->e, &pt, src, srcLen, 0 ); - if(!rc){ - cwal_scope SC = cwal_scope_empty; - if(!newScope || !(rc=cwal_scope_push2(se->e, &SC))){ - cwal_value * xrv = 0; - pt.name = (name && *name) ? name : 0; - rc = s2_eval_ptoker( se, &pt, 0/*TODO: flags*/, rv ? &xrv : 0 ); - if(!rc && rv) *rv = xrv; - else cwal_refunref(xrv); - if(SC.parent){ - cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0)); - } - } - } - s2_ptoker_finalize( &pt ); - return rc; -} - -int s2_eval_cstr_with_var( s2_engine * se, - char const * varName, - cwal_value * varValue, - char const * scriptName, - char const * src, int srcLen, - cwal_value **rv ){ - int rc; - cwal_scope sc = cwal_scope_empty; - rc = cwal_scope_push2(se->e, &sc); - if(rc) return rc; - cwal_value_ref(varValue); - rc = cwal_var_decl(se->e, 0, varName, cwal_strlen(varName), varValue, 0); - if(!rc){ - rc = s2_eval_cstr(se, 0, scriptName, src, srcLen, rv ); - } - cwal_scope_pop2(se->e, rc ? (rv ? *rv : 0) : 0); - cwal_value_unhand(varValue); - return rc; -} - - -int s2_eval_ptoker( s2_engine * se, s2_ptoker * pr, int e2Flags, cwal_value **rv ){ - int const oldTStackSize = se->st.vals.size; - cwal_value * xrv = 0 /* pending result value */; - int hardEoxCount = 0; - int const srcLen = (int)(s2_ptoker_end(pr) - s2_ptoker_begin(pr)); - s2_ptoker const * oldScript = se->currentScript; - char xrvWasVacSafe = 0 /* whether not not xrv was vacuum-safe (if not, we need to make it so). */; - int rc = s2_check_interrupted(se,0); - if(rc) return rc; - else if(srcLen<0){ - return s2_engine_err_set(se, CWAL_RC_MISUSE, "Invalid s2_ptoker range."); - }else if(!srcLen || !*s2_ptoker_begin(pr)){ - if(rv) *rv = 0; - return 0; - } - - /* - We have one notable problem in the sweepup mechanism with regard - to iterating over expressions and keeping the most recent result: - vacuuming can steal the result value from us. So we either have to - make the value itself vacuum-proof (which doesn't work for PODs - and would have unwanted side-effects in some cases) or we can - disallow vacuuming so long as we have a pending expression. - Bummer. - - As of 20160228, cwal can make non-container instances - vacuum-proof, so we use that to make the pending result safe from - vacuuming (and a ref point to keep it safe from sweep-up) while - not unduly extending its lifetime because (A) each new result - overwrites it, potentially cleaning it up immediately, and (B) at - the end of the block we set the references straight (in a kosher - manner) for the return via *rv. - */ - s2_engine_err_reset(se); - se->currentScript = pr; -#if 0 - if(pr->name){ - MARKER(("Running script [%s]\n", pr->name)) - /* for helping in finding a script name bug in interactive - mode. */ - ; - } -#endif - for( ; !rc ; ){ - int isEof, isEox; - cwal_value * vrx = 0; - s2_engine_sweep(se); - - rc = s2_eval_expr_impl(se, pr, 0, 0, rv ? &vrx : 0); - if(rc) break; -#if 0 - MARKER(("Ran expr: value=%p, capture=[[[%.*s]]]\n", - (void const *)vrx, - (int)(s2_ptoken_begin(&pr->capture.end) - - s2_ptoken_begin(&pr->capture.begin)), - s2_ptoken_begin(&pr->capture.begin))); -#endif - isEof = s2_ptoker_is_eof(pr); - isEox = isEof ? isEof : s2_ttype_is_eox(pr->token.ttype); - if(vrx){ - hardEoxCount = 0 /* tells us (later) to keep xrv at the next EOX. */; - assert(rv && "Else all of this is a waste of time..."); - /* Swap out pending result... */ - cwal_value_ref(vrx); - if(xrv){ - if(!xrvWasVacSafe){ - assert(cwal_value_is_vacuum_proof(xrv)); - cwal_value_make_vacuum_proof(xrv, 0); - } - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* will fail on too many unrefs */); - cwal_value_unref(xrv); - } - /* Make vrx the new pending result... */ - xrv = vrx; - vrx = 0; - xrvWasVacSafe = xrv ? cwal_value_is_vacuum_proof(xrv) : 0; - if(xrv){ /* formerly known as vrx */ - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* we ref'd it above */); - if(!xrvWasVacSafe) cwal_value_make_vacuum_proof(xrv, 1); - /* cwal_unique_wrapped_set(holder, xrv); */ - /* s2_dump_val(xrv, "s2_eval_expr result"); */ - } - } - if(!isEof && isEox){ - /* - Reminder; "3 ;" ends in an EOX but has a value. It evals - differently than "3 ; ;", which evals to two expressions, the - second one empty. - - We accept one ';' as "keep previous value", but a series of - semicolons, possibly separated by other empty expressions, - evals to NULL. We pass this NULL back to the caller so that - they can differentiate between empty expression [as the final - result] and the undefined value (which would be the - alternative result). Splitting hairs, either way, in this - context (but the distinction is important in/via - s2_eval_expr_impl()). - */ - if(rv && ++hardEoxCount>1 && xrv){ - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* we ref'd it above */); - assert(cwal_value_is_vacuum_proof(xrv)); - if(!xrvWasVacSafe) cwal_value_make_vacuum_proof(xrv, 0); - cwal_value_unref(xrv); - xrv = 0; - } - }else if(!isEox){ - hardEoxCount = 0; - } - if(isEof) break; - }/* for-each expression */ - /* MARKER(("Stack sizes: op=%d, token=%d\n", se->pr->ops.size, se->pr->vals.size)); */ - if(!rc){ - if(oldTStackSize != se->st.vals.size){ - /*MARKER(("Unexpected stack sizes: op=%d, token=%d\n", - se->pr->ops.size, se->pr->vals.size));*/ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected stack size in s2_eval_ptoker(): %d", - se->st.vals.size); - }else{ - rc = s2_check_interrupted(se, rc); - } - } - - if(xrv && !xrvWasVacSafe){ - assert(cwal_value_is_vacuum_proof(xrv)); - cwal_value_make_vacuum_proof(xrv, 0); - } - /* s2_dump_val(xrv, "s2_eval_ptoker result"); */ - /* s2_dump_val(xrv, "s2_eval_ptoker result"); */ - if(!pr->parent - && CWAL_RC_RETURN==rc - && !(e2Flags & S2_EVALP_F_PROPAGATE_RETURN)){ - /* Top-level parser shall accept a RETURN as a non-error, and - stuff it in *rv. Unless e2Flags has the S2_EVALP_F_PROPAGATE_RETURN - bit set. - - We do not handle EXIT here because this routine will be used to - import files from other files, without having the parent - sources as pr->parent, and exit() needs to work cross-file - whereas we can justify RETURN stopping at a file boundary - (PHP-style). - */ - cwal_value * rr = s2_propagating_take(se); - assert(rr); - cwal_value_ref(rr); - cwal_value_unref(xrv); - cwal_value_unhand(rr); - xrv = rr; - rc = 0; - /* s2_engine_err_reset(se); */ - }else if(xrv){ - assert(rv); - if(!rc) cwal_value_unhand(xrv) - /* we pass xrv on down below */; - else{ - cwal_value_unref(xrv); - xrv = 0; - } - } - - if(!rc && rv){ - *rv = xrv; - assert((!xrv || cwal_value_scope(xrv) || cwal_value_is_builtin(xrv)) - && "Seems like we cleaned up xrv too early."); - if(se->flags.traceTokenStack){ - s2_dump_val(*rv, "s2_eval_ptoker result"); - } - } - se->currentScript = oldScript; - return rc; -} - -int s2_keyword_f_breakpoint( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc; - S2_UNUSED_ARG kw; - S2_UNUSED_ARG se; - S2_UNUSED_ARG pr; - *rv = cwal_value_undefined(); - rc = 0 /* place breakpoint here */; - /** - TODO: - - add an interactive breakpoint callback hook, which the client can - optionally start running when the hook is called. e.g. s2sh might - (only in interactive mode) switch (back) to interactive mode in - the callback, returning here when it's done. We'd need to install - some convenience symbols here: - - __SCOPE = vars - */ - return rc; -} - -int s2_keyword_f_builtin_vals( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - if(se->skipLevel){ - *rv = cwal_value_undefined(); - return 0; - }else{ - switch(pr->token.ttype){ - case S2_T_KeywordUndefined: *rv = cwal_value_undefined(); return 0; - case S2_T_KeywordNull: *rv = cwal_value_null(); return 0; - case S2_T_KeywordTrue: *rv = cwal_value_true(); return 0; - case S2_T_KeywordFalse: *rv = cwal_value_false(); return 0; - default: - assert(!"Invalid keyword mapping"); - S2_UNUSED_ARG kw; - s2_fatal(CWAL_RC_RANGE, "Invalid keyword mapping in s2_keyword_f_builtin_vals()") - /* does not return, but your compiler doesn't know that, so... */; - return CWAL_RC_ERROR; - } - } -} - -int s2_keyword_f_FLC( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - s2_linecol_t line = 0, col = 0; - char const * script = 0; - cwal_size_t scriptLen = 0; - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - } - /* s2_ptoker_err_info( pr, &script, s2_ptoken_begin(&pr->token), &line, &col ); */ - switch(kw->id){ - case S2_T_KeywordFILE: - script = s2_ptoker_name_first(pr, &scriptLen); - *rv = script - ? cwal_new_string_value(se->e, script, scriptLen) - : cwal_value_undefined(); - break; - case S2_T_KeywordFILEDIR:{ - char const * sepPos; - cwal_size_t len; - script = s2_ptoker_name_first(pr, &scriptLen); - sepPos = scriptLen ? s2_last_path_sep(script, scriptLen) : 0; - len = sepPos ? (cwal_size_t)(sepPos - script) : 0; - assert(len <= scriptLen); - *rv = len - ? cwal_new_string_value(se->e, script, len) - : (sepPos /* root dir */ - ? cwal_new_string_value(se->e, sepPos, 1) - : cwal_new_string_value(se->e, 0, 0)); - break; - } - case S2_T_KeywordLINE: - s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), &line, 0); - *rv = cwal_new_integer(se->e, (cwal_int_t)line); - break; - case S2_T_KeywordCOLUMN: - s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), 0, &col); - *rv = cwal_new_integer(se->e, (cwal_int_t)col); - break; - case S2_T_KeywordSRCPOS: - s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), &line, &col); - script = s2_ptoker_name_first(pr, &scriptLen); - *rv = (line>0) - ? cwal_string_value(script - ? cwal_new_stringf(se->e, "%.*s:%d:%d", - (int)scriptLen, script, - line, col) - : cwal_new_stringf(se->e, "unnamed script:%d:%d", - line, col) - ) - : cwal_value_undefined(); - break; - default: - assert(!"Invalid operator mapping"); - return CWAL_RC_ERROR; - } - return *rv ? 0 : CWAL_RC_OOM; -} - - -int s2_keyword_f_exception( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc; - cwal_array * args = 0; - cwal_value * argsV = 0; - cwal_size_t argc = 0; - s2_ptoker sub = s2_ptoker_empty; - s2_ptoken const origin = pr->token; - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) return rc; - else if(pr->token.ttype != S2_T_ParenGroup){ - /* 20191228: if the exception keyword is followed by anything - other than a'(', it resolves to the exception prototype. */ - s2_ptoker_putback(pr); - *rv = se->skipLevel - ? cwal_value_undefined() - : cwal_prototype_base_get( se->e, CWAL_TYPE_EXCEPTION ); - return 0; - } - /*MARKER(("exception: %.*s\n", - (int)s2_ptoken_len(&pr->token), s2_ptoken_begin(&pr->token))); */ - *rv = cwal_value_undefined(); - if(rc || se->skipLevel>0) goto end; - - args = cwal_new_array(se->e); - if(!args){ - rc = CWAL_RC_OOM; - goto end; - } - argsV = cwal_array_value(args); - cwal_value_ref(argsV); - cwal_value_make_vacuum_proof(argsV, 1); - - rc = s2_ptoker_sub_from_toker(pr, &sub); - if(!rc) rc = s2_eval_to_array(se, &sub, args, 0); - s2_ptoker_finalize(&sub); - if(rc) goto end; - argc = cwal_array_length_get(args); - /* s2_dump_val(argsV,"argsV"); */ - if(argc==1 || argc==2){ - /* - (ARG) is equivalent to what we get with (throw ARG), namely - that the 'message' property === ARG. - - (ARG1, ARG2) sets the 'code' property to ARG1 and the - 'message' property to ARG2. - */ - int exceptionCode = CWAL_RC_EXCEPTION; - if(argc>1){ - /* Try to parse result code from exception(CODE,message)... - Accept an integer or string in the form "CWAL_RC_name" - resp. "S2_RC_name". - */ - cwal_value * arg0 = cwal_array_get(args, 0); - cwal_value *hv = s2_stash_get(se, "RcHash") - /* Optimization: if the stashed RcHash (set up in s2.c) is - available, check it first. This avoids having to allocate - x-strings which we know are already in that hash. It also - incidentally supports a reverse mapping, such that passing in - the string 'CWAL_RC_OOM' will return its integer value. - */; - *rv = 0; - if(hv){ - cwal_hash * h = cwal_value_get_hash(hv); - assert(h); - *rv = cwal_hash_search_v(h, arg0); - if(*rv){ - if(!cwal_value_is_integer(*rv)){ - *rv = cwal_hash_search_v(h, *rv) /* reverse mapping */; - assert(*rv); - assert(cwal_value_is_integer(*rv)); - } - exceptionCode = (int)cwal_value_get_integer(*rv); - } - } - if(!*rv){ - /* Try argument as a string (enum entry name) or integer (enum - entry value)... */ - cwal_size_t nameLen = 0; - char const * codeName = cwal_value_get_cstr(arg0, &nameLen); - if(codeName && s2_cstr_to_rc(codeName, (cwal_int_t)nameLen, &exceptionCode)){ - if(! (*rv = cwal_new_integer(se->e, (cwal_int_t)exceptionCode)) ){ - rc = CWAL_RC_OOM; - } - }else{ - exceptionCode = (int)cwal_value_get_integer(arg0); - } - } - *rv = 0; - } - switch(exceptionCode){ - case 0: - case CWAL_RC_OOM: /* don't allow OOM passed in here to crash the script engine */ - exceptionCode = CWAL_RC_EXCEPTION; - break; - } - s2_ptoker_errtoken_set(pr, &origin); - rc = s2_throw_value( se, pr, exceptionCode, - cwal_array_get(args, argc==1 ? 0 : 1)); - if(CWAL_RC_EXCEPTION==rc){ - /* Success! Now take away the newly-populated exception. */ - cwal_value * exv = cwal_exception_get(se->e); - assert(exv); - /* s2_dump_val(exv,"exv"); */ - assert(cwal_value_exception_part(se->e,exv)); - cwal_value_ref(exv); - cwal_exception_set(se->e, 0); - cwal_value_unhand(exv); - s2_engine_err_reset(se); - *rv = exv; - rc = 0; - } - }else{ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s(...) requires (Message) or " - "(int|string Code, Message) arguments.", - kw->word); - } - - end: - if(argsV){ - cwal_value_make_vacuum_proof(argsV, 0); - cwal_value_unref(argsV); - } - return rc; -} - -int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - cwal_scope scope_ = cwal_scope_empty; - cwal_scope * scope = 0 /* gets set if we push a scope */; - cwal_value * xrv = 0 /* internal result value */; - int sourceType = 0 /* logical token type of the part after the keyword */; - int modifier = 0 /* set if the -> or => modifier is specified */; - int phase = 0 /* evaluation phase: 1st or 2nd (where we eval string content fetched in phase 1) */; - s2_ptoken const origin = pr->token; - s2_ptoken exprPos = s2_ptoken_empty; - int evalFlags = 0; - int scopeFlags = S2_SCOPE_F_NONE; - int isBlock = 0; - switch(kw->id){ - /* These push a scope... */ - case S2_T_KeywordCatch: - case S2_T_KeywordScope: - if(!se->skipLevel) scope = &scope_; - break; - /* These do not push a scope, or do so at their own level... */ - case S2_T_KeywordEval: - case S2_T_KeywordFatal: - case S2_T_KeywordThrow: - break; - case S2_T_KeywordExit: - case S2_T_KeywordBreak: - case S2_T_KeywordReturn: -#if 0 - /* i don't like this inconsistency */ - evalFlags = S2_EVAL_EMPTY_GROUP_OK; -#endif - break; - /* And these are just plain wrong... */ - default: - assert(!"Invalid keyword mapping!"); - return s2_err_ptoker(se, pr, CWAL_RC_FATAL, - "Invalid keyword mapping."); - } - - /* - Check if the following expression is a {squiggly} or not, - or whether it uses one of the -> or => modifiers... - */ - { - s2_ptoker next = *pr; - next_token: /* modifiers cause us to goto here */ - rc = s2_next_token(se, &next, 0, 0); - if(rc) return rc; - else{ - switch(next.token.ttype){ - case S2_T_SquigglyBlock: - sourceType = next.token.ttype; - s2_ptoker_token_set(pr, &next.token)/*consume it*/; - break; - case S2_T_OpArrow2: - switch(kw->id){ - case S2_T_KeywordBreak: - case S2_T_KeywordEval: - case S2_T_KeywordExit: - case S2_T_KeywordFatal: - case S2_T_KeywordReturn: - break; - default: - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "The => modifier can only be used " - "with these keywords: " - "break, eval, exit, fatal, return"); - } - CWAL_SWITCH_FALL_THROUGH; - case S2_T_OpArrow: - if(!modifier){ - modifier = next.token.ttype; - s2_ptoker_token_set(pr, &next.token) /* consume it */; - goto next_token; - }else{ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected extra modifier token '%.*s'.", - (int)s2_ptoken_len(&next.token), - s2_ptoken_begin(&next.token)); - } - break; - default: - s2_ptoker_next_token_set(pr, &next.token) - /* hits fairly often: just over 400 out of 10457 - next-token requests in the current UNIT.s2 - amalgamation. */; - break; - } - } - } - - isBlock = (sourceType == S2_T_SquigglyBlock) ? 1 : 0 - /* treat heredocs as strings for eval purposes */; - if(!isBlock){ - /* - Consider: (1 ? catch 2 : 3) - - Without this flag, the ':' after 'catch 2' is seen as a - standalone token with no '?' counterpart (causing a syntax - error). Thus we have to set a flag to tell downstream code that - the ':' ends that (sub)expression. - */ - scopeFlags = S2_SCOPE_F_KEEP_TERNARY_LEVEL; - } - - if(scope){ - rc = s2_scope_push_with_flags(se, scope, scopeFlags); - if(rc) return rc; - assert(scope->parent); - } - - exprPos = pr->token; - /* As of here, don't use 'return': use goto end. */ - - if(isBlock){ - if(se->skipLevel>0){ - /* Skip it! */ - goto end; - } - else if(S2_T_OpArrow2 == modifier){ - /* eval=>{block} result = the block body as a string. */ - char const * beg; - cwal_size_t len; - s2_ptoken const * block = &pr->token; - assert(S2_T_SquigglyBlock==block->ttype); - assert(s2_ptoken_adjbegin(block)); - assert(s2_ptoken_adjend(block) && - s2_ptoken_end(block) >= s2_ptoken_begin(block)+2); - /* Should we keep leading/trailing spaces or not? */ -#if 0 - /* Keep spaces... */ - beg = s2_ptoken_begin(block) + 1 /* skip opening '{' */; - len = (cwal_size_t)s2_ptoken_len(block)-2 - /* skip trailing '}' */; -#else - /* Strip spaces... */ - beg = s2_ptoken_cstr2( block, &len ); -#endif - xrv = cwal_new_string_value(se->e, beg, len); - if(!xrv) rc = CWAL_RC_OOM; - else cwal_value_ref(xrv); - } - else{ - /* Eval the block... */ - switch(kw->id){ - case S2_T_KeywordBreak: - case S2_T_KeywordExit: - case S2_T_KeywordFatal: - case S2_T_KeywordReturn: - case S2_T_KeywordThrow: - /* $keyword {} treats {} as an Object. This is incidentally - aligns with $keyword [...] seeing an array literal. We - eval it as a normal expression, as opposed to an Object - literal, to pick up object literals and any following - operators. */ - s2_ptoker_next_token_set(pr, &pr->token) /* avoids re-tokenization effort... */; - /* MARKER(("pr->nextToken = pr->token\n")); */ - rc = s2_eval_expr_impl(se, pr, 0, 0, &xrv); - /* - This leads to a syntactic inconsistency between - eval/scope/catch and the other variants: - - eval {1},2; // two separate expressions: (eval==>1), literal 2 - return {a:1},2; // one expresion (object, 2) ==> 2 - */ - cwal_value_ref(xrv); - break; - default: - /* Treat {} as a script block */ - rc = s2_eval_current_token( se, pr, 0, 0, &xrv ); - cwal_value_ref(xrv); - break; - } - /* s2_dump_val(xrv, "EVAL result"); */ - if(!rc && !xrv) xrv = cwal_value_undefined(); - } - }else{ - /* Not a block construct. Behave mostly as if the keyword wasn't - there (except that we ignore LHS operators). */ - s2_op const * pseudoOp = -#if 1 - 0 -#else - (sourceType == S2_T_Heredoc) - ? s2_ttype_op(S2_T_Comma) - : 0 - /* So that we stop eval'ing the RHS at a comma or higher prec, - AND because the special RHSEval. - - But doing that breaks: - - return 1, 2, 3; - - And fixing that causes some broken (wrong script name) - error messages in code broken by this. - - Consider: - - eval {1+2}, 3 - - because of the {block}, that "probably really" should be - (1+2), 3, as this is the only context where we allow a - {block} like this. - */ -#endif - ; - char const isArrow2 = (S2_T_OpArrow2 == modifier) ? 1 : 0; - /* MARKER(("%s'ing around %.10s ...\n", kw->word, - s2_ptoken_begin(&exprPos))); */ - - if(isArrow2){ - ++se->skipLevel - /* so that we capture the full text of the expression - without "really" executing it. */; - } - rc = s2_eval_expr_impl( se, pr, pseudoOp, evalFlags, &xrv ); - cwal_value_ref(xrv); - if(isArrow2) --se->skipLevel; - if(isArrow2 && !rc){ - /* Capture the expression's text as a string. For consistency - with other eval contexts, treat an empty expression (as - opposed to an empty script string) as an error. */ - assert(0==xrv || cwal_value_undefined()==xrv); - /* Capture the text of the expression as the result. */ - if(se->skipLevel){ - xrv = cwal_value_undefined(); - }else{ - cwal_size_t capLen = 0; - char const * cap = s2_ptoker_capture_cstr(pr, &capLen); - assert(cap); - /*MARKER(("OpArrow2 capture: <<<%.*s>>>\n",(int)capLen, cap));*/ - while(capLen>0 && s2_is_space(*cap)){ - /* Skip leading spaces for this specific case because - "it just looks funny" otherwise. */ - /* Leading space hypothetically cannot happen with the - two-token capture approach, but better safe than sorry - (it's conceivable, but seems unlikely, that a newline - could slip through this way). */ - ++cap; - --capLen; - } - while(capLen>0 && s2_is_space(cap[capLen-1])){ - /* Skip trailing spaces for consistency with the {block} form.*/ - --capLen; - } - if(!capLen){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Empty expression is not allowed here."); - goto end; - } - assert(capLen); - xrv = cwal_new_string_value(se->e, cap, capLen); - if(!xrv) rc = CWAL_RC_OOM; - else cwal_value_ref(xrv); - } - } - /* s2_dump_val(xrv, "EVAL result"); */ - if(!rc && !xrv){ - assert(!isArrow2); - switch(kw->id){ - /* These are allowed to have empty expressions... */ - case S2_T_KeywordReturn: - case S2_T_KeywordExit: - case S2_T_KeywordBreak: - case S2_T_KeywordFatal: - xrv = cwal_value_undefined(); - break; - /* The rest are not... */ - default:{ - char const * errMsg; - switch(kw->id){ - case S2_T_KeywordThrow: - errMsg = "%s requires a non-empty expression operand."; - break; - default: - errMsg = "%s requires a non-empty expression or {script} operand."; - } - s2_ptoker_errtoken_set(pr, &origin); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - errMsg, kw->word); - } - } - } - } - ++phase; - - if(!rc - && !se->skipLevel - && S2_T_OpArrow == modifier /* got -> modifier */ - && cwal_value_is_string(xrv) - /* ^^^ We don't want this upcoming cwal_value_get_cstr() to get - source code from a Buffer because (partly) of potentially - fatal corner-cases if that Buffer is modified during/via the - eval. */ - ){ - /* second-pass evaluation: eval -> expr */ - cwal_size_t vlen = 0; - char const * vstr = cwal_value_get_cstr(xrv, &vlen); - if(vstr){ - s2_ptoker sub = s2_ptoker_empty; - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv)); - rc = s2_ptoker_init_v2( se->e, &sub, vstr, (int)vlen, 0 ); - if(!rc){ - /* We have to ref and vacuum-guard to keep this follow-up eval - from vacuuming xrv out from under us. Thanks again, - valgrind. We also have to ref xrv (thanks, cwal - assertions). - */ - cwal_value * xrv2 = 0; - ++se->scopes.current->sguard.vacuum; - s2_ptoker_count_lines( pr, s2_ptoken_end(&exprPos), - &sub.lineOffset, - &sub.colOffset ); - sub.name = s2_ptoker_name_first(pr, 0) - /* Needed when functions are created in this code. It kinda - (well... totally) confuses the __FLC-related bits, - though, as well as exceptions and parse errors. - */; - /* sub.parent = pr; wrong: sub is dynamic text */ - rc = s2_eval_ptoker(se, &sub, - S2_EVALP_F_PROPAGATE_RETURN, - &xrv2) - /* ..._RETURN flag needed so that (eval -> 'return 1') - behaves just like (eval -> return 1) does.*/ - ; - --se->scopes.current->sguard.vacuum; - cwal_value_ref(xrv2); - cwal_value_unref(xrv); - xrv = xrv2; - } - s2_ptoker_finalize(&sub); - ++phase; - } - }/* end second-pass eval */ - end: - if(se->skipLevel>0){ - if(xrv){ - assert(cwal_value_undefined() == xrv); - }else{ - xrv = cwal_value_undefined(); - } - }else{ - switch(kw->id){ - case S2_T_KeywordBreak: - case S2_T_KeywordExit: - case S2_T_KeywordFatal: - case S2_T_KeywordReturn: - if(!rc){ - if(xrv){ - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv)); - }else{ - xrv = cwal_value_undefined(); - } - s2_propagating_set(se, xrv); - cwal_value_unref(xrv) /* ^^^ took a reference */; - xrv = 0; - switch(kw->id){ - case S2_T_KeywordExit: rc = CWAL_RC_EXIT; break; - case S2_T_KeywordFatal: rc = CWAL_RC_FATAL; break; - case S2_T_KeywordReturn: rc = CWAL_RC_RETURN; break; - case S2_T_KeywordBreak: rc = CWAL_RC_BREAK; break; - default: - s2_fatal(CWAL_RC_ASSERT, - "Cannot happen: invalid nested switch() values."); - } - /* - But we need to keep the error location... - */ - { - s2_ptoken err = origin; - s2_ptoken_begin_set(&err, s2_ptoken_end(&origin)); - s2_ptoken_end_set(&err, s2_ptoken_end(&err)); - s2_ptoker_errtoken_set(pr, &err); - rc = s2_err_ptoker(se, pr, rc, 0); - } - } - break; - case S2_T_KeywordThrow: - if(!rc){ - if(xrv){ - assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv)); - }else{ - xrv = cwal_value_undefined(); - } - s2_ptoker_errtoken_set(pr, &origin); - rc = s2_throw_value( se, pr, CWAL_RC_EXCEPTION, xrv); - /* rc will be CWAL_RC_EXCEPTION or something more serious - (CWAL_RC_OOM) */; - assert(rc); - cwal_value_unref(xrv) /* throwing took a refrence to it. */; - xrv = 0; - } - break; - case S2_T_KeywordCatch: - cwal_value_unref(xrv) - /* discard any would-propagate result */; - xrv = 0; - switch(rc){ /* Convert certain errors to exceptions... */ - case 0: - xrv = cwal_value_undefined(); - break; - case CWAL_SCR_SYNTAX: - /* MARKER(("pr->errPos=[%.30s...]\n", pr->errPos ? pr->errPos : "")); */ - /* MARKER(("se->opErrPos=[%.30s...]\n", se->opErrPos ? se->opErrPos : "")); */ - if(!s2__err(se).code){ - break /* we have nothing to report */; - }else if(phase < 2 && S2_T_SquigglyBlock != sourceType){ - /* Don't convert errors which came from EXPR input - unless the error came from its contents (via the - -> modifier), because we cannot know from here if the - EXPR part itself has a syntax error. - */ - break; - } - else if( CWAL_RC_EXCEPTION != - (rc = s2_throw_err_ptoker(se, pr))){ - /* a more serious error */ - break; - } - /* - Else fall through and treat it like the exception we - just threw. We know that the error is somewhere - contained in the _contents_, but the contents are - (ostensibly) syntactically legal. In the EXPR form, the - error might be somewhere in the top of the expr (we - don't know), so we have to pass those on as potentially - fatal to the current script. - */ - CWAL_SWITCH_FALL_THROUGH; - case CWAL_RC_EXCEPTION: - xrv = cwal_exception_get(se->e); - assert(xrv && - "Downstream code failed to call cwal_exception_set()."); - /* assert(0==cwal_value_refcount(xrv) && "This is only temporary while debugging something"); */ - /* s2_dump_val(xrv,"exception before rescope"); */ - if(scope){ - assert(scope->parent); - cwal_value_rescope(scope->parent, xrv); - } - cwal_value_ref(xrv); - cwal_exception_set(se->e, 0); - cwal_value_unhand(xrv); - /* s2_dump_val(xrv,"exception after set-exception 0"); */ - assert((cwal_value_scope(xrv) || cwal_value_is_builtin(xrv)) - && "Premature cleanup on isle 7."); - assert(!cwal_exception_get(se->e)); - rc = 0 - /* we will propagate xrv as the non-error result. */; - break; - default: - break; - } - break; - /* end S2_T_KeywordCatch */ - default: - /* Propagate anything else... */ - if(!rc){ - /* we'll allow the NULL==>undefined translation here for - sanity's sake of sanity: eval -> "". An empty string is a - valid script, but results in NULL instead of the - undefined value, and keywords are generally not supposed - to set *rv to NULL. - */ - if(S2_T_OpArrow == modifier && !xrv){ - xrv = cwal_value_undefined(); - } - } - rc = s2_rv_maybe_accept(se, 0, rc, 0); - break; - } - } - if(rc || !rv){ - cwal_value_unref(xrv); - xrv = 0; - }else{ - if(rv) *rv = xrv ? xrv : cwal_value_undefined(); - cwal_value_unhand(xrv); - } - if(scope){ - /* We pushed a scope */ - assert(se->scopes.current->cwalScope == scope); - assert(s2_scope_current(se)->cwalScope == scope); - cwal_scope_pop2(se->e, rc ? 0 : xrv); - assert(!scope->e); - assert(!scope->props); - } - return rc; -} - -int s2_keyword_f_reserved( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - if(se->skipLevel>0){ - /* Workaround: so that if/else/etc blocks which traverse these - don't cry. But then they cry about token ordering. Hmm. - */ - *rv = cwal_value_undefined(); - return 0; - }else{ - return s2_err_ptoker( se, pr, - CWAL_SCR_SYNTAX - /* need for ^^^^ 'catch' to intercept this */, - "'%s' is a reserved keyword.", - kw->word); - } -} - -static int s2_keyword_f_assert( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoken const pos = pr->token; - cwal_value * xrv = 0; - assert(S2_T_KeywordAssert==pos.ttype - || S2_T_KeywordAffirm==pos.ttype); - rc = s2_eval_expr_impl(se, pr, 0, 0, &xrv); - if(rc){ - if(kw){/*avoid unused param warning*/} - assert(!xrv && "expecting NULL result value on error."); - return rc; - } - else if(!xrv){ - s2_ptoker_errtoken_set(pr, &pos); - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected empty expression"); - } - else if(se->skipLevel>0){ - assert(cwal_value_undefined()==xrv); - *rv = cwal_value_undefined(); - return 0; - }else{ - cwal_size_t capLen = 0; - char const * capStr = s2_ptoker_capture_cstr(pr, &capLen); - char const isAssert = (S2_T_KeywordAssert==pos.ttype) ? 1 : 0; - char buul; - cwal_value_ref(xrv); - buul = cwal_value_get_bool(xrv); - cwal_value_unref(xrv); - xrv = 0; - if(!buul){ - if(se->flags.traceAssertions > 1){ - cwal_outputf(se->e, "%s FAILED: %.*s\n", - isAssert ? "ASSERTION" : "AFFIRMATION", - (int)capLen, capStr); - } - s2_ptoker_errtoken_set(pr, &pos); - return isAssert - ? s2_err_ptoker(se, pr, CWAL_RC_ASSERT, - "Assertion failed: %.*s", - (int)capLen, capStr) - : s2_throw_ptoker(se, pr, CWAL_RC_ASSERT, - "Affirmation failed: %.*s", - (int)capLen, capStr); - }else{ - if(isAssert) ++se->metrics.assertionCount; - if(se->flags.traceAssertions>1 - || (isAssert && se->flags.traceAssertions>0)){ -#if 0 - /* testing token-level line/column count. - These are off. */ - cwal_size_t slen = 0; - char const * script = s2_ptoker_name_first(pr, &slen); - int l = 0, c = 0; - /* s2_ptoken_adjusted_lc(pr, &ast, &l, &c); */ - s2_ptoker_count_lines2( pr, &pr->capture.begin, &l, &c ); - cwal_outputf(se->e, "%.*s:%d:%d: %s passed: %.*s\n", - (int)slen, script, l, c, - isAssert ? "Assertion" : "Affirmation", - (int)capLen, capStr); -#else - cwal_outputf(se->e, "%s passed: %.*s\n", - isAssert ? "Assertion" : "Affirmation", - (int)capLen, capStr); -#endif - } - *rv = cwal_value_true(); - return 0; - } - } -} - -int s2_keyword_f_typename( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc; - s2_ptoken const origin = pr->token; - cwal_value * xrv = 0; - assert(S2_T_KeywordTypename==pr->token.ttype); - /* - TODO: - - - peek and see if the next value is-a identifier. If so, use its string value - to resolve - */ - /* Behave as if the keyword wasn't there. */ - rc = s2_eval_expr_impl( se, pr, s2_ttype_op(S2_T_Comma), - S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED, - &xrv ); - /* s2_dump_val(xrv, "TYPENAME result"); */ - if(rc) return rc; - else if(!xrv){ - s2_ptoker_errtoken_set(pr, &origin); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s requires a non-empty expression.", - kw->word); - }else if(se->skipLevel>0){ - assert(cwal_value_undefined()==xrv); - *rv = cwal_value_undefined(); - }else{ - cwal_value * vTn; - cwal_value_ref(xrv); - vTn = cwal_prop_get_v(xrv, se->cache.keyTypename) - /* If interested, see the notes in s2_keyword_f_typeinfo(), - about TYPEINFO_NAME, for why we first look for this - property. */; - if(!vTn){ - cwal_size_t tlen = 0; - char const * tn; - tn = cwal_value_type_name2( xrv, &tlen ); - if(!tn){ - assert(!"Can't really happen, can it?"); - tn = cwal_type_id_name(CWAL_TYPE_UNDEF); - tlen = cwal_strlen(tn); - } - vTn = cwal_new_string_value(se->e, tn, tlen); - if(!vTn) rc = CWAL_RC_OOM; - } - cwal_value_ref(vTn); - cwal_value_unref(xrv); - if(rc){ - assert(NULL == vTn); - cwal_value_unref(vTn) /* noop, but for symmetry */; - }else{ - assert(NULL != vTn); - *rv = vTn; - cwal_value_unhand(vTn); - } - } - return rc; -} - -int s2_keyword_f_refcount( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ -#if 1 - S2_UNUSED_ARG(rv); - return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "'%s' is so gauche. Use pragma(refcount ...) " - "instead!", kw->word ); -#else - int rc; - cwal_value * xrv = 0; - s2_ptoken const origin = pr->token; - rc = s2_eval_expr_impl( se, pr, s2_ttype_op(S2_T_Comma), - /* S2_EVAL_NO_SKIP_FIRST_EOL */ 0, &xrv ); - if(rc){ - assert(!xrv); - }else{ - if(!xrv){ - s2_ptoker_errtoken_set(pr, &origin); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s requires an RHS expression", - kw->word); - }else if(se->skipLevel>0){ - assert(cwal_value_undefined()==xrv); - *rv = cwal_value_undefined(); - }else{ - *rv = cwal_new_integer(se->e, (cwal_int_t)cwal_value_refcount(xrv)); - cwal_refunref(xrv); - rc = *rv ? 0 : CWAL_RC_OOM; - } - } - return rc; -#endif -} - -int s2_keyword_f_nameof( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - s2_ptoken next = s2_ptoken_empty; - int rc; - assert(S2_T_KeywordNameof==pr->token.ttype); - rc = s2_next_token( se, pr, 0, &next ); - if(rc) return rc; - else if(S2_T_Identifier != next.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s requires an IDENTIFIER argument", - kw->word); - }else if(se->skipLevel>0){ - s2_ptoker_token_set(pr, &next); - *rv = cwal_value_undefined(); - return 0; - }else{ - cwal_value * xrv = 0; - cwal_size_t tlen = s2_ptoken_len(&next); - assert(s2_ptoken_begin(&next)token.ttype) - && "lookahead broken?"); - s2_ptoker_token_set(pr, &next); - *rv = cwal_new_string_value(se->e, s2_ptoken_begin(&next), tlen); - return *rv ? 0 : CWAL_RC_OOM; - } - } -} - -/** - Internal helper for collecting lists of declared variables, namely - var/const and function parameter lists. - - Evaluates a comma-separated list of IDENTIFIER [= EXPR] tokens - from pr until EOX, declaring each one in the current scope. - If isConst, the vars are declared const. - - If argv is not 0 then it is assumed to be the arguments array - passed to a function call, and it is populated with any default - values not accounted for by that array (exception: in skip mode it - is not modified). Default parameter values are not processed for - slots filled by argv. Contrariwise, if the var list contains more - entries than argv, any extras are appended to argv. If rv is not - 0, the last-evaluated value is put in *rv. - -*/ -static int s2_keyword_f_var_impl( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv, - char isConst, - cwal_array * argv){ - s2_ptoken next = s2_ptoken_empty; - s2_ptoken ident = s2_ptoken_empty; - cwal_value * v; - int rc; - char gotComma; - cwal_size_t argPos = 0; - uint16_t declFlags; - s2_op const * pseudoOp = s2_ttype_op(S2_T_Comma); - cwal_size_t argc = argv ? cwal_array_length_get(argv): 0; - - assert(pseudoOp); - next_part: - v = 0; - declFlags = isConst ? CWAL_VAR_F_CONST : 0; - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) return rc; - else if(S2_T_Identifier != pr->token.ttype){ - if(argv && s2_ttype_is_eof(pr->token.ttype)){ - /* empty function argument list */ - return 0; - }else{ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "'%s' list requires an identifier argument, " - "but got token type %d with contents [%.*s].", - kw->word, pr->token.ttype, - (int)s2_ptoken_len(&pr->token), - s2_ptoken_begin(&pr->token)); - } - } - gotComma = 0; - ident = pr->token; - if(!se->skipLevel){ - if(s2_ptoken_keyword2(se, &ident)){ - /* Confirm that it's not a keyword, as the user would not be - able to use the var/param properly. Throw vs error: - s2_err_ptoker() is ostensibly fatal to a script, but - subscripts will, on error propagation, convert this to - an exception, so a sub-/imported script won't kill a - top-level script this way. Hmmm... if this is an Error - then it's difficult to test (our current unit tests - will abort when they hit it). We'll make this an - Exception for the time being: - */ - return s2_throw_ptoker(se, pr, CWAL_RC_ALREADY_EXISTS, - "Cannot use keyword '%.*s' " - "as a %s name.", - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident), - argv ? "parameter" : "variable"); - - }else if(cwal_scope_search(/*&se->currentScope->scope - ^^^^ - cwal_function_call() pushes a scope - which s2 doesn't have an s2_scope - for, so we need to look in - se->e->current instead, else an - overloaded operator's declared vars - can report a collision where there is - really none. Been there, done that. - - 20191117: that "shouldn't" be an - issue since the cwal API added the - scope-push/pop hooks, as the scopes - are now in sync. This setup continues - to work, though, so we'll leave it as - is. - */ - se->e->current, - 0, s2_ptoken_begin(&ident), - s2_ptoken_len(&ident), - 0)){ - /* Check for dupe symbols before evaluating the RHS. This - "need" was uncovered by the sqlite3 module. It's a - small performance hit, though. */ - return s2_throw_ptoker(se, pr, CWAL_RC_ALREADY_EXISTS, - "Symbol '%.*s' is already declared " - "in the current scope.", - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - } - } - rc = s2_next_token( se, pr, 0, &next); - if(rc) return rc; - switch(next.ttype){ - case S2_T_Comma: - gotComma = 1; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_EOF: - case S2_T_EOX: - v = isConst ? 0 : cwal_value_undefined(); - s2_ptoker_token_set( pr, &next ) /* consume it */; - break; - case S2_T_OpColonEqual: - if(argv){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "The := operator is not permitted on function " - "parameters because of syntactic inconsistencies " - "with regards to parameters with resp. without " - "default values."); - } - declFlags = CWAL_VAR_F_CONST; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_OpAssign: - s2_ptoker_token_set(pr, &next); - if(argv && argPosword, (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - }else{ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Empty expression is not permitted " - "after %s assignment.", - argv - ? "parameter" - : (isConst?"const":"var")); - } - } - break; - default: - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected token '%.*s' in %s decl '%.*s'.", - (int)s2_ptoken_len(&next), - s2_ptoken_begin(&next), - argv - ? "parameter" - : (isConst?"const":"var"), - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - } - if(!v){ - if(isConst){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s decl '%.*s' requires a value.", - kw->word, (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - } - v = cwal_value_undefined(); - } - - if(!se->skipLevel){ - cwal_size_t const tlen = s2_ptoken_len(&ident); - /* s2_dump_val(v, "var decl before"); */ - if(argv && argPosword, (int)tlen, - s2_ptoken_begin(&ident))); */ - /* s2_dump_val(v, "var decl after"); */ - ++argPos; - } - if(!s2_ptoker_is_eof(pr) && !gotComma){ - /* Check for a follow-up token */ - if(s2_ptoker_next_is_ttype(se, pr, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){ - gotComma = 1; - } - } - assert(!rc); - rc = s2_check_interrupted(se, rc); - if(!rc){ - if(gotComma) goto next_part; - assert(v); - if(rv) *rv = v; - } - return rc; -} - - -int s2_keyword_f_var( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - char const isConst = (S2_T_KeywordConst==pr->token.ttype) ? 1 : 0; - return s2_keyword_f_var_impl( kw, se, pr, rv, isConst, 0 ); -} - -int s2_keyword_f_unset( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - s2_ptoken next = s2_ptoken_empty; - s2_ptoken origin = s2_ptoken_empty; - s2_ptoken ident = s2_ptoken_empty; - int rc; - char gotComma; - int identLen; - assert(S2_T_KeywordUnset==pr->token.ttype); - next_part: - origin = pr->token; - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) return rc; - else if(S2_T_Identifier != pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s requires an IDENTIFIER argument", - kw->word); - } - gotComma = 0; - ident = pr->token; - identLen = (int)s2_ptoken_len(&ident); - /* Check next token first to avoid unsetting the obj part of obj.prop. */ - rc = s2_next_token( se, pr, 0, &next); - if(rc) return rc; - switch(next.ttype){ - case S2_T_Comma: - gotComma = 1; - s2_ptoker_token_set( pr, &next ) /* consume it */; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_EOF: /* e.g. typing "unset x" in s2sh */ - case S2_T_EOX:{ - if(!se->skipLevel){ - cwal_kvp * kvp = cwal_scope_search_kvp(se->e->current, 0, - s2_ptoken_begin(&ident), - (cwal_size_t)identLen, - 0); - if(!kvp){ - return s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, - "%s could not resolve identifier '%.*s' " - "in the local scope.", - kw->word, identLen, s2_ptoken_begin(&ident)); - }else if(CWAL_VAR_F_CONST & cwal_kvp_flags(kvp)){ - return s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, - "Cannot %s const '%.*s'.", - kw->word, identLen, s2_ptoken_begin(&ident)); - } - rc = s2_handle_set_result(se, pr, - s2_set( se, 0, s2_ptoken_begin(&ident), - (cwal_size_t)identLen, 0)); - /* MARKER(("Unsetting ident [%.*s] rc=%d\n", - (int)s2_ptoken_len(&ident), s2_ptoken_begin(&ident), rc)); */ - if(rc && !cwal_exception_get(se->e)){ - rc = s2_throw_err_ptoker( se, pr ); - } - if(rc) return rc; - } - if(gotComma) goto next_part; - break; - } -#if 0 - case S2_T_OpArrow: -#endif - case S2_T_OpDot: - case S2_T_BraceGroup:{ - s2_op const * pseudoOp = s2_ttype_op(S2_T_Comma); - assert(pseudoOp); - /* MARKER(("Unsetting a property?\n")); */ - /* - Treat this as a property unset: - - OpDot | BraceGroup: eval expr with Comma - precedence; get se->dotOp.lhs and se->dotOp.key; - - That's full of weird corner cases, though: - - unset a.b = c.d - - 20191117: that ends up unsetting (without error) c.d because - se->dotOp refers, at the point where the unset is resolved, to - c.d instead of a.b. Hmmm. Maybe we need a new - S2_EVAL_FOR_UNSET flag, which only allows identifiers and - dot-op, throwing if any other operators are seen - access... except that in order to know whether we're doing a - dot-op, we first have to eval the LHS of that dot op - (e.g. unset (someFuncCall()).foo is/needs to be legal). Hmmm. - The only solution i currently see is to skip-mode the first - eval phase, store its source position, then check for a - dot. If it's a dot, go back and eval that LHS (whatever it - is), else check if the LHS is an identifier, and fail if it's - not. Hmmm. - - So far such a case has never happened in practice, so priority - for "fixing" it is low, but it's still an unsightly behaviour. - */ - s2_ptoker_token_set(pr, &origin); - rc = s2_eval_expr_impl(se, pr, pseudoOp, - S2_EVAL_RETAIN_DOTOP_STATE, 0); - if(rc) return rc; - else if(!se->skipLevel){ - if(!se->dotOp.lhs || !se->dotOp.key){ - s2_ptoker_errtoken_set(pr, &ident); - return s2_throw_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Illegal RHS for %s operation.", - kw->word); - } - cwal_value_ref(se->dotOp.lhs); - cwal_value_ref(se->dotOp.key); - /* Do we want to support: unset hash # key? */ - rc = s2_handle_set_result(se, pr, - s2_set_v( se, se->dotOp.lhs, se->dotOp.key, 0 )); - cwal_value_unhand(se->dotOp.lhs); - cwal_value_unhand(se->dotOp.key); - s2_dotop_state(se, 0, 0, 0); - if(rc) return rc; - } - if(s2_ptoker_next_is_ttype(se, pr, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){ - goto next_part; - } - break; - } - default: - s2_ptoker_errtoken_set(pr, &next); - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected token type %s in %s.", - s2_ttype_cstr(next.ttype), - kw->word - /*(int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)*/); - } - assert(0==rc); - *rv = cwal_value_undefined(); - return 0; -} - -int s2_keyword_f_if( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoken next = s2_ptoken_empty; - s2_ptoken tCondition = s2_ptoken_empty; - s2_ptoken tBody = s2_ptoken_empty; - cwal_scope _SCOPE = cwal_scope_empty; - cwal_scope * scope = 0; - cwal_value * xrv = 0; - char buul = 0; - char finalElse = 0; - char hasTrued = 0; - int runCount = 0; - char bodyIsExpr = 0; - next_if: - ++runCount; - /* Get the condition part... */ - rc = s2_next_token(se, pr, 0, 0); - if(S2_T_ParenGroup != pr->token.ttype){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after '%s'.", - kw->word); - goto end; - } - tCondition = pr->token; - - /* Get the body part... */ - rc = s2_next_token(se, pr, 0, 0); - if(rc) goto end; - - capture_body: - bodyIsExpr = 0; - tBody = pr->token; - while(S2_T_SquigglyBlock != tBody.ttype - && S2_T_Heredoc/*historical behaviour*/ != tBody.ttype){ - if(s2_ttype_is_eox(tBody.ttype)){ - /* special case: empty body. */ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Empty non-block expression is not allowed."); - goto end; - } - else{ - /* Check for an expression... - - Remember that tCondition.end is a bit screwy because it's based - on the s2_ptoken::adjEnd of its input token. - */ - if(!finalElse){ - s2_ptoker_token_set(pr, &tCondition); - }/* else the token is already placed where it needs to be */ - /* MARKER(("pr->token=%.*s...\n",10, s2_ptoken_begin(&pr->token))); */ - if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))){ - goto end; - }else if(s2_eval_is_eox(se, pr) - /* is an expression ending with an EOX */){ - /* FIXME: won't work as-is with #compiled tokens */ - tBody = pr->capture.begin; - s2_ptoken_end_set(&tBody, s2_ptoken_begin(&pr->capture.end)); - /*MARKER(("CAPTURE: %.*s\n", (int)s2_ptoken_len(&tBody), - s2_ptoken_begin(&tBody)));*/ - bodyIsExpr = 1; - break; - }else{ - MARKER(("???\n")); - assert(!"what happens here?"); - } - } - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting {...} or a single " - "expression after %s(...).", - kw->word); - goto end; - } - if(finalElse){ - goto the_body; - } - - if(1==runCount && !se->skipLevel){ - assert(!scope); - scope = &_SCOPE; - rc = cwal_scope_push2(se->e, scope); - if(rc) return rc; - } - - /* Eval the condition... */ - s2_ptoker_token_set(pr, &tCondition); - /* Reminder: we need to eval-sub, even in skip mode, - for the corner case of an empty (). */ - rc = s2_eval_current_token( se, pr, 0, 0, &xrv ); - if(rc) goto end; - else if(!xrv){ - rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Empty '%s' condition is not allowed.", - kw->word); - goto end; - } - buul = (se->skipLevel>0) ? 0 : cwal_value_get_bool(xrv); - xrv = 0; - the_body: - /*MARKER(("tBody: %.*s\n", (int)s2_ptoken_len(&tBody), - s2_ptoken_begin(&tBody)));*/ - - /* assert(S2_T_SquigglyBlock==pr->token.ttype || bodyIsExpr); */ - if(!finalElse && (!buul || hasTrued || se->skipLevel>0)){ - s2_ptoker_token_set(pr, &tBody); - if(bodyIsExpr){ - s2_next_token(se, pr, 0, 0) - /* Slurp up the semicolon */; - assert(s2_ttype_is_eox(pr->token.ttype)); - } - goto check_else; - } - - /* - Arguably slightly broken, but really not: - - 1 ? if(1){ 1; } else 1 : 1 - ----------------------^^^ - - ==> Unexpected ':' token (no X?Y:Z op in progress). - - The problem is one of scope. The 'else' is happening in a - different scope than the '?', and ternary state explicitly does - not span scopes (it's saved/reset/restored when pushing/popping - scopes). That construct can be resolved by using a {block} or - explicitly ending the else's expression with a semicolon. - */ - s2_ptoker_token_set(pr, &tBody); - rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0); - if(!rc && bodyIsExpr){ - /* - var a = 0; - if(0) throw __FLC; else if(1) a = 1; else throw __FLC; - -------------------------------------^^^^ - - Could not resolve identifier 'else' - - tBody = "a = 1". rc from this eval is 0. - capture after eval of the 2nd 'if' is "a = 1". - pr is not at EOX afterwards: current token is S2_T_INVALID - (because tBody is from the capture, not a valid token type, - and pr is not advanced by s2_eval_current_token()). - - The problem is that the next token in pr, in that case, is an - EOX, which s2_eval_current_token() is not getting/consuming, so - we are seeing is as the end of this keyword's work and we're - propagating back up to the eval loop, which then picks up the - 'else' and thinks it's an unknown identifier. - - As a workaround, we'll check the next token and, if it's an EOX, - consume it so that the next/upcoming check of an 'else' part can - DTRT. If it's not an EOX, we'll put it back and let the next part - deal with it. - */ - rc = s2_next_token(se, pr, 0, &next); - if(!rc){ - if(s2_ttype_is_eox(next.ttype)) s2_ptoker_token_set(pr, &next) /* consume it */; - else s2_ptoker_next_token_set(pr, &next) /* give it back for the next caller */; - } - } - if(rc) goto end; - else if(!hasTrued && !finalElse){ - hasTrued = 1; - ++se->skipLevel; - } - - check_else: - if(finalElse){ - goto end; - } - next = s2_ptoken_empty; - rc = s2_next_token(se, pr, 0, &next); - - /*MARKER(("bodyIsExpr=%d\n", bodyIsExpr));*/ - /*MARKER(("next: <<<%.*s>>>\n", (int)s2_ptoken_len(&next), - s2_ptoken_begin(&next)));*/ -#define TOK_IS_ELSE(TOKP) (4==(int)(s2_ptoken_len(TOKP)) \ - && 0==memcmp(s2_ptoken_begin(TOKP),"else",4)) - bodyIsExpr = 0; - /*MARKER(("after IF: <<<%.*s>>>\n", (int)s2_ptoken_len(&next), - s2_ptoken_begin(&next)));*/ - if(!rc && TOK_IS_ELSE(&next)){ - s2_keyword const * ifCheck; - s2_ptoken const theElse = next; - s2_ptoker_token_set(pr, &next) /*consume it*/; - - next = s2_ptoken_empty; - rc = s2_next_token(se, pr, 0, &next); - if(rc) goto end; - else if((ifCheck=s2_ptoken_keyword(&next)) - && (S2_T_KeywordIf==ifCheck->id)){ - s2_ptoker_token_set(pr, &next) /*consume it*/; - goto next_if; - }else{ - /*MARKER(("final else: %s <<<%.*s>>>\n", - s2_ttype_cstr(next.ttype), - (int)s2_ptoken_len(&next), s2_ptoken_begin(&next)));*/ - if(S2_T_SquigglyBlock==next.ttype - || S2_T_Heredoc==next.ttype){ - s2_ptoker_token_set(pr, &next) /* make sure we've got the right token type */; - }else{ - s2_ptoker_token_set(pr, &theElse); - } - finalElse = 1; - goto capture_body; - } - } -#undef TOK_IS_ELSE - end: - if(hasTrued) --se->skipLevel; - if(scope){ - assert(scope->parent); - cwal_scope_pop(se->e); - } - if(!rc){ - *rv = se->skipLevel - ? cwal_value_undefined() - : (hasTrued - ? cwal_value_true() - : cwal_value_false()) - ; - } - /*MARKER(("tokenizer at: %.*s\n", - (int)(s2_ptoker_end(pr) - s2_ptoken_end(&pr->token)), -s2_ptoken_end(&pr->token)));*/ - return rc; -} - -int s2_keyword_f_continue( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - if(kw){/*avoid unused param warning*/} - return 0; - }else{ - return s2_err_ptoker(se, pr, CWAL_RC_CONTINUE, 0); - } -} - -/** - Internal code consolidator for do/while/for/foreach loops. - - kw is the keyword on whose behalf this is working. - - Preconditions: pr's must be set up such that the next call to - s2_next_token() (which this function makes) will fetch what we - believe/hope is the loop body. This function determines whether - it's a {block} or a single expression and sets *tBody to contain - the block/expression contents. - - *bodyIsExpr is assigned 0 if tBody represents a {block}, else it's - set to non-0. - - All loop types except for foreach() allow an empty body but require - that non-{} expression bodies be explicitly EOX-terminated or end - on an implicit EOX (e.g. the end of a block construct). - - Returns 0 on success. Any error must be propagated back to the - loop's caller and the state of pr, tBody, and bodyIsExpr is - unspecified. -*/ -static int s2_keyword_loop_get_body( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, s2_ptoken * tBody, - char * bodyIsExpr){ - /*s2_ptoken const origin = pr->token;*/ - char const allowEmptyBody = S2_T_KeywordForEach==kw->id ? 0 : 1; - int rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - *tBody = pr->token; - if(S2_T_SquigglyBlock == tBody->ttype - || S2_T_Heredoc/*historical behaviour*/ == tBody->ttype){ - if(!allowEmptyBody && !s2_ptoken_has_content(tBody)){ - goto empty_body; - }else{ - *bodyIsExpr = 0; - return 0; - } - } - else if(s2_ttype_is_eox(tBody->ttype)){ - /* special case: empty body. */ - if(allowEmptyBody){ - /* all but foreach() allow an empty body but require that it ends with - an explicit EOX. */ - *bodyIsExpr = 1; - return 0; - }else{ - goto empty_body; - /* foreach() disallows an empty body because it'd do nothing - at all except iterate (no visible side-effects). */ - } - }else{ - /* Check for an expression... */ - /* MARKER(("pr->token=[%.*s]\n",(int)s2_ptoken_len(&pr->token), - s2_ptoken_begin(&pr->token))); */ - s2_ptoker_next_token_set(pr, tBody); - if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))) return rc; - else if(s2_eval_is_eox(se, pr) - /* is an expression ending with an EOX */){ - *tBody = pr->capture.begin; - s2_ptoken_end_set(tBody, s2_ptoken_begin(&pr->capture.end)) - /* 20200107 FIXME: this can't work with #compiled tokens. */; - *bodyIsExpr = 1; -#if 0 - { - cwal_size_t n = 0; - char const * str = s2_ptoken_cstr(tBody, &n); - MARKER(("expr tBody = %.*s\n", (int)n, str)); - str = s2_ptoker_capture_cstr(pr, &n); - MARKER(("pr->capture = %.*s\n", (int)n, str)); - } -#endif - return 0; - } - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting {...} or a single " - "expression after %s(...).", - kw->word); - } - empty_body: - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s may not have an empty body.", - kw->word); -} - - -int s2_keyword_f_while( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoken tCondition = s2_ptoken_empty; - s2_ptoken tBody = s2_ptoken_empty; - cwal_scope scope = cwal_scope_empty; - cwal_value * xrv = 0; - char bodyIsExpr = 0; - - /* Get the condition part... */ - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else if(S2_T_ParenGroup!=pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after '%s'.", - kw->word); - } - tCondition = pr->token; - - /* Get the body part... */ - rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr); - /* Run the tBody while the tCondition evaluates to truthy... */ - while(!rc){ - char buul = 0; - if(scope.parent){ - cwal_scope_pop(se->e); - assert(!scope.parent); - } - rc = cwal_scope_push2(se->e, &scope); - if(rc) break; - assert(scope.parent); - s2_ptoker_token_set(pr, &tCondition); - assert(!xrv); - rc = s2_eval_current_token( se, pr, 0, 0, &xrv ); - if(rc) break; - else if(!xrv){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Invalid empty expression in %s().", - kw->word); - break; - } - buul = cwal_value_get_bool(xrv); - /* ref()/unref() combo kills temporaries, regardless of - their owning scope, but leaves others effectively - untouched. This is the same hack the for() loop uses - - see explanation in s2_keyword_f_for(). */ - cwal_refunref(xrv); - xrv = 0; - if(!buul){ - s2_ptoker_token_set(pr, &tBody) - /* consume the body. err... you know what i mean. */; - break; - } - else{ - int doBreak = 0; - s2_ptoker_token_set(pr, &tBody) /* set body up for eval */; - if(se->skipLevel>0){ - /* we only need to get this far to ensure - syntax rules are met. */ - break; - } - s2_ptoker_token_set(pr, &tBody); - rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0); - switch(rc){ - case CWAL_RC_BREAK: - doBreak = rc; - xrv = s2_propagating_take(se); - assert(xrv); - assert(scope.parent); - cwal_value_rescope(scope.parent, xrv); - rc = 0; - cwal_value_ref(xrv) /* required when xrv is created in tCondition! */; - s2_engine_err_reset(se); - break; - case CWAL_RC_CONTINUE: - rc = 0; - s2_engine_err_reset(se); - break; - default: - break; - } - if(doBreak) break; - }/* end body eval */ - }/* end while(!rc) */ - - if(scope.parent){ - cwal_scope_pop2(se->e, (rc && !se->skipLevel) ? 0 : xrv); - assert(!scope.parent); - } - if(rc){ - cwal_value_unref(xrv); - }else{ - cwal_value_unhand(xrv); - *rv = se->skipLevel - ? cwal_value_undefined() - : (xrv ? xrv : cwal_value_undefined()); - } - return rc; -} - -int s2_keyword_f_dowhile( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoken tCondition = s2_ptoken_empty; - s2_ptoken tBody = s2_ptoken_empty; - /* s2_ptoken const tOrigin = pr->token; */ - cwal_scope scope = cwal_scope_empty; - cwal_value * xrv = 0; - char bodyIsExpr = 0; - /* Get the body part... */ - rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr); - if(rc) return rc; - /* Get the while(condition) parts... */ - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else{ - s2_keyword const * kWhile = s2_ptoken_keyword(&pr->token); - if(!kWhile){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting while(...) after '%s' body.", - kw->word); - } - } - /* Get the (condition) part... */ - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else if(S2_T_ParenGroup != pr->token.ttype){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after %s...while.", - kw->word); - } - tCondition = pr->token; - if(se->skipLevel){ - *rv = cwal_value_undefined(); - return 0; - } - /* Run tBody while tCondition evaluates to truthy... */ - do{ - char doBreak = 0; - if(scope.parent){ - cwal_scope_pop(se->e); - assert(!scope.parent); - } - rc = cwal_scope_push2(se->e, &scope); - if(rc) break; - assert(scope.parent); - - /* Run the body... */ - xrv = 0; - s2_ptoker_token_set(pr, &tBody) /* set body up for eval */; - rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0); - switch(rc){ - case CWAL_RC_BREAK: - xrv = s2_propagating_take(se); - assert(xrv && "Expecting 'break' keyword to set this."); - assert(scope.parent); - cwal_value_ref(xrv); - doBreak = 1; - CWAL_SWITCH_FALL_THROUGH; - case CWAL_RC_CONTINUE: - rc = 0; - s2_engine_err_reset(se); - break; - default: - break /* other, more serious, error, - or a 'return' or similar */; - } - if(rc || doBreak) break; - s2_ptoker_token_set(pr, &tCondition); - xrv = 0; - rc = s2_eval_current_token( se, pr, 0, 0, &xrv ); - if(rc){ - xrv = 0 /* just in case, so we don't misbehave below */; - break; - }else if(!xrv){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Invalid empty expression in %s...while().", - kw->word); - break; - }else if(!cwal_value_get_bool(xrv)){ - /* Condition is falsy, end the loop. */ - xrv = 0; - break; - }else{ - /* Condition is truthy, so continue. ref/unref combo cleans up - temps, regardless of owning scope, while not cleaning up - non-temps.*/ - cwal_refunref(xrv); - xrv = 0; - } - }while(!rc); - - if(scope.parent){ - cwal_scope_pop2(se->e, rc ? 0 : xrv); - assert(!scope.parent); - } - - if(rc){ - cwal_value_unref(xrv); - }else{ - cwal_value_unhand(xrv); - s2_ptoker_token_set(pr, &tCondition) /* consume the final (condition) part */; - *rv = se->skipLevel - ? cwal_value_undefined() - : (xrv ? xrv : cwal_value_undefined()); - } - return rc; -} - - -int s2_keyword_f_for( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoken tCondition = s2_ptoken_empty; - s2_ptoken tPre = s2_ptoken_empty; - s2_ptoken tPost = s2_ptoken_empty; - s2_ptoken tBody = s2_ptoken_empty; - s2_ptoker prParens = s2_ptoker_empty; - cwal_scope scopeOut = cwal_scope_empty; - cwal_scope scopeIn = cwal_scope_empty; - cwal_value * xrv = 0; - char bodyIsExpr = 0; - unsigned int runCount = 0; - /* Get the condition part... */ - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else if(S2_T_ParenGroup!=pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after '%s'.", - kw->word); - } - rc = s2_ptoker_sub_from_toker(pr, &prParens); - if(rc) return rc; - - /* Get the body part... */ - rc = s2_keyword_loop_get_body( kw, se, pr, &tBody, &bodyIsExpr ); - if(rc) goto end; - - /* Capture the initial expr of (x;y;z) */ - tPre = prParens.token; - rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0); - if(rc) goto end; - if( S2_T_EOX != prParens.token.ttype){ - s2_ptoker_errtoken_set(pr, &prParens.token); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting ';' at start of %s(...), " - "but got %s.", - kw->word, - s2_ttype_cstr(prParens.token.ttype)); - goto end; - } - /* reminder to self: tPre, being assigned before prParens' first token - is fetched, has no 'end'. We probably don't need it. */ - - /* Capture the condition expr of (x;y;z) */ - tCondition = prParens.token; - rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0); - if(rc) goto end; - if( S2_T_EOX != prParens.token.ttype){ - s2_ptoker_errtoken_set(pr, &prParens.token); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting ';' after condition part " - "of %s(...), but got %s.", - kw->word, - s2_ttype_cstr(prParens.token.ttype)); - goto end; - } - - /* Capture the post-loop expr of (x;y;z) and make sure it's sound */ - tPost = prParens.token; - rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0); - if(rc) goto end; - if( !s2_ttype_is_eof(prParens.token.ttype)){ - s2_ptoker_errtoken_set(pr, &prParens.token); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Extra junk after post-loop part of " - "of %s(...): got a %s.", - kw->word, - s2_ttype_cstr(prParens.token.ttype)); - goto end; - } - - /* Now we know that the ( pre; condition; post ) part is well-formed */ - - if(se->skipLevel>0){ - goto end; - } - - /* - We need two scopes here if we want to support var decls - in both the prefix part and loop body. The first scope - opens just before the (...) is run, the second is pushed - before each loop iteration, before the condition is run, - and popped after the post-loop is run. - */ - /* oldRefCount = s2_scope_refs_count(se); */ - rc = cwal_scope_push2(se->e, &scopeOut); - if(rc) goto end; - /* No RETURNs as of here... */ - - /* Run the prefix part */ - s2_ptoker_token_set( &prParens, &tPre ); - rc = s2_eval_expr_impl( se, &prParens, 0, 0, 0 ); - if(rc) goto end; - - /* Run the tBody while the tCondition evaluates - to truthy... */ - while( !rc ){ - /* cwal_value * postRv = 0 */ /* a cleanup hack */ - cwal_value * condRv = 0; - char condBuul; - if(scopeIn.parent){ - /* Inner scope needs to be re-initialized on each - iter. - */ - cwal_scope_pop(se->e); - assert(!scopeIn.parent); - } - assert(&scopeOut == se->scopes.current->cwalScope); - if(0==(++runCount % 5 /* # arbitrarily chosen, the point being - only to not sweep on every iteration - (would be overkill).*/)){ - s2_engine_sweep(se) - /* Outer scope: we need this to clean up overwritten loop vars - declared in this scope, though i'm not sure why refcounting - isn't doing it for us? Simple loop/metrics tests imply - that it's not happening, anyway, or that refs are being - held elsewhere. Specifically, it's easy to see with: - - var y = 100; - for(var x = 0; xe, &scopeIn); - if(rc) break; - assert(scopeIn.parent) - - /* Check the condition part */; - prParens.token = tCondition; - rc = s2_eval_expr_impl( se, &prParens, 0, 0, &condRv ); - if(rc) break; - condBuul = condRv /* Treat empty expr as true here */ - ? cwal_value_get_bool(condRv) - : 1; - if(condRv){ - cwal_refunref(condRv); - condRv = 0; - } - if(!condBuul) break; - - /* Run the body ... */; - xrv = 0; - s2_ptoker_token_set(pr, &tBody); - rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0); - if(rc){ - if(CWAL_RC_BREAK==rc){ - /* fall through */ - } - else if(CWAL_RC_CONTINUE==rc){ - rc = 0; - s2_engine_err_reset(se); - } - else break; - } - /* - Run the post part. By an accident of design, this runs while the - per-iteration scope is active, meaning it can reference vars - declared there. Feature or bug? - */ - if(!rc){ - prParens.token = tPost; - rc = s2_eval_expr_impl( se, &prParens, 0, 0, 0 /* &postRv */ ); - } - if(CWAL_RC_BREAK==rc){ - /* - There's little reason not to allow the tPost part to 'break' - out. Unconventional, yes, but so are keywords which resolve - to values. - */ - xrv = s2_propagating_take(se); - assert(xrv && "else misuse of CWAL_RC_BREAK."); - assert(scopeOut.parent); - cwal_value_rescope(scopeOut.parent, xrv); - rc = 0; - cwal_value_ref(xrv); - s2_engine_err_reset(se); - break; - } -#if 0 - /* 20160206: doesn't seem to be needed anymore. Tested with the - example case described below and everything was cleaned up - optimally. */ - if( postRv ){ - /* What this does: keeps lower-scope temporaries from - surviving until the end of the loop: - - Without this hack: - - s2> var a=[100] - result: array@0x104ca20[scope=#1@0x7fffdb851260 ref#=1] ==> [100] - s2> for(a.0 = 100; a.0 > 0; a.0-- ); - MARKER: s2.c:264:s2_engine_sweep(): Swept up 99 value(s) in vacuum mode - - With this hack: no post-loop sweepup of those temps because - they're cleaned up here. This only works (has a benefit) for - the common case (a single var gets modified), not for a - series of them. The comma op has a similar hack which cleans up - the LHS side(s) of a comma-separated list of expressions. - */ - cwal_refunref(postRv); - } -#endif - } - - end: - if(scopeIn.parent){ - assert(&scopeIn == se->scopes.current->cwalScope); - cwal_scope_pop(se->e); - assert(!scopeIn.parent); - } - if(scopeOut.parent){ - assert(&scopeOut == se->scopes.current->cwalScope); - cwal_scope_pop(se->e); - assert(!scopeOut.parent); - } - if(!rc){ - s2_ptoker_token_set(pr, &tBody); - cwal_value_unhand(xrv); - *rv = se->skipLevel - ? cwal_value_undefined() - : (xrv ? xrv : cwal_value_undefined()); - }else{ - cwal_value_unref(xrv); - } - s2_ptoker_finalize( &prParens ); - return rc; -} - - -int s2_eval_filename( s2_engine * se, char pushScope, - char const * fname, - cwal_int_t fnlen, - cwal_value ** rv ){ - int rc; - cwal_buffer buf = cwal_buffer_empty; - cwal_value * xrv = 0; - cwal_scope _SCOPE = cwal_scope_empty; - cwal_scope * scope = pushScope ? &_SCOPE : 0; - cwal_size_t const nFname = (fnlen<0) ? cwal_strlen(fname) : (cwal_size_t)fnlen; - if(!se || !fname) return CWAL_RC_MISUSE; - else if( !*fname || !nFname) return CWAL_RC_RANGE; - if(rv) *rv = 0; -#if defined(S2_OS_UNIX) - if(nFname>1 || '-'!=*fname - /* ^^^ upcoming call will interpret that as stdin, so skip stat() */){ - /* - Check if fname is a directory. Trying to eval a dir entry will - lead to (in my experience) confusing CWAL_RC_OOM error (though - other weird errors are also possible). We only have code for - doing this check on platforms which support stat(). We could - arguably also fail for other device types, e.g. BLOCK, but we'll - let those stay for now because they haven't caused any grief in - practice. - */ - s2_fstat_t fst = s2_fstat_t_empty; - rc = s2_fstat( fname, nFname, &fst, 1 ); - if(CWAL_RC_UNSUPPORTED==rc){ - /* Assume that stat() is not available in this build, so go - ahead and try to read the given filename without stat()'ing - it first. This is needed for non-stat()-aware builds to be - able to eval any external scripts.*/ - rc = 0; - /* fall through... */ - } - else if(rc){ - return s2_engine_err_set(se, rc, "stat(\"%.*s\") failed.", - (int)nFname, fname); - } - else if(S2_FSTAT_TYPE_DIR == fst.type){ - return s2_engine_err_set(se, CWAL_RC_TYPE, - "Cannot eval a directory."); - } - } -#endif - rc = cwal_buffer_fill_from_filename2( se->e, &buf, fname, nFname ); - if(rc){ - rc = s2_engine_err_set(se, rc, "%s: %s", - buf.used ? "Could not open" : "Could not read", - fname); - }else{ - if(scope){ - rc = cwal_scope_push2(se->e, scope); - if(!rc){ - assert(0==se->scopes.current->sguard.sweep); - assert(0==se->scopes.current->sguard.vacuum); - } - } - if(!rc){ - s2_ptoker pr = s2_ptoker_empty; - rc = s2_ptoker_init_v2( se->e, &pr, (char const *)buf.mem, - (cwal_int_t)buf.used, 0 ); - if(!rc){ - pr.name = fname; - rc = s2_eval_ptoker( se, &pr, 0, rv ? &xrv : 0 ); - } - s2_ptoker_finalize(&pr); - switch(rc){ - case CWAL_RC_INTERRUPTED: - case CWAL_RC_EXCEPTION: - case CWAL_RC_OOM: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_ASSERT: - break; - case CWAL_RC_RETURN: - assert(!"This cannot happen anymore: s2_eval_ptoker() will catch this."); - rc = 0; - xrv = s2_propagating_take(se); - /* s2_engine_err_reset(se); */ - CWAL_SWITCH_FALL_THROUGH; - case 0: - if(scope && xrv && rv) cwal_value_rescope(scope->parent, xrv); - if(rv) *rv = xrv; - break; - default: - if(s2__err(se).code){ - /* Convert non-exception errors to exceptions so that - eval'ing a sub-script does not kill the top-level - script. - */ - rc = s2_throw_err(se, 0, 0, 0, 0); - } - break; - } - } - if(scope && scope->parent){ - cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0)); - } - } - cwal_buffer_clear(se->e, &buf); - /* MARKER(("se->err.script=%.*s\n", (int)se->err.script.used, (char const *)se->err.script.mem)); */ - - if(!rc && rv){ - assert((!*rv || cwal_value_scope(*rv) || cwal_value_is_builtin(*rv)) - && "Seems like we've cleaned up a value too early."); - } - return rc; -} - -/** - The cwal_callback_f() impl used by script-side functions - created by s2_keyword_f_function(). -*/ -static int s2_callback_f( cwal_callback_args const * args, cwal_value ** rv ){ - int rc = 0; - s2_func_state * fst = s2_func_state_for_func(args->callee) - /*(s2_func_state *)cwal_args_state(args, &s2_func_state_empty)*/ - ; - s2_ptoker _pr = s2_ptoker_empty; - s2_ptoker * pr = &_pr; - s2_ptoken tBody = s2_ptoken_empty; - s2_ptoken tParams = s2_ptoken_empty ; - s2_engine * se = fst ? s2_engine_from_args(args) : 0; - cwal_array * cbArgv = se ? se->callArgV : 0 - /* The array form of args->argv */ - ; - char const * src = 0; - cwal_size_t srcLen = 0; - s2_ptoker const * oldScript = se->currentScript; - s2_func_state const * oldScriptFunc = se->currentScriptFunc; - se->callArgV = 0 - /* Necessary so that this array does not "leak" into certain - nested call constructs. Triggered by calling an overloaded - operator inside a script function - the _first_ set of call - args were wrong (leaked in from a higher call). - */; - - assert(fst); - assert(se); - assert(se->funcStash); - - if((fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS) - && (fst->flags & S2_FUNCSTATE_F_EMPTY_BODY)){ - /* We have neither a body nor params, so let's - go the easy route. - - One might think that we could do this if the body is empty but - the parameter list is not, but default param values can have - side-effects, so we need to evaluate those. We "could" do a - quick scan for a '=', as side effects cannot (legally) happen - without an assignment to a default param value. If one is not - found, we could take the empty-params route. A - micro-optimization, in any case. - */ - /* assert(!fst->lastCallReturned); */ - assert(!fst->keyScriptName); - assert(!fst->vSrc); - *rv = cwal_value_undefined(); - return 0; - } - assert(cbArgv || (fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)); - - assert(fst->keyScriptName); - assert(cwal_value_scope(fst->keyScriptName)||cwal_value_is_builtin(fst->keyScriptName)); - - assert(fst->vSrc); - assert(cwal_value_scope(fst->vSrc) || cwal_value_is_builtin(fst->vSrc)); - - /* As of here, no 'return' - use goto end. */ - src = cwal_value_get_cstr(fst->vSrc, &srcLen); - assert(src); - assert(srcLen>=8); - - rc = s2_ptoker_init_v2( se->e, pr, src, (cwal_int_t)srcLen, 0 ); - if(rc) goto end; - - se->currentScriptFunc = fst; - se->currentScript = pr; - pr->name = cwal_value_get_cstr(fst->keyScriptName, 0); - pr->lineOffset = fst->line; - pr->colOffset = fst->col; - /* MARKER(("line=%d, col=%d\n", pr->lineOffset, pr->colOffset)); */ - /* s2_dump_val(fst->keyScriptName, "fst->keyScriptName"); */ - /* MARKER(("Calling func from script [%s]\n", pr->name)); */ - - /* Skip over keyword */ - rc = s2_next_token( se, pr, 0, 0 ); - /* assert(!rc && "Should have failed at decl time!"); */ - if(rc) goto end /* we know the tokens are valid, but interruption - can trigger this */; - - /* Get the params */ - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) goto end; - else if(S2_T_Identifier==pr->token.ttype){ /* The name part */ - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) goto end; - } - tParams = pr->token; - assert(S2_T_ParenGroup==tParams.ttype); - - /* The body... */ - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) goto end - /* likely an interruption error, not tokenization (which we - know works because it was parsed at decl-time). Yes, it - happened when testing s2_interrupt(). - */; - tBody = pr->token; - if(S2_T_Identifier==tBody.ttype){ - /* Skip over using(...) resp. using{...} */ - assert((5U == s2_ptoken_len(&tBody)) && "Not 'using'?"); - rc = s2_next_token(se, pr, 0, 0); - if(rc) goto end; - if(S2_FUNCSTATE_F_NO_USING_DECL & fst->flags){ - assert(S2_T_OpDot==pr->token.ttype); - rc = s2_next_token(se, pr, 0, 0); - if(rc) goto end; - } - assert(S2_T_ParenGroup==pr->token.ttype - || S2_T_SquigglyBlock==pr->token.ttype); - rc = s2_next_token(se, pr, 0, 0); - if(rc) goto end; - tBody = pr->token; - } - assert(S2_T_SquigglyBlock==tBody.ttype); - - assert(s2_ptoker_begin(pr) < s2_ptoken_begin(&tParams)); - assert(s2_ptoker_begin(pr) < s2_ptoken_begin(&tBody)); - assert(s2_ptoker_end(pr) > s2_ptoken_end(&tParams)); - assert(s2_ptoker_end(pr) >= s2_ptoken_end(&tBody)); - /* fst->lastCallReturned = 0; */ - if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)){ - /* Collect parameter list */ - s2_ptoker sub = s2_ptoker_empty; - s2_ptoker_token_set(pr, &tParams); - rc = s2_ptoker_sub_from_toker(pr, &sub); - /* MARKER(("PARAMS: %.*s\n", (int)s2_ptoken_len(&tParams), - s2_ptoken_begin(&tParams))); */ - if(!rc){ - rc = s2_keyword_f_var_impl( s2_ttype_keyword(S2_T_KeywordFunction), - se, &sub, 0, 0, cbArgv ); - } - s2_ptoker_finalize(&sub); - /* s2_dump_val(cwal_array_value(cbArgv), "func->argv"); */ - if(rc) goto end; - } - /* MARKER(("CALLING:\n%.*s\n", (int)srcLen, cwal_string_cstr(srcStr))); */ - if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_BODY)){ - assert(!rc); - s2_ptoker_token_set(pr, &tBody); - rc = s2_eval_current_token( se, pr, 0, 0, NULL ); - } - switch(rc){ - case 0: - /* function ended without an explicit return. */ - *rv = cwal_value_undefined(); - break; - case CWAL_RC_RETURN: - rc = 0; - *rv = s2_propagating_take(se); - s2_engine_err_reset(se); - assert(*rv && "s2_propagating_set() must be used with CWAL_RC_RETURN"); - if(!*rv) rc = s2_err_ptoker(se, pr, CWAL_RC_ASSERT, - "internal misuse: only return CWAL_RC_RETURN " - "in conjunction with s2_propagating_set()."); - break; - case CWAL_RC_ASSERT: - case CWAL_RC_CANNOT_HAPPEN: - case CWAL_RC_EXCEPTION: - case CWAL_RC_EXIT: - case CWAL_RC_FATAL: - case CWAL_RC_INTERRUPTED: - case CWAL_RC_OOM: - case CWAL_SCR_SYNTAX: - break; - case CWAL_RC_BREAK: - /* Cosmetic: improve error messages for break/continue... We - really need this handling in a higher-level place, but to get - a good stack trace we have to not BREAK/CONTINUE here. - */ - assert(s2_propagating_get(se) - && "s2_propagating_set() must be used with CWAL_RC_BREAK"); - s2_propagating_set(se, 0); - CWAL_SWITCH_FALL_THROUGH /* ... to 'continue' */; - case CWAL_RC_CONTINUE: - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unhandled '%s' outside of a loop.", - (CWAL_RC_BREAK==rc) ? "break" : "continue"); - break; - default: - if(s2__err(se).code == rc){ - /*MARKER(("se->err.code=%s: %.*s\n", cwal_rc_cstr(se->err.code), - (int)se->err.msg.used, (char *)se->err.msg.mem));*/ - /* Leave it! */ - /* rc = s2_err_ptoker(se, pr, rc, 0, 0); */ - }else{ - rc = s2_throw_ptoker(se, pr, rc, - "Script function call returned " - "non-exception error code #%d (%s).", - rc, cwal_rc_cstr(rc)); - } - break; - } - end: - /* s2_dump_val(*rv, "call result"); */ - s2_ptoker_finalize( pr ); - se->currentScriptFunc = oldScriptFunc; - se->currentScript = oldScript; - return rc; -} - -/** - cwal_callback_hook_pre_f() impl which installs the following - vars in argv->scope: - - this - argv - funcNameIfSetInFuncDecl - - and symbols imported via Function.importSymbols() or the - 'using' keyword. - - It does not set up named parameters - those are set up before the - Function is call()ed, and this callback is triggered from - cwal_function_call_in_scope(). - - s2_engine_from_state(args) must return a (s2_interp*) or an - assertion may be triggered (or a NULL pointer deref). -*/ -int s2_callback_hook_pre(cwal_callback_args const * args, - void * stateUnused){ - s2_func_state * fs = args->callee - ? s2_func_state_for_func(args->callee) - : NULL; - S2_UNUSED_ARG stateUnused; - /* MARKER(("PRE-FUNC HOOK fstate=%p argc=%d!\n", (void const *)fs, (int)args->argc)); */ - if( - !fs /* It's a native function */ - || (/* A function with neither body content nor params. */ - fs->flags & S2_FUNCSTATE_F_EMPTY_BODY - && fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS) - ){ - s2_engine * se = s2_engine_from_state(args->engine); - assert(se); - se->callArgV = 0 /* Make sure this doesn't propagate through to a script - func that this native function calls. Been - there, debugged that! */; - /* MARKER(("Non-script or empty function. Not injecting argv/this.\n")); */ - s2_dotop_state( se, 0, 0, 0 ) /* necessary!!! */; - return 0; - } - else{ - /* Set up argv, callee name, "this", and imported symbols. */ - int rc = 0; - cwal_array * ar = 0; - cwal_value * av = 0; - cwal_value * calleeV = 0; - cwal_scope * s = args->scope; - s2_engine * se = s2_engine_from_args(args); - assert(fs && "Currently always true here."); - assert(se); - calleeV = cwal_function_value(args->callee); - assert(calleeV); - s2_dotop_state( se, 0, 0, 0 ) /* necessary!!! */; - - assert(!(/* empty function */ - fs->flags & S2_FUNCSTATE_F_EMPTY_BODY - && - fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS) - && "optimizations should have skipped this call() overhead" - ); - ar = se->callArgV; - /* s2_dump_val(cwal_array_value(ar),"ar"); */ - if(ar){ - /* This call is EITHER: - - 1) coming from a script-parsed call() point - about to call into s2_callback_f(). - - 2) from a new T() call. - - In either case, we'll re-use this argv array. - */ - av = cwal_array_value(ar); - assert(cwal_value_refcount(av) && "Expecting a ref from our caller's end."); - cwal_value_ref(av); - }else{ - /* This is probably being called from C code, so we need to inject - an argv array. */ - int i = (int)args->argc; - /* MARKER(("Script func called from C code? argc=%d\n", i)); */ - ar = cwal_new_array(args->engine); - if(!ar){ - return CWAL_RC_OOM; - } - av = cwal_array_value(ar); - cwal_value_ref(av); - for( ; !rc && i > 0; --i ){ - /* insert in reverse order as a reallocation optimization */ - /* s2_dump_val(args->argv[i-1],"args->argv[i-1]"); */ - rc = cwal_array_set(ar, (cwal_size_t)i-1, args->argv[i-1]); - } - if(rc){ - cwal_value_unref(av); - return rc; - } - se->callArgV = ar /* i don't like this, but currently needed */; - } - if(0){ - MARKER(("Function call() argc=%d...\n", (int)args->argc)); - s2_dump_val(args->self, "args->self"); - s2_dump_val(av, "argv"); - } - - /* s2_dump_val(cwal_array_value(ar),"args array"); */ - rc = s2_var_set_v(se, 0, se->cache.keyArgv, av); - cwal_value_unref(av); - if(rc) return rc; - - /* Inject function's name (if any)... */ - if( fs->vName - && - (rc = cwal_scope_chain_set_with_flags_v(s, 0, fs->vName, calleeV, - CWAL_VAR_F_CONST)) ){ - return rc; - } - /* Inject "this"... */ - rc = s2_var_set_v( se, 0, se->cache.keyThis, args->self - ? args->self - : cwal_value_undefined()) - /* Should we use var_decl instead and make it const? So - far no need, and i have a vague memory of that approach - causing grief when that topic came up before. - */; - if(rc) return rc; -#if S2_TRY_INTERCEPTORS - else if(args->propertyHolder){ - /* s2_dump_val(args->propertyHolder, "interceptee"); */ - rc = s2_var_set_v( se, 0, se->cache.keyInterceptee, args->propertyHolder ); - if(rc) return rc; - } -#endif - if(fs - && fs->vImported - && !(S2_FUNCSTATE_F_NO_USING_DECL & fs->flags)){ - /* Import imported properties (using/Function.importSymbols()) - into the current scope. - */ - rc = cwal_scope_import_props( s, fs->vImported ); - } - return rc; - } -} - -int s2_callback_hook_post(cwal_callback_args const * args, - void * state, - int fRc, cwal_value * rv){ - int rc = 0; - s2_engine * se = s2_engine_from_state(args->engine); - /* s2_func_state * fs = (s2_func_state *)cwal_args_callee_state(args, &s2_func_state_empty); */ - if(state || fRc || rv){/*avoid unused param warning*/} - assert(se); - /* MARKER(("Callback post-hook\n")); */ - /* s2_dump_val(cwal_array_value(se->callArgV), "se->callArgV"); */ - se->callArgV = 0 - /* required for certain calling combinations of native vs script funcs - to work. */; - /** - Reminder to self: - - We can potentially use this callback to communicate certain - non-zero results back up the call chain, e.g. CWAL_RC_OOM could - be stuffed into a dedicated error code propagation slot (the plan - is to consolidate return/exit/etc. with the - s2_engine::flags::interrupted for that purpose). - */ -#if 0 - if(!fs) return 0; - if(fRc && fs){ - /* Script function: if it threw an exception with no location info, - let's ammend the location info now where we have the input - script source. */ - cwal_value * ex = cwal_exception_get(args->engine): - assert(ex); - if(!cwal_prop_get(ex, "line", 4)){ - rc = s2_ammend_script_func_exception(se, fs, ex); - } - } -#endif - return rc; -} - - -/** - Internal helper to populate s2_func_state on behalf of a function - declation. -*/ -static int s2_function_setup_srcinfo( s2_engine * se, s2_ptoker const * pr, - s2_ptoken const * tOrigin, - s2_ptoken const * tName, - char const * endPos, - s2_func_state * fst, - cwal_value * func ){ - int tlen = (int)(endPos - s2_ptoken_begin(tOrigin)); - int rc = 0; - cwal_hash * h = s2_fstash(se); - cwal_value * v = 0; - cwal_size_t nameLen = 0; - char const * scriptName = s2_ptoker_name_first(pr, &nameLen); - assert(endPos >= s2_ptoken_begin(tOrigin)+8 - && endPos <= s2_ptoker_end(pr) - /* 8 == strlen("proc(){}"), shortest possible function - decl */); - if(!h){ - return CWAL_RC_OOM; - } - assert(tlen >=8 /*proc(){}*/); - /* MARKER(("setting up srcinfo for:\n%.*s\n", tlen, - s2_ptoken_begin(tOrigin))); */ - - /* Calculate script/line/column info, as we need those for - error reporting. */ - { - s2_ptoker_count_lines( pr, s2_ptoken_begin(tOrigin), - &fst->line, &fst->col ); - } - /* currenly used as the mechanism for holding a func's name. */ - if(s2_ptoken_begin(tName)){ - assert(s2_ptoken_end(tName) > s2_ptoken_begin(tName)); - assert(!fst->vName); - rc = s2_ptoken_create_value( se, pr, tName, &fst->vName ); - if(rc) return rc; - cwal_value_ref(fst->vName); - s2_value_to_lhs_scope(func, fst->vName); - } - if(fst->vImported - || !(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS - && fst->flags & S2_FUNCSTATE_F_EMPTY_BODY) - ){ - /* For "empty" functions (no non-junk tokens), - we don't really need any of these string bits, - as they're only used for error reporting... - */ - v = cwal_new_string_value(se->e, s2_ptoken_begin(tOrigin), - (cwal_size_t)tlen); - if(!v){ - return CWAL_RC_OOM; - } - fst->vSrc = v; - cwal_value_ref(fst->vSrc); - s2_value_to_lhs_scope( func, fst->vSrc ); - if(scriptName && *scriptName){ - v = cwal_hash_search( h, scriptName, nameLen ); - if(v){ - ++se->metrics.totalReusedFuncStash; - }else{ - v = cwal_new_string_value(se->e, scriptName, nameLen); - if(!v){ - return CWAL_RC_OOM; - } - cwal_value_ref(v); - rc = cwal_hash_insert_v( h, v, v, 0 ); - if(!rc) rc = cwal_hash_grow_if_loaded(h, 0.8); - cwal_value_unref(v); - if(rc) return rc; - /* hashtable now holds a ref to v (or v is a builtin) */; - } - fst->keyScriptName = v; - cwal_value_ref(fst->keyScriptName); - /* s2_dump_val(v, "fst->keyScriptName"); */ - } - } - return 0; -} - -/** - A cwal_value_rescoper_f() intended for use with - cwal_function instances holding s2_func_state native - data. -*/ -static int cwal_value_rescoper_f_func_state(cwal_scope * s, - cwal_value * v){ - cwal_function * f = cwal_value_get_function(v); - s2_func_state * fst = s2_func_state_for_func(f); - assert(f); - assert(fst); - if(fst->vSrc) cwal_value_rescope(s, fst->vSrc); - if(fst->vImported) cwal_value_rescope(s, fst->vImported); - if(fst->vName) cwal_value_rescope(s, fst->vName); - if(fst->keyScriptName) cwal_value_rescope(s, fst->keyScriptName); - return 0; -} - - -/** - Internal helper to process the using(...) part of: proc() - using(...) {}, resp. the {object} part of ... using {object} ... - - It stashes imported properties into fst->vImported. - - pr_->token must be the (...) or {object} token. fv must be the - Function Value and fst must be fv's state. - - Returns 0 on success, and all that. - - Ownership/lifetime of se, fv, and fst are not modified. - - If passed an object literal, it imports all properties of - that literal into the function. If passed (...) then: - - 1) Any identifiers in that list are resolved immediately and - that identifier's name/value are imported into the function. - - 2) Any object literals are treated as mentioned above. - - - This handling leads to a descrepancy with importSymbols(), which - is best demonstrated with an example: - - const with = proc(obj, func){return proc(){return func()}.importSymbols(obj)()}; - assert 3 === with({a: 1, b:2}, proc(){return a+b}); - - The difference is that 'using' currently (20171112) has no syntax - for saying "import the properties of this non-literal object". - - One easy-to-implement approach would be to extend the (...) syntax - just a small bit: - - using (->nonLiteralObject1, nonLiteralObject2) - - such that the first one would, due to the -> modifier, import the - symbols of that object, whereas the second one would be imported - as-is. -*/ -static int s2_function_using( s2_engine * se, s2_ptoker const * pr_, - cwal_value * fv, s2_func_state * fst ){ - s2_ptoken next = s2_ptoken_empty; - cwal_value * v = 0; - int rc; - char gotComma; - cwal_size_t argPos = 0; - s2_ptoker sub = s2_ptoker_empty; - s2_ptoker * pr = &sub /* simplifies some copy/paste reuse of code :/ */; - s2_ptoken const tUsing = pr_->token; - cwal_value * importHolder = 0; - s2_op const * commaOp = s2_ttype_op(S2_T_Comma); - /* if(!importHolder) return CWAL_RC_OOM; */ - assert(cwal_value_is_function(fv)); - assert(commaOp); - if(S2_T_SquigglyBlock==tUsing.ttype){ - /* using {object} ... */ - sub = *pr_; - rc = s2_eval_object_literal( se, pr, &v ); - if(rc){ - assert(!v); - }else{ - assert(v); - cwal_value_ref(v); - if(!cwal_value_is_object(v)){ - rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Expecting an Object literal but got a %s.", - cwal_value_type_name(v)); - cwal_value_unref(v); - } - else if(!fst->vImported){ - fst->vImported = v /* fst now owns the ref point */; - cwal_value_prototype_set(v, NULL); - s2_value_to_lhs_scope(fv, v); - }else{ - rc = cwal_props_copy(v, fst->vImported); - cwal_value_unref(v); - } - v = 0; - } - goto end; - }/* end of {object}*/ - /* Else using (...) ... */ - assert(S2_T_ParenGroup == tUsing.ttype); - assert(s2_ptoken_len(&tUsing) >= 3 - && "(...) emptiness was checked earlier"); - importHolder = s2_func_import_props(se, fv, fst); - if(!importHolder) return CWAL_RC_OOM; - rc = s2_ptoker_sub_from_toker(pr_, &sub); - if(rc) return rc; - next_part: - gotComma = 0; - v = 0; - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) goto end; - else if(S2_T_Identifier==pr->token.ttype){ - if(!se->skipLevel){ - s2_ptoken const ident = pr->token; - if(s2_ptoken_keyword2(se, &ident)){ - /* confirm that it's not a keyword, as the user would not - be able to use such an imported var. - */ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Cannot use keyword '%.*s' " - "as a 'using' identifier.", - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - goto end; - } - s2_get(se, 0, s2_ptoken_begin(&ident), s2_ptoken_len(&ident), &v); - if(!v){ - rc = s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND, - "Cannot resolve identifier '%.*s'.", - (int)s2_ptoken_len(&ident), - s2_ptoken_begin(&ident)); - goto end; - } - /* Import identifier's value... */ - cwal_value_ref( v ); - rc = cwal_prop_set( importHolder, s2_ptoken_begin(&ident), - s2_ptoken_len(&ident), v); - cwal_value_unref( v ); - if(rc) goto end; - } - }else{ - /* See if we got an Object we can import props from... */ - s2_ptoker_putback(pr) /* for pending eval... */; - rc = s2_eval_expr_impl(se, pr, commaOp, 0, &v); - if(rc){ - assert(!v); - goto end; - }else if(!se->skipLevel){ - if(!v){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "An empty expression is not allowed here."); - goto end; - }else if(!cwal_props_can(v)){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected '%s' value in 'using' list. " - "Expecting identifier or object.", - cwal_value_type_name(v)); - cwal_refunref(v); - goto end; - }else{ - cwal_value_ref(v); - rc = cwal_props_copy(v, importHolder); - cwal_value_unref(v); - if(rc) goto end; - } - }else{ - assert(cwal_value_undefined()==v - && "Violation of current skip-mode conventions."); - } - } - /* Check for trailing comma or EOX */ - rc = s2_next_token( se, pr, 0, &next); - if(rc) goto end; - switch(next.ttype){ - case S2_T_Comma: - gotComma = 1; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_EOF: - case S2_T_EOX: - s2_ptoker_token_set(pr, &next) /* consume it */; - break; - default: - s2_ptoker_errtoken_set(pr, &next); - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Unexpected token '%.*s' in 'using' list.", - (int)s2_ptoken_len(&next), - s2_ptoken_begin(&next)); - goto end; - } - assert(!rc); - if(!se->skipLevel){ - ++argPos; - } - if(!s2_ptoker_is_eof(pr) && !gotComma){ - /* Check for a follow-up token */ - if(s2_ptoker_next_is_ttype(se, pr, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){ - gotComma = 1; - } - } - assert(!rc); - rc = s2_check_interrupted(se, rc); - if(!rc && gotComma) goto next_part; - end: - s2_ptoker_finalize(&sub); - return rc; -} - -/** - Part of s2_keyword_f_function(): - - Requires that curToken->ttype==S2_T_Identifier and be the token at - the X part of either (proc() X) or (proc(){} X). stage must be -1 - for former case and 1 for the latter. Both of these conditions are - assert()ed. - - On error, non-0 is returned and s2_keyword_f_function() must - propagated it. - - On success 0 is returned. - - If -1==stage: - - - An error is triggered if curToken is NOT the identifier "using"... - - - else *tUsing is set to the "using" clause's body: (...) or - {...}. - - If 1==stage: - - - If curToken is an identifier other than "using", this function - has no side effects and 0 is returned: this indicates that whatever - follows the function body is not part of the function definition - (it might be an error, but not one which we have enough information - to diagnose from here). Neither tUsing, fsFlags, nor pr->token are - modified in that case. If curToken is the "using" identifier then, - on success, *tUsing is set to the "using" clause's body token: - (...) or {...}. - - Both cases, on success: - - - If the "using" clause was followed immediately by a dot, - *fsFlags gets OR'd with S2_FUNCSTATE_F_NO_USING_DECL. - - - pr->token is set to the last-consumed token (the body part of - the "using" clause). - */ -static int s2_keyword_f_function_using(s2_keyword const * kw, - s2_engine * se, s2_ptoker * pr, - int stage, s2_ptoken const * curToken, - s2_ptoken * tUsing, - int * fsFlags){ - s2_ptoken check = s2_ptoken_empty; - int rc = 0; - int gotDot = 0; - assert(S2_T_Identifier==curToken->ttype); - assert(-1==stage || 1==stage); - if(5 != s2_ptoken_len(curToken) - || 0 != cwal_compare_cstr("using", 5, s2_ptoken_begin(curToken), 5)){ - if(1==stage){ - /* proc(){body} : we do not/cannot know, from here, if - this is legal, so we do not consume this token, leaving it - for downstream to deal with. */ - return 0; - }else{ - s2_ptoker_errtoken_set(pr, curToken); - return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Unexpected token (type %s) after %s(...)%s.", - s2_ttype_cstr(curToken->ttype), kw->word, - 1==stage ? "{...}" : "" - ); - } - } - /* Grab the (...) resp. {...} part... */ - s2_ptoker_token_set(pr, curToken)/*consume it*/; - rc = s2_next_token( se, pr, 0, &check ); - if(rc) return rc; - - if(S2_T_OpDot == check.ttype){ - /* proc() using ... indicates that we should not - declare using/imported symbols as local symbols at - call()-time. They can instead be accessed via the using() - keyword. */ - /*fFlags |= S2_FUNCSTATE_F_NO_USING_DECL;*/ - s2_ptoker_token_set(pr, &check)/*consume it*/; - rc = s2_next_token(se, pr, 0, &check); - if(rc) return rc; - ++gotDot; - } - - /* Expecting a (...) or {...} part... */ - if(S2_T_ParenGroup != check.ttype - && S2_T_SquigglyBlock != check.ttype){ - s2_ptoker_errtoken_set(pr, &check); - return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) or {object} after 'using%s', " - "but got token type %s. Sub-parse stage: %d.", - gotDot ? "." : "", - s2_ttype_cstr(check.ttype), stage); - } - *tUsing = check /* the (...) or {...} part */; - s2_ptoker_token_set(pr, &check)/*consume it*/; - if(gotDot) *fsFlags |= S2_FUNCSTATE_F_NO_USING_DECL; - return 0; -} - -int s2_keyword_f_function( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - s2_ptoken tParams = s2_ptoken_empty /* function params list */; - s2_ptoken tBody = s2_ptoken_empty /* function body */; - s2_ptoken tName = s2_ptoken_empty /* function name */; - s2_ptoken tUsing = s2_ptoken_empty /* the (...) part of: using (...) */; - s2_ptoken tTail = s2_ptoken_empty /* either tBody or tUsing, whichever appears last */; - s2_ptoken const tOrigin = pr->token; - int rc; - cwal_function * cf = 0; - cwal_value * vf = 0; - s2_func_state * fst = 0; - int fFlags = 0; - assert(S2_T_KeywordFunction==pr->token.ttype - || S2_T_KeywordProc==pr->token.ttype); - rc = s2_next_token( se, pr, 0, &tParams ); - if(rc) goto end; - if(S2_T_Identifier==tParams.ttype){ - tName = tParams; - s2_ptoker_token_set(pr, &tParams); - rc = s2_next_token( se, pr, 0, &tParams ); - if(rc) goto end; - } - if(S2_T_ParenGroup != tParams.ttype){ - rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Expecting [identifier](...) after '%s', " - "but got token type %s.", - kw->word, - s2_ttype_cstr(tParams.ttype)); - goto end; - } - s2_ptoker_token_set(pr, &tParams); - if(!s2_ptoken_has_content(&pr->token)){ - fFlags |= S2_FUNCSTATE_F_EMPTY_PARAMS; - } - /* - TODO? - skip-eval the params and body for syntactic correctness - here. This would also let us count the arity (for potential use - in function dispatching). - - TODO: consolidate the two checks for the 'using' modifier. - */ - - rc = s2_next_token( se, pr, 0, &tBody ); - if(rc) goto end; - if(S2_T_Identifier == tBody.ttype){ - /* check for using(...) _before_ function body... */ - assert(!s2_ptoken_begin(&tUsing)); - rc = s2_keyword_f_function_using(kw, se, pr, -1, - &tBody, &tUsing, &fFlags); - if(rc) goto end; - assert(s2_ptoken_begin(&tUsing)); - rc = s2_next_token( se, pr, 0, &tBody ); - if(rc) goto end; - } - - /* Get/check the body... */ - if(S2_T_SquigglyBlock != tBody.ttype){ - s2_ptoker_errtoken_set(pr, &tBody); - rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "Expecting {...} after %s(...), " - "but got token type %s.", - kw->word, - s2_ttype_cstr(tBody.ttype)); - goto end; - }else if(!s2_ptoken_has_content(&tBody)){ - fFlags |= S2_FUNCSTATE_F_EMPTY_BODY; - } - s2_ptoker_token_set(pr, &tBody) /* consume it */; - if(!s2_ptoken_begin(&tUsing)){ - /* Check for using(...) _after_ the body. */ - s2_ptoken check = s2_ptoken_empty; - rc = s2_next_token(se, pr, 0, &check); - if(rc) goto end; - else if(S2_T_Identifier==check.ttype){ - rc = s2_keyword_f_function_using(kw, se, pr, 1, - &check, &tUsing, &fFlags); - if(rc) goto end; - }else{/* Roll it back, let downstream deal with it. */ - assert(s2_ptoken_begin(&pr->token) == s2_ptoken_begin(&tBody)); - /*s2_ptoker_token_set(pr, &tBody);*/ - s2_ptoker_next_token_set(pr, &check); - } - } - assert(!rc); - - if(S2_T_ParenGroup==tUsing.ttype - && !s2_ptoken_has_content(&tUsing)){ - s2_ptoker_errtoken_set(pr, &tUsing); - rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX, - "using (...) may not be empty."); - /* We explicitly disallow using() but we do allow - using{} because (A) that {} is parsed at a - different level and (B) it turns out, with the addition of the - using() keyword, to be potentially useful to have an empty - imports object installed. - */ - goto end; - } - - tTail = (s2_ptoken_begin(&tUsing) - && s2_ptoken_begin(&tUsing) > s2_ptoken_begin(&tBody)) - ? tUsing : tBody; - - if(se->skipLevel){ - *rv = cwal_value_undefined(); - rc = 0; - s2_ptoker_token_set(pr, &tTail); - goto end; - } - - /*MARKER(("Got func:\n%.*s\n", - (int)(s2_ptoken_end(&tBody) - s2_ptoken_begin(&tOrigin)), - s2_ptoken_begin(&tOrigin)));*/ - *rv = cwal_value_undefined(); - - fst = s2_func_state_malloc(se); - if(!fst){ - rc = CWAL_RC_OOM; - goto end; - } - cf = cwal_new_function( se->e, s2_callback_f, fst, - cwal_finalizer_f_func_state, - &s2_func_state_empty); - if(!cf){ - s2_func_state_free( se, fst ); - rc = CWAL_RC_OOM; - goto end; - } - /* As of here fst belongs to vf. */ - cwal_function_set_rescoper( cf, cwal_value_rescoper_f_func_state ); - fst->flags = fFlags; - vf = cwal_function_value(cf); - cwal_value_ref(vf); - if(S2_T_INVALID != tUsing.ttype){ - s2_ptoken const tmp = pr->token; - assert(S2_T_ParenGroup==tUsing.ttype - || S2_T_SquigglyBlock == tUsing.ttype); - s2_ptoker_token_set(pr, &tUsing); - rc = s2_function_using( se, pr, vf, fst ); - s2_ptoker_token_set(pr, &tmp); - if(rc) goto end; - } - assert(!rc); - rc = s2_function_setup_srcinfo( se, pr, &tOrigin, - &tName, s2_ptoken_end(&tTail), - fst, vf ); - if(rc) goto end; - - *rv = vf; - assert(s2_ptoken_begin(&tTail)); - s2_ptoker_token_set(pr, &tTail); - goto end; - - end: - if(vf){ - if(rc) cwal_value_unref(vf); - else{ - assert(*rv == vf); - cwal_value_unhand(vf); - } - } - return rc; -} - -int s2_ctor_apply( s2_engine * se, cwal_value * operand, - cwal_function * ctor, cwal_array * args, - cwal_value **rv ){ - cwal_value * newThis; - int rc; - cwal_scope sc = cwal_scope_empty; - *rv = 0; - if(!se || !operand || !rv) return CWAL_RC_MISUSE; - else if(!ctor){ - rc = s2_ctor_fetch( se, NULL, operand, &ctor, 1 ); - if(rc) return rc; - } - rc = cwal_scope_push2(se->e, &sc); - if(rc) return rc; - if(args) cwal_value_ref(cwal_array_value(args)); - newThis = cwal_new_object_value(se->e); - if(!newThis){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_ref(newThis); - rc = s2_set_v( se, newThis, se->cache.keyPrototype, operand) - /* instead of cwal_value_prototype_set(newThis, operand) so that - we get consistent error reporting (via s2_set_v(), as opposed - to an unadorned error code via the lower-level function). */; - if(!rc){ - cwal_value * ctorResult = 0 /* the ctor result */; - cwal_array * oldArgV = se->callArgV - /* not _actually_ sure this is needed, but there might - be a corner case or two without it. */; - uint16_t const vFlags - = cwal_container_client_flags_set( newThis, S2_VAL_F_IS_NEWING ); - se->callArgV = args; - rc = args - ? cwal_function_call_array(&sc, ctor, newThis, &ctorResult, args) - : cwal_function_call_in_scope(&sc, ctor, newThis, &ctorResult, - 0, NULL) - ; - cwal_container_client_flags_set( newThis, vFlags ); - assert(!se->callArgV && "callArgV Gets unset via the call() hook(s)"); - se->callArgV = oldArgV; - if(!rc && ctorResult - && newThis != ctorResult - && cwal_value_undefined() != ctorResult){ - /* - If the ctor returns a non-undefined value, use that as the - result of the construction. It's up to the caller to set - the prototype in that case, but he can fetch is via the - ctor's args->self. This approach is needed when the client - wants/needs to return a type other than Object. - */ - /* s2_dump_val(ctorResult,"ctorResult"); */ - /* s2_dump_val(newThis,"newThis"); */ - cwal_value_ref(ctorResult); - cwal_value_unref(newThis); - newThis = ctorResult; - } - } - end: - assert(se->scopes.current->cwalScope == &sc); - cwal_scope_pop2(se->e, newThis); - assert(se->scopes.current->cwalScope != &sc); - if(rc){ - cwal_value_unref(newThis); - }else{ - assert(newThis); - *rv = newThis; - cwal_value_unhand(newThis); - } - if(args) cwal_value_unref(cwal_array_value(args)); - return rc; -} - -/** - Impl for the "new" keyword: - - new Operand(...args...) {optional post-ctor init block} - - Evaluates to the result of the Operand's constructor call (a new - Object by default unless that ctor returns a non-undefined - value). In skip mode, it always evaluates to the undefined value. - - On success pr will be set up such that the next token fetched will - be the one right after the (...args...) or {post-ctor block}. -*/ -int s2_keyword_f_new( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc; - cwal_size_t operandLen = 0; - char const * operandStr = 0; - cwal_value * operand = 0; - cwal_function * ctor = 0; - cwal_array * args = 0; - cwal_value * vargs = 0; - s2_ptoker ptArgs = s2_ptoker_empty; - s2_ptoken tTail = s2_ptoken_empty; - s2_ptoken tWithThis = s2_ptoken_empty; - s2_strace_entry strace = s2_strace_entry_empty; - s2_ptoken const origin = pr->token; - cwal_value * xrv = 0; - assert(rv); - rc = s2_strace_push_pos(se, pr, &origin, &strace ) - /* treat new() like a function for stack trace purposes - or exceptions may contain far-off location info. - */; - if(rc) return rc /* after this, no more 'return', only goto end */; -#if 0 - /* Reminder to self: - - 1) please leave more descriptive comments to self in the future. - - 2) this will not work with the #compiled token layer. - */ - while(s2_is_space(*s2_ptoken_end(&pr->token))){ - /* Cosmetic workaround! */ - s2_ptoken_end_set(&pr->token, s2_ptoken_end(&pr->token)+1); - } -#endif - - rc = s2_eval_expr_impl(se, pr, s2_ttype_op(S2_T_Comma), - S2_EVAL_STOP_AT_CALL, - &operand); - if(rc) goto end; - else if(operand) cwal_value_ref(operand); - if(!operand || S2_T_ParenGroup!=pr->token.ttype){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting EXPR(...) after '%s'.", - kw->word); - goto end; - } - tTail = pr->token; - operandStr = s2_ptoker_capture_cstr(pr, &operandLen); -#if 0 - MARKER(("operand=%.*s\n", (int)operandLen, operandStr)); -#endif - if(se->skipLevel>0){ - xrv = cwal_value_undefined(); - goto check_tail; - }else if(!cwal_props_can(operand)){ - /* s2_dump_val(operand,"new operand"); */ -#if 0 - if(cwal_value_is_tuple(operand)){ - /* we just happen to know this has a ctor, so - fall through and try it... */ - } - else -#endif - { - rc = s2_throw_ptoker(se, pr, CWAL_RC_EXCEPTION, - "'%s' expects a container, " - "but '%.*s' resolves to type '%s'.", - kw->word, (int)operandLen, operandStr, - cwal_value_type_name(operand)); - goto end; - } - } - rc = s2_ctor_fetch(se, pr, operand, &ctor, -1); - if(rc) goto end; - assert(ctor); -#if 0 - MARKER(("new'ing=%.*s\n", (int)operandLen, operandStr)); - MARKER(("Capture=%.*s\n", (int)(s2_ptoken_begin(&pr->capture.end) - -s2_ptoken_begin(&pr->capture.begin)), - s2_ptoken_begin(&pr->capture.begin))); - MARKER(("Call bits=%.*s\n", (int)s2_ptoken_len(&pr->token), - s2_ptoken_begin(&pr->token))); - s2_dump_val(operand, "new() prototype"); -#endif - - /* Eval the call params... */ - assert(S2_T_ParenGroup==pr->token.ttype); - args = cwal_new_array(se->e); - if(!args){ - rc = CWAL_RC_OOM; - goto end; - } - vargs = cwal_array_value(args); - assert(vargs); - cwal_value_ref(vargs); - cwal_value_make_vacuum_proof(vargs, 1); - /* cwal_value_make_vacuum_proof(operand, 1); */ - /* cwal_value_ref(operand); */ - rc = s2_ptoker_sub_from_toker(pr, &ptArgs); - if(!rc){ - rc = s2_eval_to_array( se, &ptArgs, args, 1 ); - /* cwal_value_unhand(operand); */ - /* cwal_value_make_vacuum_proof(operand, 0); */ - if(!rc){ - /* s2_dump_val(vargs,"call args"); */ - rc = s2_ctor_apply( se, operand, ctor, args, &xrv ); - /* s2_dump_val(xrv, "result from 'new'"); */ - } - } - s2_ptoker_finalize( &ptArgs ); - - check_tail: - if(rc){ - assert(!xrv); - }else{ - if(xrv) cwal_value_ref(xrv); - /* Check for a {body} part after the T(...), and treat it like - an inlined extension to the ctor like in Java: new X() {{ ... }}. - */ - s2_next_token(se, pr, 0, &tWithThis); - if(S2_T_SquigglyBlock==tWithThis.ttype){ - tTail = tWithThis; - s2_ptoker_token_set(pr, &tTail); - /*MARKER(("Got post-new() body: %.*s\n", - (int)s2_ptoken_len(&tTail), s2_ptoken_begin(&tTail)));*/ - if(!se->skipLevel){ - rc = s2_eval_current_token_with_this(se, pr, xrv, NULL); - } - }else{ - s2_ptoker_next_token_set(pr, &tWithThis); - } - } - end: - switch(rc){ - case CWAL_RC_EXCEPTION: - s2_exception_add_script_props(se, pr) - /* Needed to decorate calls to native functions. */; - break; - default: - break; - } - if(strace.pr){ - s2_strace_pop(se); - } - if(operand){ - cwal_value_unref(operand); - operand = 0; - } - if(vargs){ - cwal_value_make_vacuum_proof(vargs, 0); - cwal_value_unref(vargs); - } - if(!rc){ - s2_ptoker_token_set(pr, &tTail); - *rv = xrv; - cwal_value_unhand(xrv); - }else if(xrv){ - cwal_value_unref(xrv); - } - return rc; -} - -static int s2_enum_is_legal_id( int ttype ){ - switch(ttype){ - case S2_T_Identifier: - case S2_T_LiteralStringSQ: - case S2_T_LiteralStringDQ: - case S2_T_LiteralString: -#if 0 - /* reminder to self: - - The reason numbers as keys in enums is not a good idea is - because lookups may or may not be type-strict, depending on - whether the enum uses an object or hash for storage. Consider: - - var e = enum {1: 'one'}; - e::1; // fine - e::'1'; // doh: will match for an object but not a hash - - So... we disallow them solely to eliminate that corner case :/. - */ - case S2_T_LiteralIntBin: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntHex: - case S2_T_LiteralIntOct: - case S2_T_LiteralDouble: -#endif - return ttype; - default: - return 0; - } -} - -int s2_keyword_f_echo( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - return s2_keyword_f_reserved(kw, se, pr, rv); -} - -int s2_keyword_f_enum( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - /* TODO: reimplement this using the s2_enum_builder API. - The glaring problem is that the builder wants to know, in advance, - whether we want a hash or object for property storage. - - 2020-02-21: enums are now always hashes, so that's not an issue - anymore. - */ - /* Also TODO: this function is a mess. Clean it up! */ - int rc; - s2_ptoken tName = s2_ptoken_empty; - s2_ptoker ptBody = s2_ptoker_empty; - s2_ptoker const * oldScript = se->currentScript; - cwal_value * store = 0; - cwal_value * key; - cwal_size_t entryCount = 0, loopCount = 0; - cwal_value * enumProto = s2_prototype_enum(se); - int doBreak = 0; - cwal_hash * hashStore = 0; - int gotCustomProto = 0 /* If a custom prototype property is applied, we - need a couple special cases. */; - if(!enumProto) return CWAL_RC_OOM; - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else if(S2_T_Identifier==pr->token.ttype){ - tName = pr->token; - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - } - - if(S2_T_SquigglyBlock != pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting {...} after '%s'.", - kw->word); - } - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - } - if(!s2_ptoken_has_content(&pr->token)){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Empty %s body is not permitted.", - kw->word); - } - - rc = s2_ptoker_sub_from_toker(pr, &ptBody); - /* As of here, NO RETURNs */ - if(rc) goto end; - -#if 0 - MARKER(("tName=%.*s\n", (int)nameLen, s2_ptoken_begin(&tName))); - MARKER(("Body=%.*s\n", (int)s2_ptoken_len(&ptBody), - s2_ptoken_begin(&ptBody))); -#endif - - assert(ptBody.parent == pr); - se->currentScript = &ptBody; - while(!rc && !doBreak){ - s2_ptoken tId = s2_ptoken_empty; - s2_ptoken tValue = s2_ptoken_empty; - cwal_value * uv = 0; - cwal_value * wrappedVal = 0; - char wrappedIsId = 0 /* true if the wrapped value is the - string form of the ID token, so that - we can know to re-use that string - in the entry-name-to-entry mapping. */; - char isPrototype = 0 /* true if an entry's key is "prototype" */; - key = 0; - rc = s2_next_token(se, &ptBody, 0, 0); - if(rc) goto loop_end; - else if(1==++loopCount - && S2_T_OpHash==ptBody.token.ttype){ - rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX, - "The '#' modifier for enums is no longer " - "needed nor permitted."); - goto loop_end; - }else if(!s2_enum_is_legal_id(ptBody.token.ttype)){ - rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX, - "Unexpected token type [%s] in %s body.", - s2_ttype_cstr(ptBody.token.ttype), - kw->word); - goto loop_end; - } - tId = ptBody.token; - if(9U==s2_ptoken_len(&tId) - && 0==cwal_compare_cstr("prototype", 9, - s2_ptoken_begin(&tId), 9)){ - isPrototype = 1; - } - /* Check next token before continuing to alloc this value, to - accommodate our switch-to-hash bits... */ - rc = s2_next_token(se, &ptBody, 0, &tValue); - if(rc) goto loop_end; - s2_ptoker_token_set(&ptBody, &tValue) /* consume it */; - doBreak = s2_ptoker_is_eof(&ptBody) - /* Reminder: we want to fall through when doBreak - is true so that store can be upgraded to a hash - if needed. - */; - if(!doBreak){ - if(S2_T_Colon == tValue.ttype){ /* Entry: VALUE */ - if( (rc = s2_eval_expr_impl( se, &ptBody, - s2_ttype_op(S2_T_Comma), 0, - &wrappedVal)) ){ - assert(!wrappedVal); - /*cwal_value_unref(wrappedVal);*/ - goto loop_end; - } - cwal_value_ref(wrappedVal); - /* s2_dump_val(wrappedVal, "enum_entry = (value)"); */ - rc = s2_next_token(se, &ptBody, 0, 0); - if(rc) goto loop_end; - doBreak = s2_ptoker_is_eof(&ptBody); - } - else if(S2_T_Comma != tValue.ttype){ - rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX, - "Expecting comma in %s body, " - "but got token type %s.", - kw->word, - s2_ttype_cstr(tValue.ttype)); - goto loop_end; - } - } - assert(!rc); - if(!wrappedVal){ - /* treat an entry with no value as having its name as its - value. That seems, in practice, to be more useful than the - undefined value. */ - assert(!wrappedIsId); - if(isPrototype){ - rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX, - "%s: prototype property cannot be applied " - "without a value.", - kw->word); - goto loop_end; - } - rc = s2_ptoken_create_value(se, pr, &tId, &wrappedVal); - if(!wrappedVal){ - rc = CWAL_RC_OOM; - goto loop_end; - } - cwal_value_ref(wrappedVal); - wrappedIsId = 1; - } - if(!store){ - /* Create the property storage. */ - assert(!entryCount); - hashStore = cwal_new_hash(se->e, 7); - if(!hashStore){ - rc = CWAL_RC_OOM; - goto loop_end; - }else{ - store = cwal_hash_value(hashStore); - s2_hash_dot_like_object(store, 1 - /* Needed for remainder of the loop, - so that s2_set_v() will DTRT. - Will get overwritten afterwards*/); - cwal_value_ref(store); - cwal_value_make_vacuum_proof(store,1); - } - } - assert(!key); - if(wrappedIsId){ - assert(!isPrototype); - key = wrappedVal; - }else if(!isPrototype){ - rc = s2_ptoken_create_value(se, pr, &tId, &key); - if(rc){ - assert(!key); - goto loop_end; - } - } - if(isPrototype){ - assert(!key); - assert(wrappedVal); - ++gotCustomProto; - rc = s2_set_v( se, store, se->cache.keyPrototype, wrappedVal) - /* ^^^^^^^^ for its non-trivial handling of prototype setting. */ - ; - if(rc) goto loop_end; - }else{ - cwal_value_ref(key); - uv = cwal_new_unique(se->e, 0); - if(!uv){ - cwal_value_unref(key); - rc = CWAL_RC_OOM; - goto loop_end; - } - cwal_unique_wrapped_set( uv, wrappedVal ); - cwal_value_ref(uv); - rc = s2_set_with_flags_v( se, store, key, uv, CWAL_VAR_F_CONST ); - cwal_value_unref(uv); - cwal_value_unref(wrappedVal); - wrappedVal = 0; - ++entryCount; - if(rc){ - cwal_size_t keylen = s2_ptoken_len(&tId); - char const * ckey = s2_ptoken_begin(&tId); - cwal_value_unref(key); - key = 0; - s2_ptoker_token_set( &ptBody, &tId ); - rc = s2_err_ptoker(se, &ptBody, rc, - "Assignment to enum entry '%.*s' failed with " - "code %d (%s).", - (int)keylen, ckey, rc, cwal_rc_cstr(rc)); - goto loop_end; - }else{ - rc = s2_set_with_flags_v( se, store, uv, key, - CWAL_VAR_F_CONST - | CWAL_VAR_F_HIDDEN ); - cwal_value_unref(key); - key = 0; - if(rc) goto loop_end; - } - } - loop_end: - if(wrappedVal){ - cwal_value_unref(wrappedVal); - } - key = 0; - } - - end: - s2_ptoker_finalize( &ptBody ); - if(!rc && !entryCount){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s requires at least one entry.", - kw->word); - } - - if(!rc){ - assert(hashStore); - rc = cwal_hash_grow_if_loaded( hashStore, 0.85 ); - } - - if(!rc && s2_ptoken_begin(&tName)){ - /* Set typename... */ - cwal_size_t const nameLen = s2_ptoken_len(&tName); - assert(nameLen); - key = cwal_new_string_value(se->e, s2_ptoken_begin(&tName), nameLen); - if(!key) rc = CWAL_RC_OOM; - else{ - cwal_value_ref(key); - rc = cwal_prop_set_with_flags_v( store, se->cache.keyTypename, key, - CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN ) - /* Note that this is an object-level property, not hash entry. */; - cwal_value_unref(key); - key = 0; - } - } - - se->currentScript = oldScript; - if(store){ - cwal_value_make_vacuum_proof(store,0); - } - if(rc){ - if(store){ - cwal_value_unref(store); - } - }else{ - if(!gotCustomProto){ - cwal_value_prototype_set(store, enumProto); - } - cwal_container_flags_set(store, - CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES - | CWAL_CONTAINER_DISALLOW_PROP_SET - /* TODO:??? - | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET */ - ); - cwal_container_client_flags_set(store, S2_VAL_F_ENUM); - cwal_value_unhand(store); - *rv = store; - } - return rc; -} - -/** - Tag type IDs for typeinfo(TAG ...). - - ACHTUNG: their order MUST match the entries of the s2TypeInfoWords - array (defined below) because we reference those by index with these - entries. -*/ -enum s2_typeinfo_words { - /* Sentinel value. Must be 0. */ - TYPEINFO_NONE = 0, - /** True if operand can be used as an operand for the "new" keyword. */ - TYPEINFO_CANNEW, - /** True if operand is or has an array in its prototype chain. */ - TYPEINFO_HASARRAY, - /** True if operand is or has an buffer in its prototype chain. */ - TYPEINFO_HASBUFFER, - /** True if operand is or has an enum in its prototype chain. */ - TYPEINFO_HASENUM, - /** True if operand is or has an exception in its prototype chain. */ - TYPEINFO_HASEXCEPTION, - /** True if operand is or has a hash in its prototype chain. */ - TYPEINFO_HASHASH, - /** True if operand is or has a native in its prototype chain. */ - TYPEINFO_HASNATIVE, - /** True if operand is or has an Object in its prototype chain. */ - TYPEINFO_HASOBJECT, - /** True if operand has a prototype. */ - TYPEINFO_HASPROTOYPE, - /** True if operand is an array. */ - TYPEINFO_ISARRAY, - /** True if operand is a bool. */ - TYPEINFO_ISBOOL, - /** True if operand is a buffer. */ - TYPEINFO_ISBUFFER, - /** True if operand is callable (is a function or has a function - in its prototype chain). */ - TYPEINFO_ISCALLABLE, - /** True if operand is a container (can hold its own properties). */ - TYPEINFO_ISCONTAINER, - /** True if operand is the name of a declared (currently in-scope) variable/const. */ - TYPEINFO_ISDECLARED, - /** True if operand is legal for use with the dot operator. */ - TYPEINFO_ISDEREFABLE, - /** True if operand is a double. */ - TYPEINFO_ISDOUBLE, - /** True if operand is an enum. */ - TYPEINFO_ISENUM, - /** True if operand is an exception. */ - TYPEINFO_ISEXCEPTION, - /** True if operand is a function. */ - TYPEINFO_ISFUNCTION, - /** True if operand is a hash. */ - TYPEINFO_ISHASH, - /** True if operand is an integer . */ - TYPEINFO_ISINT, - /** True if cwal_value_is_iterating_props(operand) OR cwal_value_is_iterating_list(operand). */ - TYPEINFO_ISITERATING, - /** True if cwal_value_is_iterating_list(operand). */ - TYPEINFO_ISITERATINGLIST, - /** True if cwal_value_is_iterating_props(operand). */ - TYPEINFO_ISITERATINGPROPS, - /** True if operand is a tuple or an array. */ - TYPEINFO_ISLIST, - /** True if operand is a var/const declared in the local scope. */ - TYPEINFO_ISLOCAL, - /** True if operand is a native. */ - TYPEINFO_ISNATIVE, - /** True if the current "this" is being initalized via the 'new' keyword. */ - TYPEINFO_ISNEWING, - /** True if operand is an integer or a double. */ - TYPEINFO_ISNUMBER, - /** - True if operand is an integer, a double, a boolean, - or a numeric-format string (one parseable by - NumberPrototype.parseNumber()). - */ - TYPEINFO_ISNUMERIC, - /** True if operand is an Object */ - TYPEINFO_ISOBJECT, - /** True if operand is a string. */ - TYPEINFO_ISSTRING, - /** True if operand is a Tuple */ - TYPEINFO_ISTUPLE, - /** True if operand is a Unique-type value. */ - TYPEINFO_ISUNIQUE, - /** True if cwal_value_may_iterate(operand) OR cwal_value_may_iterate_list(operand). */ - TYPEINFO_MAYITERATE, - /** True if cwal_value_may_iterate_list(operand). */ - TYPEINFO_MAYITERATELIST, - /** True if cwal_value_may_iterate(operand). */ - TYPEINFO_MAYITERATEPROPS, - /** Evaluates to the type's name (as as the typename - keyword). */ - TYPEINFO_NAME, - /** Evaluates to the operand's refcount. */ - TYPEINFO_REFCOUNT -}; -enum s2_typeinfo_arg_type { -TYPEINFO_ARG_IDENTIFIER = -1, -TYPEINFO_ARG_NONE = 0, -TYPEINFO_ARG_EXPR = 1 -}; -typedef struct { - enum s2_typeinfo_words type; - /** - Argument type. - */ - enum s2_typeinfo_arg_type argType; -} s2_typeinfo_word; -static const s2_typeinfo_word s2TypeInfoWords[] = { - /* - ACHTUNG: their order MUST match the entries of - the s2_typeinfo_words enum because we reference them - by index that way! - */ - { TYPEINFO_NONE, TYPEINFO_ARG_NONE}, - { TYPEINFO_CANNEW, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASARRAY, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASBUFFER, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASENUM, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASEXCEPTION, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASHASH, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASNATIVE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASOBJECT, TYPEINFO_ARG_EXPR}, - { TYPEINFO_HASPROTOYPE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISARRAY, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISBOOL, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISBUFFER, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISCALLABLE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISCONTAINER, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISDECLARED, TYPEINFO_ARG_IDENTIFIER}, - { TYPEINFO_ISDEREFABLE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISDOUBLE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISENUM, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISEXCEPTION, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISFUNCTION, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISHASH, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISINT, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISITERATING, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISITERATINGLIST, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISITERATINGPROPS, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISLIST, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISLOCAL, TYPEINFO_ARG_IDENTIFIER}, - { TYPEINFO_ISNATIVE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISNEWING, TYPEINFO_ARG_EXPR - /*special case: isnewing requires argType TYPEINFO_ARG_EXPR but - optionally accepts no argument. */}, - { TYPEINFO_ISNUMBER, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISNUMERIC, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISOBJECT, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISSTRING, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISTUPLE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_ISUNIQUE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_MAYITERATE, TYPEINFO_ARG_EXPR}, - { TYPEINFO_MAYITERATELIST, TYPEINFO_ARG_EXPR}, - { TYPEINFO_MAYITERATEPROPS, TYPEINFO_ARG_EXPR}, - { TYPEINFO_NAME, TYPEINFO_ARG_EXPR}, - { TYPEINFO_REFCOUNT, TYPEINFO_ARG_EXPR} -}; - -/** - If the given ptoken resolves to a typeinfo(WORD) identifier, its - entry is returned, else NULL is returned. This is an O(1) search. - See s2_ptoken_keyword() for more details. - */ -static s2_typeinfo_word const * s2_ptoken_typeinfo( s2_ptoken const * pt ){ - cwal_size_t const tlen = s2_ptoken_len(pt); - s2_typeinfo_word const * rc; -#define W(X,E) rc = tlen==(cwal_size_t)sizeof(X)-1 && \ - 0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1) \ - ? &s2TypeInfoWords[E] : NULL; \ - assert(rc ? E==rc->type : 1); return rc - switch(s2__keyword_perfect_hash(pt)){ - /* Generated by s2-keyword-hasher.s2 (or equivalent): */ - - case 0x00002004: W("cannew",TYPEINFO_CANNEW); - case 0x00003a10: W("can-new",TYPEINFO_CANNEW); - case 0x000088d4: W("hasarray",TYPEINFO_HASARRAY); - case 0x0000f5ba: W("has-array",TYPEINFO_HASARRAY); - case 0x000112eb: W("hasbuffer",TYPEINFO_HASBUFFER); - case 0x0001ece2: W("has-buffer",TYPEINFO_HASBUFFER); - case 0x000043ba: W("hasenum",TYPEINFO_HASENUM); - case 0x00007a24: W("has-enum",TYPEINFO_HASENUM); - case 0x0008c084: W("hasexception",TYPEINFO_HASEXCEPTION); - case 0x000f9697: W("has-exception",TYPEINFO_HASEXCEPTION); - case 0x000042db: W("hashash",TYPEINFO_HASHASH); - case 0x00007920: W("has-hash",TYPEINFO_HASHASH); - case 0x0001163a: W("hasnative",TYPEINFO_HASNATIVE); - case 0x0001f102: W("has-native",TYPEINFO_HASNATIVE); - case 0x00011411: W("hasobject",TYPEINFO_HASOBJECT); - case 0x0001ee92: W("has-object",TYPEINFO_HASOBJECT); - case 0x0008fe4e: W("hasprototype",TYPEINFO_HASPROTOYPE); - case 0x000fe16a: W("has-prototype",TYPEINFO_HASPROTOYPE); - case 0x0000466e: W("isarray",TYPEINFO_ISARRAY); - case 0x00007814: W("is-array",TYPEINFO_ISARRAY); - case 0x00002216: W("isbool",TYPEINFO_ISBOOL); - case 0x00003abf: W("is-bool",TYPEINFO_ISBOOL); - case 0x00008df4: W("isbuffer",TYPEINFO_ISBUFFER); - case 0x0000f16b: W("is-buffer",TYPEINFO_ISBUFFER); - case 0x00023026: W("iscallable",TYPEINFO_ISCALLABLE); - case 0x0003bb16: W("is-callable",TYPEINFO_ISCALLABLE); - case 0x00048a39: W("iscontainer",TYPEINFO_ISCONTAINER); - case 0x0007a928: W("is-container",TYPEINFO_ISCONTAINER); - case 0x0002317e: W("isdeclared",TYPEINFO_ISDECLARED); - case 0x0003bcff: W("is-declared",TYPEINFO_ISDECLARED); - case 0x00047176: W("isderefable",TYPEINFO_ISDEREFABLE); - case 0x00078b66: W("is-derefable",TYPEINFO_ISDEREFABLE); - case 0x00008f26: W("isdouble",TYPEINFO_ISDOUBLE); - case 0x0000f2e6: W("is-double",TYPEINFO_ISDOUBLE); - case 0x00002290: W("isenum",TYPEINFO_ISENUM); - case 0x00003b5a: W("is-enum",TYPEINFO_ISENUM); - case 0x00049271: W("isexception",TYPEINFO_ISEXCEPTION); - case 0x0007b484: W("is-exception",TYPEINFO_ISEXCEPTION); - case 0x00024c1e: W("isfunction",TYPEINFO_ISFUNCTION); - case 0x0003de01: W("is-function",TYPEINFO_ISFUNCTION); - case 0x000021d6: W("ishash",TYPEINFO_ISHASH); - case 0x00003a7b: W("is-hash",TYPEINFO_ISHASH); - case 0x00012407: W("isinteger",TYPEINFO_ISINT); - case 0x0001ecea: W("is-integer",TYPEINFO_ISINT); - case 0x00049bc0: W("isiterating",TYPEINFO_ISITERATING); - case 0x0007c0fa: W("is-iterating",TYPEINFO_ISITERATING); - case 0x0049f317: W("isiteratinglist",TYPEINFO_ISITERATINGLIST); - case 0x00f86719: W("is-iterating-list",TYPEINFO_ISITERATINGLIST); - case 0x0093f07e: W("isiteratingprops",TYPEINFO_ISITERATINGPROPS); - case 0x01f0da02: W("is-iterating-props",TYPEINFO_ISITERATINGPROPS); - case 0x000022fe: W("islist",TYPEINFO_ISLIST); - case 0x00003bef: W("is-list",TYPEINFO_ISLIST); - case 0x00004697: W("islocal",TYPEINFO_ISLOCAL); - case 0x0000788c: W("is-local",TYPEINFO_ISLOCAL); - case 0x00009072: W("isnative",TYPEINFO_ISNATIVE); - case 0x0000f4ba: W("is-native",TYPEINFO_ISNATIVE); - case 0x0000918a: W("isnewing",TYPEINFO_ISNEWING); - case 0x0000f61c: W("is-newing",TYPEINFO_ISNEWING); - case 0x0000932c: W("isnumber",TYPEINFO_ISNUMBER); - case 0x0000f84b: W("is-number",TYPEINFO_ISNUMBER); - case 0x00012a04: W("isnumeric",TYPEINFO_ISNUMERIC); - case 0x0001f4bc: W("is-numeric",TYPEINFO_ISNUMERIC); - case 0x00008e90: W("isobject",TYPEINFO_ISOBJECT); - case 0x0000f291: W("is-object",TYPEINFO_ISOBJECT); - case 0x00009662: W("isstring",TYPEINFO_ISSTRING); - case 0x0000fc5c: W("is-string",TYPEINFO_ISSTRING); - case 0x00004a2e: W("istuple",TYPEINFO_ISTUPLE); - case 0x00007d16: W("is-tuple",TYPEINFO_ISTUPLE); - case 0x0000954c: W("isunique",TYPEINFO_ISUNIQUE); - case 0x0000fb0a: W("is-unique",TYPEINFO_ISUNIQUE); - case 0x000243ae: W("mayiterate",TYPEINFO_MAYITERATE); - case 0x00040cc2: W("may-iterate",TYPEINFO_MAYITERATE); - case 0x00246da6: W("mayiteratelist",TYPEINFO_MAYITERATELIST); - case 0x0081db28: W("may-iterate-list",TYPEINFO_MAYITERATELIST); - case 0x0048e4dc: W("mayiterateprops",TYPEINFO_MAYITERATEPROPS); - case 0x0103c160: W("may-iterate-props",TYPEINFO_MAYITERATEPROPS); - case 0x0000070c: W("name",TYPEINFO_NAME); - case 0x00008bd2: W("refcount",TYPEINFO_REFCOUNT); - - default: break; - } -#undef W - return NULL; -} - -int s2_keyword_f_typeinfo( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - s2_ptoken tIdent = s2_ptoken_empty; - s2_ptoker prBody = s2_ptoker_empty; - s2_ptoker const * oldScript = se->currentScript; - cwal_size_t idLen = 0; - cwal_value * xrv = 0; - int rc; - int buul = -1 /* <0=unknown, 0=false, >0=true */; - s2_typeinfo_word const * word = 0; - *rv = 0; - assert(pr->e); - /** - Ideas: - - typeinfo(FLAG EXPR) - - We use (...) to avoid potential precedence confusion for use - cases such as: - - typeinfo iscallable X || throw "need a callable type" - - i.e. does the || belong the RHS of the typeinfo or not? We punt - on that problem by adding the parenthesis, making it unambiguous. - - FLAG is one of the words defined in s2TypeInfoWords above. - */ - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) goto end; - - if(S2_T_ParenGroup!=pr->token.ttype){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (IDENTIFIER ...) after '%s'.", - kw->word); - goto end; - } - - if(se->skipLevel>0){ - /** - Reminder to self: someday we should arguably continue going - in skip mode so that we can validate that the contents of (...) - are syntactically valid. - */ - *rv = cwal_value_undefined(); - goto end; - } - - rc = s2_ptoker_sub_from_toker(pr, &prBody); - assert(prBody.e == pr->e); - if(!rc){ - prBody.flags |= S2_T10N_F_IDENTIFIER_DASHES; - rc = s2_next_token(se, &prBody, 0, 0); - prBody.flags &= ~S2_T10N_F_IDENTIFIER_DASHES; - } - if(rc) goto end; - else if(S2_T_Identifier!=prBody.token.ttype){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Expected typeinfo tag in %s(TAG ...).", - kw->word); - goto end; - } - - se->currentScript = &prBody; - tIdent = prBody.token; - idLen = s2_ptoken_len(&tIdent); - assert(idLen > 0); - /* MARKER(("typeinfo tag: %.*s\n", (int)idLen, s2_ptoken_begin(&tIdent))); */ - - /* - Look for sub-keyword match... - */ - word = s2_ptoken_typeinfo(&tIdent); - - assert(!rc); - if(!word){ - /* Note that in skip mode we've skipped over the whole (...), so - this won't be triggered in skip mode (an exception to the - rule/guideline regarding "blatant syntax errors"). Whether or - not that needs "fixing" (such that this error could be - triggered in skip mode) is as yet undecided. - */ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Unknown idenitifier '%.*s' in %s().", - (int)idLen, s2_ptoken_begin(&tIdent), - kw->word); - goto end; - } - - switch(word->argType){ - case TYPEINFO_ARG_IDENTIFIER: - /* typeinfo(tag IDENTIFIER) */ - rc = s2_next_token(se, &prBody, 0, 0); - if(!rc && S2_T_Identifier != prBody.token.ttype){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "The %s(%.*s ...) tag requires an IDENTIFIER.", - kw->word, (int)idLen, s2_ptoken_begin(&tIdent)); - } - if(rc) goto end; - switch(word->type){ - case TYPEINFO_ISLOCAL: - case TYPEINFO_ISDECLARED: - xrv = s2_var_get(se, TYPEINFO_ISLOCAL==word->type ? 0 : -1, - s2_ptoken_begin(&prBody.token), - s2_ptoken_len(&prBody.token)); - buul = xrv ? 1 : 0; - xrv = 0; - break; - default: - s2_fatal(CWAL_RC_ASSERT, - "Missing typeinfo() handler entry for " - "TYPEINFO_ARG_IDENTIFIER!"); - break; - } - break; - case TYPEINFO_ARG_EXPR: - /* typeinfo(tag EXPR) */ - rc = s2_eval_expr_impl(se, &prBody, 0, - (TYPEINFO_NAME==word->type) - ? S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED - : 0 - , &xrv); - if(rc){ - assert(!xrv); - goto end; - }else if(!xrv && word->type==TYPEINFO_ISNEWING){ - /* Special case: typeinfo(isnewing) ==> typeinfo(isnewing this) */ - xrv = s2_var_get_v( se, -1, se->cache.keyThis ); - }else if(!xrv){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Expecting non-empty expression after %s(%.*s ...).", - kw->word, (int)idLen, s2_ptoken_begin(&tIdent)); - goto end; - } - cwal_value_ref(xrv); - break; - case TYPEINFO_ARG_NONE: - /* typeinfo(tag) */ - ++se->skipLevel; - rc = s2_eval_expr_impl(se, &prBody, 0, 0, &xrv); - --se->skipLevel; - if(rc){ - assert(!xrv); - goto end; - }else if(xrv){ - assert(cwal_value_undefined() == xrv && "b/c of skipLevel"); - cwal_value_ref(xrv); - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Got unexpected expression in %s(%.*s ).", - kw->word, (int)idLen, s2_ptoken_begin(&tIdent)); - goto end; - } - break; - } - assert(!rc && "Expecting that non-0 RC did 'goto end'"); - - switch(word->type){ - -#define TYPEWORD(WORD,PREDICATE) \ - case WORD: buul = PREDICATE(xrv); break - - TYPEWORD(TYPEINFO_ISARRAY,cwal_value_is_array); - TYPEWORD(TYPEINFO_ISBOOL,cwal_value_is_bool); - TYPEWORD(TYPEINFO_ISBUFFER,cwal_value_is_buffer); - TYPEWORD(TYPEINFO_ISDOUBLE,cwal_value_is_double); - TYPEWORD(TYPEINFO_ISENUM,s2_value_is_enum); - TYPEWORD(TYPEINFO_ISEXCEPTION,cwal_value_is_exception); - TYPEWORD(TYPEINFO_ISFUNCTION,cwal_value_is_function); - TYPEWORD(TYPEINFO_ISINT,cwal_value_is_integer); - TYPEWORD(TYPEINFO_ISITERATINGLIST,cwal_value_is_iterating_list); - TYPEWORD(TYPEINFO_ISITERATINGPROPS,cwal_value_is_iterating_props); - TYPEWORD(TYPEINFO_ISNATIVE,cwal_value_is_native); - TYPEWORD(TYPEINFO_ISNEWING,s2_value_is_newing); - TYPEWORD(TYPEINFO_ISOBJECT,cwal_value_is_object); - TYPEWORD(TYPEINFO_ISSTRING,cwal_value_is_string); - TYPEWORD(TYPEINFO_ISTUPLE,cwal_value_is_tuple); - TYPEWORD(TYPEINFO_ISUNIQUE,cwal_value_is_unique); - TYPEWORD(TYPEINFO_MAYITERATELIST,cwal_value_may_iterate_list); - TYPEWORD(TYPEINFO_MAYITERATEPROPS,cwal_value_may_iterate); -#undef TYPEWORD - - case TYPEINFO_CANNEW: - buul = s2_value_is_newable(se, xrv); - break; - - case TYPEINFO_HASARRAY: - buul = !!cwal_value_array_part(se->e, xrv); - break; - - case TYPEINFO_HASBUFFER: - buul = !!cwal_value_buffer_part(se->e, xrv); - break; - - case TYPEINFO_HASENUM: - buul = !!s2_value_enum_part(se, xrv); - break; - - case TYPEINFO_HASEXCEPTION: - buul = !!cwal_value_exception_part(se->e, xrv); - break; - - case TYPEINFO_HASHASH: - buul = s2_value_is_enum(xrv) - ? 0 : !!cwal_value_hash_part(se->e, xrv); - break; - - case TYPEINFO_HASNATIVE: - buul = !!cwal_value_native_part(se->e, xrv, 0); - break; - - case TYPEINFO_HASOBJECT: - buul = !!cwal_value_object_part(se->e, xrv); - break; - - case TYPEINFO_HASPROTOYPE: - buul = !!cwal_value_prototype_get(se->e, xrv); - break; - - case TYPEINFO_ISCALLABLE: - buul = !!cwal_value_function_part(se->e, xrv); - break; - - case TYPEINFO_ISCONTAINER: - buul = cwal_props_can(xrv); - break; - - case TYPEINFO_ISHASH: - buul = cwal_value_is_hash(xrv) && !s2_value_is_enum(xrv); - break; - - case TYPEINFO_ISITERATING: - buul = cwal_value_is_iterating_props(xrv) || cwal_value_is_iterating_list(xrv); - break; - - case TYPEINFO_ISLIST: - buul = cwal_value_is_array(xrv) || cwal_value_is_tuple(xrv); - break; - - case TYPEINFO_ISLOCAL: - case TYPEINFO_ISDECLARED: - /* handled above */ - assert(buul >= 0); - break; - - case TYPEINFO_ISNUMBER: - buul = cwal_value_is_integer(xrv) - || cwal_value_is_double(xrv) - /* reminder: don't use cwal_value_is_number() here b/c that - one considers booleans to be numeric. */; - break; - - case TYPEINFO_ISNUMERIC: - if(!(buul = cwal_value_is_number(xrv)) - && cwal_value_is_string(xrv)){ - /* check for numeric-format strings a-la - 0.prototype.parseNumber(). */ - cwal_size_t slen = 0; - char const * src = cwal_value_get_cstr(xrv, &slen); - buul = s2_cstr_parse_double(src, slen, 0) - || s2_cstr_parse_int(src, slen, 0); - } - break; - - case TYPEINFO_MAYITERATE: - buul = cwal_value_may_iterate(xrv) || cwal_value_may_iterate_list(xrv); - break; - - case TYPEINFO_ISDEREFABLE: - switch(cwal_value_type_id(xrv)){ - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - case CWAL_TYPE_BOOL: - buul = 0; - break; - default: - buul = 1; - break; - } - break; - - case TYPEINFO_NAME:{ - /* This dual-pronged approach isn't strictly necessary, but it - keeps us from effectively strdup()'ing typenames on each call - if xrv has/inherits a __typename property. The cwal type-name - API does not deal in non-string types, so it cannot pass us - back the underlying cwal_value for a given __typename - property. Maybe that API needs to be changed? - */ - cwal_value * vTn = cwal_prop_get_v(xrv, se->cache.keyTypename); - if(!vTn){ - cwal_size_t nlen = 0; - char const * name = cwal_value_type_name2(xrv, &nlen); - vTn = nlen - ? cwal_new_string_value(se->e, name, nlen) - : cwal_value_undefined(); - if(!vTn){ - rc = CWAL_RC_OOM; - goto end; - } - } - *rv = vTn; - break; - } - case TYPEINFO_REFCOUNT: - *rv = cwal_new_integer(se->e, - (cwal_int_t)cwal_value_refcount(xrv)); - if(!*rv){ - rc = CWAL_RC_OOM; - goto end; - } - break; - - case TYPEINFO_NONE: - s2_fatal(CWAL_RC_ASSERT, - "This is not possible: unhandled case in typeinfo handler."); - break; - } - - if(rc) goto end; - else if(buul>=0){ - *rv = cwal_new_bool(buul ? 1 : 0); - }else{ - assert(*rv); - } - - end: - s2_ptoker_finalize(&prBody); - se->currentScript = oldScript; - if(xrv){ - if(!rc && *rv == xrv) cwal_value_unhand(xrv); - else cwal_value_unref(xrv); - } - return rc; -} - -/** - Internal helper for s2_foreach_iterate(), processing one entry - from the result set. - - body holds the whole for-each body. If bodyIsExp then body is - assumed to be a single expression, otherwise it's assumed to - be a block of expressions. If keyName is not NULL then it is the - NAME part of: - - foreach(X => NAME, value) - - and the key parameter must be the associated property key or - array index. - - If valName is not NULL, it is the VALUE part of: - - foreach(X => name, VALUE) - - The val param holds the associated property value or array - entry. - - Combinations of NULL/non-NULL which are legal depends on the - operand's type, but it is never legal for both to be NULL. - - Returns 0 on success. -*/ -static int s2_foreach_one( s2_engine * se, s2_ptoker const * body, - char bodyIsExpr, - cwal_value * keyName, cwal_value * key, - cwal_value * valName, cwal_value * val ){ - s2_ptoker pr = *body; - int rc = 0; - cwal_scope scope = cwal_scope_empty; - rc = cwal_scope_push2(se->e, &scope); - if(rc) return rc; - assert(keyName ? !!key : !key); - assert(valName ? !!val : !!keyName); - if(keyName && (rc = s2_var_decl_v( se, keyName, key, 0 ))){ - goto end; - } - if(valName && (rc = s2_var_decl_v( se, valName, val, 0 ))){ - goto end; - } - assert(body->e); - assert(pr.e); - /* FIXME: won't work with #compiled tokens: */ - s2_ptoken_begin_set(&pr.token, s2_ptoker_begin(body)); - s2_ptoken_end_set(&pr.token, s2_ptoker_end(body)); - rc = s2_eval_current_token(se, &pr, bodyIsExpr, 0, 0); - rc = s2_check_interrupted(se, rc); - switch(rc){ - /* case CWAL_RC_BREAK: is handled higher up */ - case CWAL_RC_CONTINUE: - rc = 0; - s2_engine_err_reset(se); - break; - default: - break; - } - - end: - cwal_scope_pop(se->e); - return rc; -} - -/** - State used by s2_foreach_iterate(). -*/ -struct s2_foreach_state { - s2_engine * se; - s2_ptoker * body; - char bodyIsExpr; - char isEnum; - cwal_value * keyName; - cwal_value * valName; - cwal_value * breakRC; - cwal_size_t index; -}; -typedef struct s2_foreach_state s2_foreach_state; -static const s2_foreach_state s2_foreach_state_empty = { -0,0,0,0,0,0,0,0U -}; - -/* cwal_kvp_visitor_f() impl for s2_foreach_iterate(). */ -static int s2_foreach_kvp_visitor( cwal_kvp const * kvp, void * state ){ - s2_foreach_state * fst = (s2_foreach_state*)state; - int rc = 0; - cwal_value * val = cwal_kvp_value(kvp); - assert(fst && fst->se); - assert(fst->keyName); - if(fst->isEnum && !cwal_value_is_unique(val)){ - /* for enums, only iterate over the string=>unique mappings, not - the reverse mappings nor the properties like enumEntryCount. Seems - reasonable enough. */ - } -#if S2_TRY_INTERCEPTORS - else if(!!s2_value_is_interceptor(val)){ - /* Don't iterate over interceptors. */ - } -#endif - else{ - rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr, - fst->keyName, cwal_kvp_key(kvp), - fst->valName, fst->valName ? val : 0 ); - if(CWAL_RC_BREAK==rc){ - fst->breakRC = s2_propagating_take(fst->se); - assert(fst->breakRC && "misuse of CWAL_RC_BREAK"); - cwal_value_ref(fst->breakRC); - /* s2_engine_err_reset(fst->se); */ - /* s2_dump_val(fst->breakRC,"breakRC"); */ - } - } - return rc; -} - -/* cwal_array_visitor_f() impl for s2_foreach_iterate(). */ -static int s2_foreach_array_visitor( cwal_array * a, cwal_value * v, - cwal_size_t index, void * state ){ - s2_foreach_state * fst = (s2_foreach_state*)state; - int rc; - cwal_engine * e; - cwal_value * vIndex; - assert(fst && fst->se); - e = fst->se->e; - vIndex = fst->keyName ? cwal_new_integer(e, (cwal_int_t)index) : 0; - if(fst->keyName && !vIndex){ - if(a){/*avoid unused param warning*/} - return CWAL_RC_OOM; - } - cwal_value_ref(vIndex); - rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr, - fst->keyName, fst->keyName ? vIndex : 0, - fst->valName, v ? v : cwal_value_undefined() ); - cwal_value_unref(vIndex); - if(CWAL_RC_BREAK==rc){ - fst->breakRC = s2_propagating_take(fst->se); - assert(fst->breakRC && "misuse of CWAL_RC_BREAK"); - cwal_value_ref(fst->breakRC); - /* s2_engine_err_reset(fst->se); */ - } - return rc; -} - -/* cwal_value_visitor_f() impl for s2_foreach_iterate(), for - visiting tuples. */ -static int s2_foreach_tuple_visitor( cwal_value * v, void * state ){ - s2_foreach_state * fst = (s2_foreach_state*)state; - int rc; - cwal_engine * e; - cwal_value * vIndex; - assert(fst && fst->se); - e = fst->se->e; - vIndex = fst->keyName ? cwal_new_integer(e, (cwal_int_t)fst->index++) : 0; - if(fst->keyName && !vIndex){ - return CWAL_RC_OOM; - } - if(vIndex) cwal_value_ref(vIndex); - rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr, - fst->keyName, fst->keyName ? vIndex : 0, - fst->valName, v ? v : cwal_value_undefined() ); - if(vIndex) cwal_value_unref(vIndex); - if(CWAL_RC_BREAK==rc){ - fst->breakRC = s2_propagating_take(fst->se); - assert(fst->breakRC && "misuse of CWAL_RC_BREAK"); - cwal_value_ref(fst->breakRC); - /* s2_engine_err_reset(fst->se); */ - } - return rc; -} - -/** - foreach impl for strings. vStr must be the X part of - foreach(X=>...) and it must be of type string (that gets - asserted). Unlike most places, a Buffer will not act like a string - here (foreach will use the Object property path for Buffers). - - Note that iteration using this approach is far more efficient than - iterating over its length, e.g. - - for(var i = 0, n = aString.#; ise->e; - cwal_value * vIndex = 0; - cwal_value * vVal = 0; - char unsigned const * const eof = pos + slen; - assert(str); - assert(cstr); - for( ; !rc && pos < eof && fst->index < slen; ++fst->index ){ - unsigned char const * cend = pos; - if(isAscii){ - pos = cstr + fst->index; - cend = pos + 1; - }else{ - cwal_utf8_read_char(pos, eof, &cend); - if(!(cend-pos)) break /*???*/; - } - assert(cend <= eof); - vVal = cwal_new_string_value(e, (char const*)pos, (cwal_size_t)(cend-pos)); - if(!vVal){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(vVal); - if(fst->keyName){ - vIndex = cwal_new_integer(e, (cwal_int_t)fst->index); - if(!vIndex){ - cwal_value_unref(vVal); - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(vIndex); - } - rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr, - fst->keyName, fst->keyName ? vIndex : 0, - fst->valName, vVal ); - if(vIndex) cwal_value_unref(vIndex); - cwal_value_unref(vVal); - pos = cend; - } - if(CWAL_RC_BREAK==rc){ - fst->breakRC = s2_propagating_take(fst->se); - assert(fst->breakRC && "misuse of CWAL_RC_BREAK"); - cwal_value_ref(fst->breakRC); - /* s2_engine_err_reset(fst->se); */ - } - return rc; -} - -/** - Part of the foreach() loop mechanism. This part loops over the - XXX part of foreach(...) XXX; - - arguments: - - kw: the keyword this function is working for (used for error - reporting). - - body: the token holding the complete body of the loop. - - bodyIsExpr: if true, body is assumed to contain only a single - expression, else is it assumed to be a {script block}. - - container: the container or string to iterate over. - - opMode: what to iterate over: 0 = object props, enum entries, or - tuple entries. S2_T_At = array and tuple entries. S2_T_OpHash = - hash entries. S2_T_LiteralString = a string (iterate over its - characters), noting that it need not be a literal (we just use this - token type ID because it's convenient to do so). Anything else is - illegal and may trigger an assert(). - - keyName: the symbol name of the key part of foreach(X=>key,val). - - valName: the symbol name of the val part of foreach(X=>key,val). - - rv: on success, the result value is put here (rescoped if needed) - if it's not NULL. -*/ -static int s2_foreach_iterate( s2_keyword const * kw, s2_engine * se, - s2_ptoker * body, char bodyIsExpr, - cwal_value * container, int opMode, - cwal_value * keyName, cwal_value * valName, - cwal_value ** rv){ - int rc = 0; - s2_foreach_state fst = s2_foreach_state_empty; - assert(0==opMode - || S2_T_At==opMode - || S2_T_OpHash==opMode - || S2_T_LiteralString==opMode); - fst.se = se; - fst.body = body; - fst.bodyIsExpr = bodyIsExpr; - fst.keyName = keyName; - fst.valName = valName; - assert(!se->skipLevel); - /* s2_dump_val(container,"container"); */ - /* s2_dump_val(keyName,"keyName"); */ - /* s2_dump_val(valName,"valName"); */ - - /* Special case: for enums, when no mode is specified, choose its - enum hash entries, but filter so that only the "forward" - mappings, not its value-to-key reverse mappings, are - traversed... */ - if( !opMode && s2_value_is_enum(container)){ - opMode = S2_T_OpHash; - fst.isEnum = 1; - assert(cwal_value_is_hash(container) && "Enums are always hashes since 2020-02-21."); - } - - if(rv) *rv = cwal_value_undefined(); - switch(opMode){ - case S2_T_OpHash: /* Hash entries */ - assert(cwal_value_get_hash(container)); - CWAL_SWITCH_FALL_THROUGH; - case 0: /* Object properties */{ - assert(cwal_props_can(container)); - rc = 0==opMode - ? cwal_props_visit_kvp( container, s2_foreach_kvp_visitor, - &fst ) - : cwal_hash_visit_kvp( cwal_value_get_hash(container), - s2_foreach_kvp_visitor, &fst ) - ; - break; - }/* Objects/Hashes */ - case S2_T_At: /* Array/Tuple entries */{ - cwal_array * ar = cwal_value_array_part(se->e, container); - cwal_tuple * tp = ar ? 0 : cwal_value_get_tuple(container); - if(ar){ - rc = cwal_array_visit2( ar, s2_foreach_array_visitor, &fst ); - }else{ - assert(tp); - rc = cwal_tuple_visit(tp, s2_foreach_tuple_visitor, &fst); - } - break; - } - case S2_T_LiteralString: - rc = s2_foreach_string_char( container, &fst ); - break; - default: - if(kw){/*avoid unused param warning*/} - assert(!"Unknown opMode!"); - rc = CWAL_RC_CANNOT_HAPPEN; - break; - } - /* Reminder to self (20180620): there's seeminly a race condition - between the break- and interruption handling here, in that a - Break can potentially hide/trump a poorly-timed interrupt. We - check for interruption in the top-level foreach impl. */ - if(CWAL_RC_BREAK==rc){ - rc = 0; - assert(fst.breakRC && "should have been set by iterator"); - if(fst.breakRC){ - assert((cwal_value_is_builtin(fst.breakRC) - || cwal_value_refcount(fst.breakRC)) - && "We're expecting the ref we held."); - if(rv) { - *rv = fst.breakRC; - cwal_value_unhand(fst.breakRC); - }else{ - cwal_value_unref(fst.breakRC); - } - } - }else{ - if(fst.breakRC/* hypothetically possible on an interrupt */){ - assert((cwal_value_is_builtin(fst.breakRC) - || cwal_value_refcount(fst.breakRC)) - && "We're expecting the ref we held."); - cwal_value_unref(fst.breakRC); - } - } - return rc; -} - - -/** - foreach(X => v) - foreach(X => k, v) - - Where X may be: - - container: iterate over non-hidden properties (never inherited properties). - - @array: iterate over array entries. - - #hash: iterate over hash entries. - - enum: iterate over (key=>enumEntry) pairs. - - tuple: iterate over tuple entries. May optionally, for consistency - with @array, be prefixed with @, but the meaning is the same. - - string: iterate over each character. - - If 1 operand is passed after =>, then its interpretation depends on - the data type: - - - Arrays, tuples, strings: the value of the element - - Everything else: the property key - - If 2 operands are passed after =>, they are the property key/index - and value, in that order. - - The value to the left of the => is evaluated only once, so it may, - e.g. be the result of a function call. - - Reminder to self: results are undefined if passed a non-NULL final - argument which points to uninitialized memory. i.e. *rv must, when - this is called, either be 0 or point to a value. In practice, - though, it must never point to a value, as we will (on success) - overwrite it without unreferencing it. We never, in day-to-day - work, leave such values uninitialized, but if this routine crashes - near the end, it may be an indication of such. -*/ -int s2_keyword_f_foreach( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ptoker prParens = s2_ptoker_empty /* the whole (X => ...) part */; - s2_ptoker prBody = s2_ptoker_empty /* the ... part of (X => ...) */; - s2_ptoken tBody = s2_ptoken_empty /* body block/expression */; - s2_ptoken tKey = s2_ptoken_empty /* (x => KEY, value) */; - s2_ptoken tVal = s2_ptoken_empty /* (x => VALUE) (X => key, VALUE) */; - cwal_value * operand = 0/*the value at the X part of foreach(X=>...)*/; - char bodyIsExpr = 0 /* true if the loop body is a standalone expr, - false if it's a block. */; - int operandMode = 0 - /* - 0 = object props, enum entries, or tuple entries - - S2_T_At = array and tuple entries - - S2_T_OpHash = hash entries - - S2_T_LiteralString = chars of a string - */; - - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - else if(S2_T_ParenGroup!=pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after '%s'.", - kw->word); - } - rc = s2_ptoker_sub_from_toker(pr, &prParens); - if(!rc){ - rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr); - } - if(rc) goto end; - - if(se->skipLevel){ - if(rv) *rv = cwal_value_undefined(); - goto end; - } - else{ - /* Parse the (X => ...) part */ - s2_ptoker prEntry = prParens - /* (X => ...) part */ - /* This will break if/when we add #compiled tokens */ - ; - s2_ptoken tmpTok = s2_ptoken_empty; - s2_op const * opArrow = s2_ttype_op(S2_T_OpArrow2); - assert(opArrow); - /* Check for a leading '@' or '#' ... */ - rc = s2_next_token(se, &prEntry, 0, 0); - if(rc){ - /*rc = s2_err_ptoker(se, &prEntry, rc, "Tokenization error.");*/ - goto end; - } - switch(prEntry.token.ttype){ - case S2_T_At: - case S2_T_OpHash: - operandMode = prEntry.token.ttype; - break; - default: - s2_ptoker_next_token_set(&prEntry, &prEntry.token); - break; - } - - rc = s2_eval_expr_impl(se, &prEntry, opArrow, 0, &operand); - if(operand) cwal_value_ref(operand); - if(rc){ - assert(!operand && "but... how?"); - /* - 20181119: let's allow a break in the operand expression, for - the sake of: - - foreach((blah ||| break)=>x){...} - - Yeah, it's a corner case, but i just tried to do it in a script - and it didn't work, so... here it is. - */ - if(CWAL_RC_BREAK==rc){ - *rv = s2_propagating_take(se); - assert(*rv && "Else internal misuse of CWAL_RC_BREAK."); - rc = 0; - } - goto end; - } - else if(!operand){ - rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX, - "Empty expression not allowed in the X part of " - "%s(X=>...).", kw->word); - goto end; - } - tmpTok = prEntry.capture.begin; - /* s2_dump_val(operand,"operand"); */ - /*MARKER(("(X => ...) operandMode=%s, X=<<<%.*s>>>\n", - s2_ttype_cstr(operandMode), - (int)s2_ptoken_len(&tmpTok), s2_ptoken_begin(&tmpTok)));*/ - rc = s2_next_token(se, &prEntry, 0, 0); - if(rc) goto end; - /* Confirm operand type is okay... */ - switch(operandMode){ - case 0: /* default mode and mode for Object properties */{ - if(cwal_value_is_tuple(operand)){ - /* Force iteration over entries */ - operandMode = S2_T_At; - } - else if(cwal_value_is_string(operand)){ - operandMode = S2_T_LiteralString; - } - else if(!cwal_props_can(operand)){ - s2_ptoker_errtoken_set(&prEntry, &tmpTok); - rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE, - "Expecting a string or a value capable of " - "holding properties."); - } - else if(!cwal_props_has_any(operand) - && !s2_value_is_enum(operand) - ){ - /* Simply checking for no properties isn't sufficient in the - case of hashed enums unless the enum has the - enumEntryCount property (which was removed 20171204). */ - - /* Nothing to do! */ - /* *rv set is important, else the eval loop - ignores what we parsed and takes "foreach" (the string) - as the next token! */ - *rv = cwal_value_undefined(); - goto end; - } - break; - } - case S2_T_At: /* Array and tuple entries */{ - cwal_array * ar = cwal_value_array_part(se->e, operand); - cwal_tuple * tp = ar ? 0 : cwal_value_get_tuple(operand); - if(!ar && !tp){ - s2_ptoker_errtoken_set(&prEntry, &tmpTok); - rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE, - "Expecting an Array or Tuple value."); - }else if(0== - (ar ? cwal_array_length_get(ar) : cwal_tuple_length(tp))){ - /* Nothing to do! */ - *rv = cwal_value_undefined(); - goto end; - } - break; - } - case S2_T_OpHash: /* Hash entries */{ - cwal_hash * h = cwal_value_hash_part(se->e, operand); - if(!h){ - s2_ptoker_errtoken_set(&prEntry, &tmpTok); - rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE, - "Expecting a Hash value."); - }else if(!cwal_hash_entry_count(h)){ - /* Nothing to do! */ - *rv = cwal_value_undefined(); - goto end; - } - break; - } - default: - rc = CWAL_RC_ASSERT; - assert(!"Wrong opMode!"); - } - if(rc) goto end; - - if(S2_T_OpArrow2 != prEntry.token.ttype){ - rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX, - "Expecting => after X in " - "%s(X => ...).", - kw->word); - goto end; - } - rc = s2_next_token(se, &prEntry, 0, 0); - if(rc) goto end; - else if(S2_T_Identifier != prEntry.token.ttype){ - rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX, - "Expecting identifier after => " - "in %s(X => ...).", - kw->word); - goto end; - }else{ - tKey = prEntry.token; - } - /* Check for a comma then a value identifier... */ - rc = s2_next_token(se, &prEntry, 0, 0); - if(rc) goto end; - else if(S2_T_Comma == prEntry.token.ttype){ - rc = s2_next_token(se, &prEntry, 0, 0); - if(rc) goto end; - else if(S2_T_Identifier != prEntry.token.ttype){ - rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX, - "Expecting identifier after comma " - "in %s(X => Y,...).", - kw->word); - goto end; - }else{ - tVal = prEntry.token; - } - }else if(!s2_ttype_is_eox(prEntry.token.ttype)){ - rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX, - "Unexpected token after Y in " - "%s(X => Y...).", - kw->word); - goto end; - }else{ - if(S2_T_At==operandMode || S2_T_LiteralString==operandMode){ - /* (@X=>V) and ("string"=>V) pass the index's value, not the - index, to each loop iteration. */ - tVal = tKey; - tKey = s2_ptoken_empty; - } - } - } - - /*MARKER(("tBody (isExpr=%d, opMode=%s): <<<%.*s>>>\n", bodyIsExpr, - s2_ttype_cstr(operandMode), (int)s2_ptoken_len(&tBody), - s2_ptoken_begin(&tBody)));*/ - /*MARKER(("tKey: <<<%.*s>>>\n", (int)s2_ptoken_len(&tKey), s2_ptoken_begin(&tKey)));*/ - /*MARKER(("tVal: <<<%.*s>>>\n", (int)s2_ptoken_len(&tVal), s2_ptoken_begin(&tVal)));*/ - - /* And now, finally, create the key/value name parts and iterate - over the body... */ - assert(!rc); - if(!rc){ - cwal_value * key = 0; - cwal_value * val = 0; - cwal_value * xrv = 0; - cwal_scope scope = cwal_scope_empty; - assert(tVal.ttype || tKey.ttype); - rc = cwal_scope_push2(se->e, &scope); - if(rc) goto end; - rc = s2_ptoker_sub_from_token(pr, &tBody, &prBody) - /* 20200107 FIXME: this won't work with #compiled tokens when - tBody is non-{} expression, nor can - s2_ptoker_sub_from_token() accommodate underlying compiled - tokens, whether or not tBody is a {} block. */; - assert(!rc && "Cannot fail!"); - assert(pr->e); - prBody.parent = pr; - prBody.e = pr->e; - if(tKey.ttype){ - rc = s2_ptoken_create_value(se, pr, &tKey, &key); - if(rc){ - assert(!key); - goto pop_scope; - } - assert(key); - cwal_value_ref(key); - } - if(tVal.ttype){ - rc = s2_ptoken_create_value(se, pr, &tVal, &val); - if(rc){ - if(key) cwal_value_unref(key); - goto pop_scope; - } - assert(val); - cwal_value_ref(val); - } - - /* s2_dump_val(key,"foreach key"); */ - /* s2_dump_val(val,"foreach value name"); */ - ++se->scopes.current->sguard.vacuum /* protect key/val */; - rc = s2_foreach_iterate( kw, se, &prBody, bodyIsExpr, - operand, operandMode, - key, val, &xrv); - --se->scopes.current->sguard.vacuum; - if(xrv) cwal_value_ref(xrv); - if(key) cwal_value_unref(key); - cwal_value_unref(val); - val = 0; - /* prBody.errPos = 0; */ - pop_scope: - assert( &scope == se->scopes.current->cwalScope ); - cwal_scope_pop2( se->e, rc ? 0 : xrv ); - rc = s2_check_interrupted(se, rc); - if(!rc && rv){ - *rv = xrv ? xrv : cwal_value_undefined(); - if(xrv) cwal_value_unhand(xrv); - }else{ - if(xrv) cwal_value_unref(xrv); - } - } - - end: - if(!rc){ - s2_ptoker_token_set(pr, &tBody) - /* 20200107 FIXME: this won't work with #compiled tokens when - tBody is non-{} expression. */; - rc = s2_check_interrupted(se, rc); - } - if(operand){ - if(!rc && rv) cwal_value_ref(*rv); - cwal_value_unref(operand); - if(!rc && rv) cwal_value_unhand(*rv); - } - s2_ptoker_finalize( &prParens ); - return rc; -} - -/** - The using() function-like keyword is used for accessing - "using"/importSymbols()-injected state. -*/ -int s2_keyword_f_using( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc; - cwal_value * imports = 0; - cwal_value * xrv = 0; - assert(S2_T_KeywordUsing == kw->id); - rc = s2_next_token(se, pr, 0, 0); - if(rc) return rc; - if(S2_T_ParenGroup!=pr->token.ttype){ - if(se->currentScriptFunc){ - /* Accept "using" as shorthand for "using()" inside a script - function body. */ - s2_ptoker_putback(pr); - }else{ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after %s keyword.", - kw->word); - } - } - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - } - if(S2_T_ParenGroup==pr->token.ttype){ - rc = s2_eval_current_token(se, pr, 0, - 0/* S2_EVAL_PUSH_SCOPE seems like overkill, - as the operand will almost always be empty - or an identifier/property access, rather than - a complex expression. Note that the empty-parens - case gets optimized out (no eval, no scope), - either way.*/, - &xrv); - if(rc){ - assert(!xrv); - return rc; - } - }else{ - assert(se->currentScriptFunc /* tested above */); - } - if(!xrv){ - /* using() is valid in the current script-side func to refer - to its own imports. */ - if(!se->currentScriptFunc){ - rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "%s() is only valid in the body of a " - "script-implemented function.", - kw->word); - }else{ - imports = se->currentScriptFunc->vImported; - cwal_value_ref(imports); - } - }else{ - /* using(expr), where expr must be a script-side function. */ - cwal_function * f = cwal_value_get_function(xrv); - s2_func_state * fst = f ? s2_func_state_for_func(f) : 0; - cwal_value_ref(xrv); - if(!fst){ - rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE, - "Expression passed to %s(...) is not a " - "script-defined function.", - kw->word) - /* Note that this throws an exception, but using() in - the wrong place is a syntax error. Feature or bug? */; - }else{ - imports = fst->vImported; - cwal_value_ref(imports); - } - cwal_value_unref(xrv); - xrv = 0; - } - if(imports){ - assert(!rc); -#if 0 - cwal_value_rescope(se->scopes.current->cwalScope, imports) - /* i don't think this is strictly necessary. i haven't (yet?) - been able to work out a case where the imports would end up - in a wrong scope without this. If the imports are accessed by - an older scope, they'll get rescoped as soon as a local symbol - references them, they're propagated, or similar.*/; -#endif - cwal_value_unhand(imports); - *rv = imports; - }else if(!rc){ - *rv = cwal_value_undefined(); - } - return rc; -} - -/** - The s2out keyword. -*/ -int s2_keyword_f_s2out( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - cwal_value * v = se->skipLevel>0 ? cwal_value_undefined() : se->cache.s2out; - if(!v){ - /* - Initialize it on the first call... - - The API does not guaranty that s2.io.output ever gets installed, - so we cannot simply reuse that object here (though we may at - some point try that before falling back to creating a new - object). Alternately, we could reverse that: initilialize - s2.output to point to this object. - */ - cwal_scope sc = cwal_scope_empty - /* - Reminder to self: we use a scope here in order to cleanly - handle the 1-in-a-bazillion chance that the s2out.operator<< - prop set succeeds but s2_stash_set() fails (out of memory). In - that case we'd leave a cyclic structure laying around, which - the scope will clean up for us. - */; - S2_UNUSED_ARG pr; - rc = cwal_scope_push2(se->e, &sc); - if(rc) return rc; - v = cwal_new_function_value(se->e, s2_cb_write, 0, 0, 0); - if(v){ - cwal_value_prototype_set(v, 0); - /* - We want s2out to be callable like s2out(...) and - s2out<word, v ) - /* Moves it to the top scope and implicitly makes it - vacuum-proof. */; - } - }else{ - rc = CWAL_RC_OOM; - } - cwal_scope_pop2( se->e, rc ? 0 : v ); - if(rc){ - v = 0 /* was destroyed by the popped scope */; - }else{ - /* v is held by/in se->stash. */ - se->cache.s2out = v; - /* We'll disallow new properties on this object for sanity's - sake. We can reconsider this later if the need arises. One - side-effect of this is that we cannot reasonably make - s2.io.output an alias for this object because there's no good - justification for locking that object this way. */ - s2_seal_container(v, 1); - } - } - if(!rc) *rv = v; - return rc; -} - -/** - The cwal_callback_f() part of the import keyword. -*/ -static int s2_cb_keyword_import( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - s2_engine * const se = s2_engine_from_args(args); - cwal_value * const vPf = cwal_prop_get(cwal_function_value(args->callee), - "path", 4); - s2_pf * const pf = s2_value_pf(vPf); - if((rc=s2_cb_disable_check(args, S2_DISABLE_FS_STAT | S2_DISABLE_FS_READ))){ - return rc; - } - else if(!pf){ - rc = s2_cb_throw(args, CWAL_RC_ASSERT, - "The import.path property has gone missing!"); - } - else if(1==args->argc && cwal_value_is_bool(args->argv[0])){ - /* import(bool): set args->self[se->cache.keyImportFlag] */; - s2_seal_container(args->self, 0); - rc = cwal_prop_set_v(args->self, se->cache.keyImportFlag, - args->argv[0]); - s2_seal_container(args->self, 1); - if(!rc) *rv = args->self; - }else if(!args->argc || args->argc>2 || - (2==args->argc && (!cwal_value_is_bool(args->argv[0]))) - /* Non-stringy argv[1] case is handled below. */ - ){ - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting ([bool doPathSearch,] string filename) " - "or (bool doPathSearch) arguments(s)."); - }else{ - /* - import([bool doPathSearch,] string filename), where doPathSearch - defaults to args->self[se->cache.keyImportFlag]. - */ - char const doPfSearch = args->argc>1 - ? cwal_value_get_bool(args->argv[0]) - : cwal_value_get_bool(cwal_prop_get_v(args->self, - se->cache.keyImportFlag)); - cwal_value * const vFName = args->argv[args->argc>1 ? 1 : 0]; - cwal_size_t fLen; - char const * fname = cwal_value_get_cstr(vFName, &fLen); -#define FMT_NOT_FOUND "File not found: %.*s", (int)fLen, fname - if(!fname){ - goto misuse; - } - else if(doPfSearch){ - char const * fn = s2_pf_search(pf, fname, fLen, &fLen, - S2_PF_SEARCH_FILES); - if(fn) fname = fn; - if(!fn){ - rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND, FMT_NOT_FOUND); - if(CWAL_RC_EXCEPTION==rc){ - /* Amend the exception with: - exception.notFound = - {filename:name, path:array, extensions:array} */ - int rc2; - cwal_value * obj; - cwal_value * const ex = cwal_exception_get(args->engine); - assert(ex && "But we *just* threw one?"); - obj = cwal_new_object_value(args->engine) - /* Reminder to self: we're running in a callback scope, so - cleanup on error is not _strictly_ necessary here. */; - if(obj){ - cwal_value_ref(obj); - rc2 = cwal_prop_set(obj, "filename", 8, vFName); - if(!rc2) rc2 = cwal_prop_set(obj, "path", 4, - cwal_array_value(s2_pf_dirs(pf))); - if(!rc2) rc2 = cwal_prop_set(obj, "extensions", 10, - cwal_array_value(s2_pf_exts(pf))); - if(!rc2){ - rc2 = cwal_prop_set(ex, "notFound", 8, obj); - } - cwal_value_unref(obj); - }else{ - rc2 = CWAL_RC_OOM; - } - if(rc2){ - rc = rc2; - } - } - } - }else if(!s2_file_is_accessible(fname, 0)){ - rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND, FMT_NOT_FOUND); - } -#undef FMT_NOT_FOUND - if(!rc){ - rc = s2_eval_filename(se, 1, fname, fLen, rv); - } - } - return rc; -} - -/** - The import keyword impl. -*/ -int s2_keyword_f_import( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - cwal_value * v = se->skipLevel>0 ? cwal_value_undefined() : se->cache.import; - if(!v){ - /* - Initialize it on the first call... - */ - cwal_scope sc = cwal_scope_empty - /* - Reminder to self: we use a scope here in order to simplify - cleanup if something goes wrong. - */; - s2_pf * pf = 0; - cwal_value * vImport = 0; - S2_UNUSED_ARG pr; - rc = cwal_scope_push2(se->e, &sc); - if(rc) return rc; - vImport = cwal_new_function_value(se->e, s2_cb_keyword_import, 0, 0, 0); - if(!vImport){ - rc = CWAL_RC_OOM; - goto end; - } - pf = s2_pf_new(se); - rc = pf - ? cwal_prop_set_with_flags(vImport, "path", 4, - s2_pf_value(pf), CWAL_VAR_F_CONST) - : CWAL_RC_OOM; - if(!rc){ - rc = cwal_prop_set_v(vImport, se->cache.keyImportFlag, - cwal_value_true()); - } - if(rc) goto end; - else{/* Set path/extensions from environment, if available... */ - struct { - char const * evar; - char const * dflt; - int (*adder)(s2_pf *, char const *, cwal_size_t); - cwal_array * (*getter)(s2_pf*); - } envs[] = { - {"S2_IMPORT_PATH", NULL, s2_pf_dir_add, s2_pf_dirs - /* Potential TODO: if s2_home_get() returns non-NULL - and S2_IMPORT_PATH is not set, add S2_HOME/lib(?) - (S2_HOME/import?) to the defeault import path. */ - }, - {"S2_IMPORT_EXTENSIONS", ".s2", s2_pf_ext_add, s2_pf_exts} - }; - s2_path_toker pt = s2_path_toker_empty; - char const * entry = 0; - char const * env; - cwal_size_t entryLen = 0; - size_t i = 0; - assert(pf); - for(; !rc && i < sizeof(envs)/sizeof(envs[0]); ++i){ - env = getenv(envs[i].evar); - if(!env){ - /* Note that empty env vars evaluate to "", not NULL. We - rely on that here to enable overriding of the default - S2_IMPORT_EXTENSIONS value with an empty list. */ - env = envs[i].dflt; - } - if(env && *env){ - s2_path_toker_init(&pt, env, -1); - while(!rc && !s2_path_toker_next(&pt, &entry, &entryLen)){ - rc = envs[i].adder(pf, entry, entryLen); - } - } - if(!rc && !envs[i].getter(pf)){ - /* If there was no env var, or it had no entries, the - current pf array member will be NULL. Calling the getter - initializes that member if needed, and we want that for - this use case. */ - rc = CWAL_RC_OOM; - break; - } - } - if(rc) goto end; - } - assert(!rc); - rc = s2_stash_set(se, kw->word, vImport) - /* Moves it to the top scope and implicitly makes it - vacuum-proof. */; - end: - cwal_scope_pop( se->e ) - /* Remember that: - 1) on success, vImport is in the stash (so it's alive) and - 2) this pop also cleans up pf on error. - */; - if(rc){ - /* vImport was destroyed by the popped scope */ - }else{ - /* vImport is held by/in se->stash. */ - v = se->cache.import = vImport; - s2_seal_container(vImport, 1); - } - }/* end of first-call initialization */ - if(!rc) *rv = v; - return rc; -} - - -/** - Tag type IDs for pragma(TAG ...). - - ACHTUNG: their order MUST match the entries of the s2PragmaWords - array (defined below) because we reference those by index with - these entries. -*/ -enum s2_pragma_type { - /* Sentinel value. Must be 0. */ - PRAGMA_NONE = 0, - /** - Get/set s2_engine::flags::exceptionStackTrace. - - */ - PRAGMA_EXCEPTION_STACKTRACE, - /** Evaluates to the operand's refcount. */ - PRAGMA_REFCOUNT, - /** Get/set s2_engine::sweepInterval */ - PRAGMA_SWEEP_INTERVAL, - /** Get/set s2_engine::vacuumInterval */ - PRAGMA_VACUUM_INTERVAL, - /** Get/set s2_engine::flags::traceSweeps */ - PRAGMA_TRACE_SWEEP, - /** Get/set s2_engine::flags::traceAssertions */ - PRAGMA_TRACE_ASSERT, - /** Get/set s2_engine::flags::traceTokenStack */ - PRAGMA_TRACE_TOKEN_STACK, - /** Get various build-time flags/options. */ - PRAGMA_BUILD_OPT - -}; -/** - Modes of operation for pragmas: - */ -enum s2_pragma_operand_mode { -/* pragma(TAG) */ -PRAGMA_OPERAND_NONE = 0, -/* pragma(TAG EXPR) */ -PRAGMA_OPERAND_EXPR, -/* pragma(TAG) or pragma(TAG EXPR) */ -PRAGMA_OPERAND_OPT_EXPR, -/* pragma(TAG IDENTIFIER) */ -PRAGMA_OPERAND_IDENTIFIER, -/* pragma(TAG) or pragma(TAG IDENTIFIER) */ -PRAGMA_OPERAND_OPT_IDENTIFIER -}; - -/** - Internal-only callback type for individual pragma() handlers. -*/ -typedef struct s2_pragma_word s2_pragma_word; - -/** - Callback for pragma() handlers. The 2nd parameter is the - pragma an whose behalf the handler is being called. The third - is NULL unless... - - If the pragma has an `opMode` of PRAGMA_OPERAND_IDENTIFIER then the - 3rd parameter is the token which contains the identifier. If opMode - is PRAGMA_OPERAND_OPT_IDENTIFIER then the 3rd parameter is NULL if - no identifier was provided. In either case, the 4th parameter - will have a value of NULL. - - If opMode is PRAGMA_OPERAND_EXPR then the 4th parameter is the - non-NULL result of that expression. If opMode is - PRAGMA_OPERAND_OPT_EXPR, the 4th parameter may be NULL, indicating - that no expression was provided (an empty expression is not - allowed, and this callback will never be called in that case). - - The pragma must write its result to the final parameter and return - 0 on success or a CWAL_RC_xxx or S2_RC_xxx value on error. Any - non-0 result other than CWAL_RC_EXCEPTION will likely be fatal to - the current script. -*/ -typedef int (*s2_pragma_f)(s2_engine *, struct s2_pragma_word const *, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv); -/** - pragma(...) state. -*/ -struct s2_pragma_word { - enum s2_pragma_type type; - /** Name of the pragma. */ - char const * word; - /** Length of this->word, in bytes. */ - cwal_size_t wordLen; - enum s2_pragma_operand_mode opMode; - s2_pragma_f call; -}; - -#define PRAGMA_F_DECL(P) static int s2_pragma_f_##P\ - (s2_engine *, struct s2_pragma_word const *, \ - s2_ptoken const *, cwal_value *, cwal_value **) -PRAGMA_F_DECL(refcount); -PRAGMA_F_DECL(stacktrace); -PRAGMA_F_DECL(svinterval); -PRAGMA_F_DECL(trace_sweep); -PRAGMA_F_DECL(trace_assert); -PRAGMA_F_DECL(trace_token_stack); -PRAGMA_F_DECL(build_opt); -#undef PRAGMA_F_DECL - -static const struct s2_pragma_word s2PragmaWords[] = { - /* - ACHTUNG: their order **MUST** match the entries of - the s2_pragma_word enum because we reference them - by index that way! - */ -{ PRAGMA_NONE, "", 0, - PRAGMA_OPERAND_NONE, 0}, -{ PRAGMA_EXCEPTION_STACKTRACE, "exception-stacktrace", 20, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_stacktrace}, -{ PRAGMA_REFCOUNT, "refcount", 8, - PRAGMA_OPERAND_EXPR, s2_pragma_f_refcount}, -{ PRAGMA_SWEEP_INTERVAL, "sweep-interval", 14, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_svinterval}, -{ PRAGMA_VACUUM_INTERVAL, "vacuum-interval", 15, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_svinterval}, -{ PRAGMA_TRACE_SWEEP, "trace-sweep", 11, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_sweep}, -{ PRAGMA_TRACE_ASSERT, "trace-assert", 12, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_assert}, -{ PRAGMA_TRACE_TOKEN_STACK, "trace-token-stack", 17, - PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_token_stack}, -{ PRAGMA_BUILD_OPT, "build-opt", 9, - PRAGMA_OPERAND_IDENTIFIER, s2_pragma_f_build_opt} -}; - -int s2_pragma_f_refcount(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - cwal_value * c; - S2_UNUSED_ARG(pw); - S2_UNUSED_ARG(tIdent); - assert(!tIdent); - assert(ev); - c = cwal_new_integer(se->e, (cwal_int_t)cwal_value_refcount(ev)); - if(c) *rv = c; - return c ? 0 : CWAL_RC_OOM; -} - -int s2_pragma_f_svinterval(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - int getVal = 0; - int * setVal = 0; - S2_UNUSED_ARG(tIdent); - assert(!tIdent); - switch(pw->type){ - case PRAGMA_SWEEP_INTERVAL: - if(ev) setVal = &se->sweepInterval; - getVal = se->sweepInterval; - break; - case PRAGMA_VACUUM_INTERVAL: - if(ev) setVal = &se->vacuumInterval; - getVal = se->vacuumInterval; - break; - default: - s2_fatal( CWAL_RC_ASSERT, "Not possible: invalid pragma type."); - } - if(setVal){ - cwal_int_t const x = cwal_value_get_integer(ev); - if(x<0){ - return cwal_exception_setf(se->e, CWAL_RC_RANGE, - "pragma(%s) does not allow negative values.", - pw->word); - } - *setVal = (int)x; - } - *rv = cwal_new_integer(se->e, (cwal_int_t)getVal); - return *rv ? 0 : CWAL_RC_OOM; -} - - -/** - Impl. of pragmas which get/set an int-type flag. A pointer to that - flag's origin is passed as the 2nd argument. The other parameters - are as for the s2_pragma_f() typedef. - - If asBool is true, the *what value and result are treated as a - booleans, rather than as integers. -*/ -static int s2_pragma_f_int_impl(s2_engine *se, - int * what, - char asBool, - s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value const * ev, cwal_value **rv){ - int const getVal = *what; - S2_UNUSED_ARG(tIdent); - assert(!tIdent); - if(ev){ - cwal_int_t const x = asBool - ? cwal_value_get_bool(ev) - : cwal_value_get_integer(ev); - if(x<0){ - return cwal_exception_setf(se->e, CWAL_RC_RANGE, - "pragma(%s) does not allow negative values.", - pw->word); - } - *what = (int)(asBool ? (x ? 1 : 0) : x); - } - if(asBool){ - *rv = getVal ? cwal_value_true() : cwal_value_false(); - }else{ - *rv = cwal_new_integer(se->e, (cwal_int_t)getVal); - } - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_pragma_f_stacktrace(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - return s2_pragma_f_int_impl(se, &se->flags.exceptionStackTrace, 1, - pw, tIdent, ev, rv); -} - - -int s2_pragma_f_trace_sweep(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - return s2_pragma_f_int_impl(se, &se->flags.traceSweeps, 0, - pw, tIdent, ev, rv); -} - -int s2_pragma_f_trace_assert(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - return s2_pragma_f_int_impl(se, &se->flags.traceAssertions, 0, - pw, tIdent, ev, rv); -} - -int s2_pragma_f_trace_token_stack(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - return s2_pragma_f_int_impl(se, &se->flags.traceTokenStack, 0, - pw, tIdent, ev, rv); -} - -/** - pragma(build-opt IDENTIFIER) - - TODO?: Make the identifier optional, in which case return an object - containing all of the known entries. We have that readily available - in cwal_build_info_object() but the property keys it uses differ - from the identifiers used by this pragma. -*/ -int s2_pragma_f_build_opt(s2_engine *se, s2_pragma_word const *pw, - s2_ptoken const * tIdent, - cwal_value * ev, cwal_value **rv){ - cwal_value * v = 0; - cwal_size_t nIden = 0; - char const * iden = s2_ptoken_cstr(tIdent, &nIden); - S2_UNUSED_ARG(pw); - S2_UNUSED_ARG(ev); - assert(tIdent); - assert(!ev); - switch(s2__keyword_perfect_hash(tIdent)){ - /* First integer values... */ -#define W(X) v = cwal_new_integer(se->e, (cwal_int_t)X); \ - if(!v) return CWAL_RC_OOM; \ - break - case 0x028b1cb7: W(CWAL_OBASE_ISA_HASH); - case 0x0052e906: W(CWAL_SIZE_T_BITS); -#undef W - /* Then string values... */ -#define W(X) v = cwal_new_string_value(se->e, X, cwal_strlen(X)); \ - if(!v) return CWAL_RC_OOM; \ - break - case 0x0295ed40: W(CWAL_VERSION_STRING); - case 0x000281a8: W(CWAL_CFLAGS); - case 0x000a2338: W(CWAL_CPPFLAGS); - /* Then values which may or may not be #define'd... */ -#undef W - case 0x000007e8: -#if defined(DEBUG) - v = cwal_value_true(); -#else - v = cwal_value_false(); -#endif - break; - case 0x09740cb5: -#if defined(S2_AMALGAMATION_BUILD) - v = cwal_value_true(); -#else - v = cwal_value_undefined(); -#endif - break; - case 0x00014d8e: -#if defined(S2_OS_UNIX) - v = cwal_value_true(); -#else - v = cwal_value_undefined(); -#endif - break; - case 0x000a7660: -#if defined(S2_OS_WINDOWS) - v = cwal_value_true(); -#else - v = cwal_value_undefined(); -#endif - } - if(!v){ - return s2_throw_ptoker(se, se->currentScript, CWAL_RC_MISUSE, - "Unknown pragma build-opt identifier: %.*s", - (int)nIden, iden); - } - *rv = v; - return 0; -} - - -/** - If the given ptoken resolves to a pragma(WORD) identifier, its - entry is returned, else NULL is returned. This is an O(1) search. - See s2_ptoken_keyword() for more details. - */ -static s2_pragma_word const * s2_ptoken_pragma( s2_ptoken const * pt ){ - cwal_size_t const tlen = s2_ptoken_len(pt); - s2_pragma_word const * rc; -#define W(X,E) rc = tlen==(cwal_size_t)sizeof(X)-1 && \ - 0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1) \ - ? &s2PragmaWords[E] : NULL; \ - assert(rc ? E==rc->type : 1); return rc - switch(s2__keyword_perfect_hash(pt)){ - /* Generated by s2-keyword-hasher.s2 (or equivalent): */ - case 0x00008bd2: W("refcount",PRAGMA_REFCOUNT); - case 0x00011029: W("build-opt",PRAGMA_BUILD_OPT); - case 0x09022910: W("exception-stacktrace",PRAGMA_EXCEPTION_STACKTRACE); - case 0x00245262: W("sweep-interval",PRAGMA_SWEEP_INTERVAL); - case 0x00045dbf: W("trace-sweep",PRAGMA_TRACE_SWEEP); - case 0x004732eb: W("vacuum-interval",PRAGMA_VACUUM_INTERVAL); - case 0x0008b1a6: W("trace-assert",PRAGMA_TRACE_ASSERT); - case 0x0117cb00: W("trace-token-stack",PRAGMA_TRACE_TOKEN_STACK); - - default: break; - } -#undef W - return NULL; -} - -int s2_keyword_f_pragma( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc; - s2_pragma_word const * ptag = 0; - s2_ptoker prBody = s2_ptoker_empty; - s2_ptoken tIdent = s2_ptoken_empty; - s2_ptoken tPTag = s2_ptoken_empty; - s2_ptoken const * pIdent = 0; - s2_ptoker const * oldScript = se->currentScript; - cwal_size_t pTagLen = 0; - cwal_value * xrv = 0 /* pragma(tag EXPR) result for EXPR */; - cwal_value * crv = 0 /* result of the pragma */; - cwal_value * vHolder = 0; - /** - Ideas: - - pragma(blah) - - There's all sorts of stuff we could potentially do with (blah), - potentially requiring different syntaxes, e.g. (x=y), (x), (x y). - - e.g.: - - pragma(stacktrace false) - - To enable/disable collection of stack traces (a major cause of - line-counting, which is slow). For most testing purposes, stack - traces are not needed - they're needed primarily in app-level - code. - - It would seem we need both getter and setter forms for some - constructs. - */ - rc = s2_next_token( se, pr, 0, 0 ); - if(rc) return rc; - else if(S2_T_ParenGroup!=pr->token.ttype){ - return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, - "Expecting (...) after '%s'.", - kw->word); - } - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - } - - rc = s2_ptoker_sub_from_toker(pr, &prBody); - if(!rc){ - prBody.flags |= S2_T10N_F_IDENTIFIER_DASHES; - rc = s2_next_token(se, &prBody, 0, 0); - prBody.flags &= ~S2_T10N_F_IDENTIFIER_DASHES; - } - if(rc) goto end; - else if(S2_T_Identifier!=prBody.token.ttype){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Expected pragma command in %s(...).", - kw->word); - goto end; - } - - se->currentScript = &prBody; - tPTag = prBody.token; - pTagLen = s2_ptoken_len(&tPTag); - assert(pTagLen > 0); - ptag = s2_ptoken_pragma(&tPTag); - if(!ptag){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Unknown pragma in %s(%.*s ...).", - kw->word, (int)pTagLen, s2_ptoken_begin(&tPTag)); - goto end; - } - assert(ptag->word && *ptag->word); - assert(ptag->call); - - rc = s2_next_token(se, &prBody, 0, 0); - if(rc) goto end; - - switch(ptag->opMode){ - case PRAGMA_OPERAND_NONE: - /* pragma(tag) */ - if(!s2_ptoker_is_eof(&prBody)){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Unexpected token in pragma %s(%.*s ...).", - kw->word, (int)pTagLen, s2_ptoken_begin(&tPTag)); - break; - } - break; - case PRAGMA_OPERAND_OPT_EXPR: - /* pragma(tag) or pragma(tag EXPR) */ - if(s2_ptoker_is_eof(&prBody)){ - break; - } - CWAL_SWITCH_FALL_THROUGH; - case PRAGMA_OPERAND_EXPR:{ - /* pragma(tag EXPR) */ - s2_ptoker_putback(&prBody); - rc = s2_eval_expr_impl(se, &prBody, 0, 0, &xrv); - if(rc) goto end; - else if(!xrv){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "Empty expression is not permitted here."); - goto end; - } - break; - } - case PRAGMA_OPERAND_OPT_IDENTIFIER: - /* pragma(tag) or pragma(tag IDENTIFIER) */ - if(s2_ptoker_is_eof(&prBody)){ - break; - } - CWAL_SWITCH_FALL_THROUGH; - case PRAGMA_OPERAND_IDENTIFIER: - /* pragma(tag IDENTIFIER) */ - if(S2_T_Identifier != prBody.token.ttype){ - rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX, - "The %s(%.*s ...) pragma requires an " - "IDENTIFIER argument", - kw->word, pTagLen, s2_ptoken_begin(&tPTag)); - goto end; - } - tIdent = prBody.token; - pIdent = &tIdent; - break; - default: - s2_fatal( CWAL_RC_ASSERT, "Cannot happen: unknown pragma operand type."); - } - - if(xrv){ - vHolder = cwal_new_unique(se->e, xrv); - if(!vHolder){ - rc = CWAL_RC_OOM; - cwal_refunref(xrv); - xrv = 0; - goto end; - } - cwal_value_ref(vHolder); - cwal_value_make_vacuum_proof(vHolder, 1); - } - rc = ptag->call(se, ptag, pIdent, xrv, &crv); - - end: - s2_ptoker_finalize(&prBody); - if(crv){ - cwal_value_ref(crv) /* needed in case crv == xrv */; - if(rc) cwal_value_unref(crv); - else *rv = crv; - cwal_value_unref(vHolder); - if(!rc) cwal_value_unhand(crv); - crv = 0; - }else{ - if(!rc){ - *rv = cwal_value_undefined(); - } - cwal_value_unref(vHolder); - } - se->currentScript = oldScript; - return rc; -} - -int s2_keyword_f_ukwd( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - s2_ukwd * uk = 0; - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - } - uk = s2__ukwd(se); - S2_UNUSED_ARG(pr); - if(uk && uk->h){ - cwal_value * v; - v = cwal_hash_search(uk->h, kw->word, kw->wordLen); - if(v){ - *rv = v; - }else{ - rc = cwal_exception_setf(se->e, CWAL_RC_NOT_FOUND, - "Could not find %.*s keyword object.", - (int)kw->wordLen, kw->word); - } - } - return rc; -} - -/** - If pt's [begin,end) range corresponds to a keyword, its entry from - S2_KWDS is returned, else NULL is returned. - - This is an O(1) search, requiring, at most, generation of 1 hash - code and (on a hash match) 1 string comparison. -*/ -static s2_keyword const * s2__cstr_keyword( char const * name, - cwal_size_t nameLen){ - s2_ptoken pt = s2_ptoken_empty; - s2_ptoken_begin_set(&pt, name); - s2_ptoken_end_set(&pt, name + nameLen); - return s2_ptoken_keyword(&pt)/*NOT keyword2()!*/; -} - -#if 0 -static s2_keyword const * s2__value_keyword( s2_engine * se, cwal_value * v ){ - cwal_size_t nameLen = 0; - char const * name = cwal_value_get_cstr(v, &nameLen); - if(name){ - s2_ptoken pt = s2_ptoken_empty; - s2_ptoken_begin_set(pt, name); - s2_ptoken_end_set(&pt, name + nameLen); - return s2_ptoken_keyword2(se, &pt); - }else{ - return 0; - } -} -#endif - -static int s2_cstr_is_identifier(char const * z, cwal_size_t len){ - char const * tail = 0; - if(!s2_read_identifier(z, z+len, &tail)) return 0; - return tail==z+len; -} - -/** - Creates a UKWD entry for the given key/value. - - See the public API docs for s2_define_ukwd() for the details, with - the addition that CWAL_RC_TYPE is returned if k is not a stringable - type. -*/ -static int s2_define_ukwd_v(s2_engine * se, cwal_value * k, cwal_value * v){ - s2_ukwd * uk = s2__ukwd(se); - s2_keyword * kw = 0; - int rc; - cwal_size_t nameLen = 0; - char const * name; - cwal_size_t const maxNameLen = 100 - /*This upper limit is essentially arbitrary, but we *must* reject - anything longer than an unsigned short because kw->wordLen will - hold the length. Also, long names have longer comparison times, - and we frequently check the UKWD list during evaluation, so - keeping them shorter is a good thing.*/; - assert(v); - if(cwal_value_undefined()==v){ - return s2_engine_err_set(se, CWAL_RC_UNSUPPORTED, - "The undefined value is not legal for use as " - "a define() value."); - } - name = cwal_value_get_cstr(k, &nameLen); - if(!name){ - return s2_engine_err_set(se, CWAL_RC_TYPE, - "define() requires a string key."); - }else if(!nameLen){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "define() key name may not be empty"); - }else if(nameLen > maxNameLen){ - return s2_engine_err_set(se, CWAL_RC_RANGE, - "define() key name is too long (max length=%d).", - (int)maxNameLen); - }else if(!s2_cstr_is_identifier(name, nameLen)){ - return s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "define() key must be a legal identifier."); - }else if(s2__cstr_keyword(name, nameLen)){ - return s2_engine_err_set(se, CWAL_RC_ACCESS, - "define() may not override a built-in " - "keyword."); - } - else if(!uk){ - uk = cwal_malloc( se->e, sizeof(s2_ukwd)); - if(!uk) return s2_engine_err_set(se, CWAL_RC_OOM, 0); - memset(uk, 0, sizeof(s2_ukwd)); - se->ukwd = uk; - } - if(!uk->h){ - cwal_value * hv; - uk->h = cwal_new_hash(se->e, 13); - if(!uk->h) return s2_engine_err_set(se, CWAL_RC_OOM, 0); - hv = cwal_hash_value(uk->h); - cwal_value_ref(hv); - rc = s2_stash_set_v(se, hv, hv) - /* To give the hash permanent lifetime */; - cwal_value_unref(hv); - if(rc){ - uk->h = 0; - return rc; - } - } - if(uk->count<=uk->alloced){ - unsigned const n = uk->alloced ? uk->alloced*3/2 : 10; - void * m = cwal_realloc(se->e, uk->list, n * sizeof(s2_keyword)); - if(!m) return s2_engine_err_set(se, CWAL_RC_OOM, 0); - uk->list = (s2_keyword *)m; - uk->alloced = n; - } - { - rc = cwal_hash_insert_with_flags_v(uk->h, k, v, 0, CWAL_VAR_F_CONST); - if(rc){ - return CWAL_RC_ALREADY_EXISTS==rc - ? s2_engine_err_set(se, rc, "define() key already exists.") - : s2_engine_err_set(se, rc, "Hash insertion of define() key " - "failed: %s", - cwal_rc_cstr(rc)) - ; - } - kw = &uk->list[uk->count]; - kw->wordLen = (unsigned short)nameLen; - kw->word = name - /* We know those bytes now live forever in uk->h, - so there's no lifetime issue. */; - ++uk->count; - kw->id = S2_T_KeywordUKWD; - kw->word = name; - kw->call = s2_keyword_f_ukwd; - kw->allowEOLAsEOXWhenLHS = 0; - qsort( uk->list, uk->count, sizeof(s2_keyword), cmp_ukwd_kw ); - cwal_hash_grow_if_loaded(uk->h, 0.7)/*ignore error - not fatal*/; - return 0; - } -} - -int s2_define_ukwd(s2_engine * se, char const * name, - cwal_int_t nameLen, cwal_value * v){ - int rc; - cwal_value * const k = - cwal_new_string_value(se->e, name, - nameLen>0 - ? (cwal_midsize_t)nameLen - : cwal_strlen(name)); - if(k){ - cwal_value_ref(k); - rc = s2_define_ukwd_v(se, k, v); - cwal_value_unref(k); - }else{ - rc = s2_engine_err_set(se, CWAL_RC_OOM, 0); - } - return rc; -} - -/** - cwal_callback_f() impl which wraps s2_define_ukwd(). Script-side - signature: - - - mixed define(string name[, mixed value]) - - If called with one argument, it returns the defined value for the - given key (or the undefined value), else it defines the given - key/value pair. - - It returns its 2nd argument. -*/ -static int s2_cb_define_ukwd( cwal_callback_args const * args, cwal_value **rv ){ - s2_engine * se = s2_engine_from_args(args); - if((!args->argc || args->argc>2) - && !cwal_value_is_string(args->argv[0])){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting (string name[, value]) arguments."); - } - if(1==args->argc){ - s2_ukwd * uk = s2__ukwd(se); - if(uk){ - cwal_value * v = cwal_hash_search_v(uk->h, args->argv[0]); - *rv = v ? v : cwal_value_undefined(); - }else{ - *rv = cwal_value_undefined(); - } - return 0; - }else{ - int rc = s2_define_ukwd_v(se, args->argv[0], args->argv[1]); - if(rc){ - if(s2__err(se).code == rc){ - rc = s2_throw_err(se, 0, 0, 0, 0); - } - } - else{ - *rv = args->argv[1]; - } - return rc; - } -} - -int s2_keyword_f_define( s2_keyword const * kw, s2_engine * se, - s2_ptoker * pr, cwal_value **rv){ - int rc = 0; - cwal_value * v = se->skipLevel>0 - ? cwal_value_undefined() - : se->cache.define; - if(!v){ - cwal_value * vDef = 0; - S2_UNUSED_ARG pr; - vDef = cwal_new_function_value(se->e, s2_cb_define_ukwd, 0, 0, 0); - if(!vDef){ - return CWAL_RC_OOM; - } - cwal_value_ref(vDef); - rc = s2_stash_set(se, kw->word, vDef) - /* Moves it to the top scope and implicitly makes it - vacuum-proof. */; - cwal_value_unref(vDef); - if(!rc){ - /* vDef is held by/in se->stash. */ - v = se->cache.define = vDef; - s2_seal_container(vDef, 1); - } - }/* end of first-call initialization */ - if(!rc) *rv = v; - return rc; -} -#undef s2__dump_token -#undef s2__ukwd -#undef s2__ukwd_key -#undef s2__keyword_perfect_hash -#undef s2__err -#undef MARKER -/* end of file eval.c */ -/* start of file func.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \ - assert(se) - -#define THIS_FUNCTION \ - cwal_function * self = 0; \ - ARGS_SE; \ - self = cwal_value_function_part(se->e, args->self); \ - if(!self){ \ - return s2_throw( se, CWAL_RC_TYPE, \ - "'this' is-not-a Function." ); \ - } (void)0 - -static int s2_cb_func_source_code( cwal_callback_args const * args, cwal_value **rv ){ - s2_func_state * fst; - THIS_FUNCTION; - fst = (s2_func_state*)cwal_function_state_get(self, &s2_func_state_empty); - if(fst){ - cwal_value * src = fst->vSrc; - if(src || fst->flags){ - if(src){ - *rv = src; - }else{ - static const char * const shortestSrc = "proc(){}"; - static const cwal_size_t shortestLen = 8/*^^^ strlen*/; - assert(fst->flags && "Expecting empty-body script function"); - *rv = cwal_new_xstring_value(args->engine, shortestSrc, shortestLen) - /* trivia: because this string's so short, we don't actually - save any memory here by using an x-string. That - particular string won't get interned, so we've got about - the same memory cost using an x-string or a normal - string */; - } - return *rv ? 0 : CWAL_RC_OOM; - }else{ - return cwal_exception_setf(args->engine, CWAL_RC_ERROR, - "Missing expected reference to this function's source code!"); - } - }else{ - /* Non-script function */ - return 0; - } -} - -cwal_value * s2_func_import_props( s2_engine * se, cwal_value const *theFunc, - s2_func_state * fst ){ - cwal_value * props; - assert(se && theFunc && fst); - props = fst->vImported; - if(!props){ - props = cwal_new_object_value(se->e); - if(!props) return 0; - s2_value_to_lhs_scope(theFunc, props); - cwal_value_ref(props); - cwal_value_prototype_set(props, NULL); - fst->vImported = props; - } - return props; -} - -/** - Implements Function.importSymbols(). -*/ -static int s2_cb_func_import_symbols( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_value * props /* where we store the symbols */; - uint16_t i, startAt = 0; - s2_func_state * fst; - THIS_FUNCTION; - if(!args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting at least one symbol name to import."); - } - fst = s2_func_state_for_func( self ); - if(!fst){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "This is not a script function, " - "so cannot import symbols."); - } - props = s2_func_import_props(se, cwal_function_value(self), fst); - if(!props) return CWAL_RC_OOM; - if(cwal_value_is_bool(args->argv[0])){ - /* initial bool explicitly specifies whether or not to clear old - properties before importing the new ones. */ - ++startAt; - if(cwal_value_get_bool(args->argv[0])){ - cwal_props_clear( props ); - } - }else{ - cwal_props_clear( props ); - } - for( i = startAt; !rc && (i < args->argc); ++i ){ - cwal_value * key; - cwal_value * v; - /* cwal_array * ary; */ - key = args->argv[i]; -#if 0 - /* TODO?: treat as a list of symbols. */ - if((ary = cwal_value_array_part(key))){ - continue; - } -#endif - if(cwal_props_can(key)){ - rc = cwal_props_copy( key, props ); - continue; - } - v = s2_var_get_v(se, -1, key); - if(!v){ - cwal_string const * str = cwal_value_get_string(key); - char const * cstr = str ? cwal_string_cstr(str) : NULL; - return s2_throw(se, CWAL_RC_NOT_FOUND, - "Could not resolve symbol '%.*s' " - "in the current scope stack.", - str ? (int)cwal_string_length_bytes(str) : 0, - cstr ? cstr : "" - /* Reminder: %.*s is necessary for the - case of non-NUL-terminated - x-strings. */ - ); - } - rc = cwal_prop_set_v( props, key, v ); - } - if(!rc){ - /* *rv = cwal_function_value(args->callee); */ - *rv = - args->self - /* cwal_function_value(self) */; - } - return rc; -} - - -/** - Script usage: - - Function.apply(thisObject, array[arg1...argN]) -*/ -static int s2_cb_func_apply( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */; - cwal_array * theList = 0 /* args->argv[1], the list of arguments to apply() */; - THIS_FUNCTION; - if(!args->argc || args->argc>2){ - return s2_throw(se, CWAL_RC_MISUSE, - "'apply' expects (Value [, Array]) argument(s)."); - } - fwdSelf = args->argv[0]; - if(args->argc>1){ - theList = cwal_value_array_part(se->e, args->argv[1]); - if(!theList){ - return s2_throw(se, CWAL_RC_TYPE, - "Second argument to 'apply' must be an array."); - } - } - return theList - ? cwal_function_call_array( 0, self, fwdSelf, rv, theList ) - : cwal_function_call( self, fwdSelf, rv, 0, 0 ); -} - -static int s2_cb_func_call( cwal_callback_args const * args, cwal_value **rv ){ - THIS_FUNCTION; - if(!args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, - "'call' expects (Value [, ...]) argument(s)."); - } - return cwal_function_call( self, args->argv[0], rv, - args->argc-1, args->argv+1 ); -} - - -cwal_value * s2_prototype_function( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_FUNCTION ); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_FUNCTION, proto ); - if(!rc) rc = s2_prototype_stash(se, "Function", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_FUNCTION)); - /* MARKER(("Setting up OBJECT prototype.\n")); */ - - { - s2_func_def const funcs[] = { - S2_FUNC2("sourceCode", s2_cb_func_source_code), - S2_FUNC2("importSymbols", s2_cb_func_import_symbols), - S2_FUNC2("call", s2_cb_func_call), - S2_FUNC2("apply", s2_cb_func_apply), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - - { - /* Function.bind() impl. */ - char const * src = - "proc(x){" - "affirm typeinfo(isfunction this);" - "return proc(){ return f.apply(x,argv) }" - "using {x, f:this} " - "}"; - int const srcLen = (int)cwal_strlen(src); - rc = s2_set_from_script(se, src, srcLen, proto, "bind", 4); - if(rc) goto end; - } - - end: - return rc ? NULL : proto; -} - - -#undef MARKER -#undef THIS_FUNCTION -#undef ARGS_SE -/* end of file func.c */ -/* start of file fs.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include -#include -#include -#include - -/** -#ifndef S2_HAVE_REALPATH -# if defined(_BSD_SOURCE) -# define S2_HAVE_REALPATH 1 -# elif defined(_XOPEN_SOURCE) && _XOPEN_SOURCE>=500 -# define S2_HAVE_REALPATH 1 -# elif defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED) -# if _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED -# define S2_HAVE_REALPATH 1 -# endif -# endif -# ifndef S2_HAVE_REALPATH -# define S2_HAVE_REALPATH 0 -# endif -#endif -*/ - -#if S2_HAVE_REALPATH -# include -#endif - -#if S2_HAVE_STAT || S2_HAVE_MKDIR -# include -# include -#endif - - -#ifdef S2_OS_UNIX -# include /* W_OK, R_OK */ -# include -# include -#else -# include -#endif - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -int s2_getcwd( cwal_engine * e, cwal_buffer * tgt ){ -#ifdef S2_OS_WINDOWS - if(e || tgt){/*unused*/} - return CWAL_RC_UNSUPPORTED; -#else - cwal_size_t const bufSize = 1024 * 2; - int rc = cwal_buffer_reserve( e, tgt, tgt->used + bufSize ); - if(rc) return rc; - else if( ! getcwd((char *)(tgt->mem+tgt->used), (size_t)bufSize) ){ - rc = s2_errno_to_cwal_rc( errno, CWAL_RC_IO); - }else{ - tgt->used += cwal_strlen( (char const *)(tgt->mem+tgt->used) ); - assert( tgt->used <= tgt->capacity ); - } - return rc; -#endif -} - -int s2_mkdir( char const * name, int mode, char const ** errMsg ){ -#if !S2_HAVE_MKDIR - if(name || mode){/*unused*/} - if(errMsg) *errMsg = "mkdir() is not available in this build."; - return CWAL_RC_UNSUPPORTED; -#else - int rc = mkdir(name, (mode_t)mode); - if(rc){ - if(errMsg) *errMsg = strerror(errno); - rc = s2_errno_to_cwal_rc(errno, CWAL_RC_IO); - } - return rc; -#endif -} - -int s2_mkdir_p( char const * name, int mode, char const ** errMsg ){ -#if !S2_HAVE_MKDIR - if(name || mode){/*unused*/} - if(errMsg) *errMsg = "mkdir() is not available in this build."; - return CWAL_RC_UNSUPPORTED; -#else - int rc = 0; - char const * t; - cwal_size_t tLen = 0; - s2_path_toker pt = s2_path_toker_empty; - char buf[1024 * 2] = {0}; - char * bufPos = buf; - char const * bufEnd = buf + sizeof(buf); - int dirCount = 0; - char const leadingSlash = '/'==*name ? 1 : 0; - s2_path_toker_init(&pt, name, cwal_strlen(name)); - pt.separators = "/"; - while(0==s2_path_toker_next(&pt, &t, &tLen)){ - if(!tLen) continue /* adjacent dir separators */; - else if(bufPos+tLen+1 >= bufEnd){ - if(errMsg) *errMsg = "Directory path is too long."; - return CWAL_RC_RANGE; - } - if((0==dirCount && leadingSlash) - || dirCount>0){ - *bufPos++ = '/'; - } - ++dirCount; - memcpy(bufPos, t, (size_t)tLen); - bufPos += tLen; - *bufPos = 0; - if(!s2_is_dir(buf, 0)){ - rc = s2_mkdir(buf, mode, errMsg); - if(rc) break; - } - } - if(!rc && !dirCount){ - rc = CWAL_RC_MISUSE; - if(errMsg) *errMsg = "No directory names found in the given string."; - } - return rc; -#endif -} - -int s2_cb_mkdir( cwal_callback_args const * args, cwal_value ** rv ){ -#if !S2_HAVE_MKDIR - if(rv){/*unused*/} - return s2_cb_throw(args, CWAL_RC_UNSUPPORTED, - "mkdir() is not available in this build."); -#else - int rc; - char const * p; - cwal_size_t pLen = 0; - cwal_int_t mode = 0750; - char const *errMsg = 0; - uint16_t ndxMode = 0/* args->argv index of the permissions mode argument, 0 for none */; - char mkp = 0; - char dirBuf[1024 * 2] - /* We copy the given path to a char buffer only so that we can - ensure that it is NUL-terminated (X-/Z-strings can be created - without a NUL terminator). We "could" instead use the - s2_engine::buffer for this purpose, rather than hard-coding a - length limit. */; - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_WRITE | S2_DISABLE_FS_STAT)) ) return rc; - else if(!args->argc || args->argc>3) goto misuse; - else if(1==args->argc){ - /* (dir) */ - }else if(cwal_value_is_bool(args->argv[1])){ - /* (dir, bool [, mode]); */ - mkp = cwal_value_get_bool(args->argv[1]); - ndxMode = args->argc>2 ? 2 : 0; - }else{ - /* (dir, mode) */ - ndxMode = 1; - } - p = cwal_value_get_cstr(args->argv[0], &pLen); - if(!p || !pLen) goto misuse; - else if(pLen >= (cwal_size_t)sizeof(dirBuf)){ - return s2_cb_throw(args, CWAL_RC_RANGE, "Dir name is too long."); - } - if(ndxMode){ - if(!cwal_value_is_number(args->argv[ndxMode])) goto misuse; - mode = cwal_value_get_integer(args->argv[ndxMode]); - } - *rv = 0; - memcpy(dirBuf, p, (size_t)pLen); - if(s2_is_dir(dirBuf, 0)){ - return 0; - } - rc = mkp - ? s2_mkdir_p(dirBuf, (int)mode, &errMsg) - : s2_mkdir(dirBuf, (int)mode, &errMsg); - if(rc){ - rc = s2_cb_throw(args, s2_errno_to_cwal_rc(errno, CWAL_RC_IO), - "mkdir() failed with errno %d: %s", - errno, errMsg); - } - return rc; - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "mkdir() requires (string dir [,bool createParentDirs] " - "[,int unixPermissions]) arguments, " - "with a non-empty directory name."); -#endif -} - - -int s2_cb_getcwd( cwal_callback_args const * args, cwal_value ** rv ){ - s2_engine * se = s2_engine_from_args(args); - cwal_buffer * buf = &se->buffer; - cwal_size_t const pos = se->buffer.used; - int rc; - char const addSep = args->argc ? cwal_value_get_bool(args->argv[0]) : 0; - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc; - rc = s2_getcwd( args->engine, buf ); - if(rc) rc = s2_cb_throw(args, rc, "getcwd() failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - else{ - if(addSep){ - rc = cwal_buffer_append( args->engine, - buf, - S2_DIRECTORY_SEPARATOR, - sizeof(S2_DIRECTORY_SEPARATOR)- - sizeof(S2_DIRECTORY_SEPARATOR[0]) ); - } - if(!rc){ - *rv = cwal_new_string_value(args->engine, - (char const *)(buf->mem + pos), - (cwal_size_t)(buf->used - pos)); - rc = *rv ? 0 : CWAL_RC_OOM; - } - } - if(buf->mem){ - buf->used = pos; - buf->mem[buf->used] = 0; - } - return rc; -} - - -const s2_fstat_t s2_fstat_t_empty = s2_fstat_t_empty_m; - -/* - Use _stati64 rather than stat on windows, in order to handle files - larger than 2GB. -*/ -#if defined(S2_OS_WINDOWS) && (defined(__MSVCRT__) || defined(_MSC_VER)) -# undef stat -# define stat _stati64 -#endif -/* - On Windows S_ISLNK always returns FALSE. -*/ -#if !defined(S_ISLNK) -# define S_ISLNK(x) (0) -#endif - -enum { -/** - Internal buffer sized used for various filesystem-related - routines. -*/ -S2_FILENAME_BUF_SIZE = 1024 * 2 -}; - -int s2_fstat( char const * filename, - cwal_size_t fnLen, - s2_fstat_t * tgt, - char derefSymlinks ){ -#if !S2_HAVE_STAT || defined(S2_OS_WINDOWS) - if(filename || fnLen || tgt || derefSymlinks){/*unused params*/} - return CWAL_RC_UNSUPPORTED; -#else - int rc; - if(!filename || !tgt) rc = CWAL_RC_MISUSE; - else if(!*filename || !fnLen) rc = CWAL_RC_RANGE; - else if(fnLen >= (cwal_size_t)S2_FILENAME_BUF_SIZE) rc = CWAL_RC_RANGE; - else{ - struct stat buf; - char isCwalRc = 0; - char fnBuf[S2_FILENAME_BUF_SIZE] = {0}; - memcpy( fnBuf, filename, (size_t)fnLen ); - fnBuf[fnLen] = 0; - if( derefSymlinks ){ - rc = stat(fnBuf, &buf); - }else{ -#if S2_HAVE_LSTAT - rc = lstat(fnBuf, &buf); -#else - rc = CWAL_RC_UNSUPPORTED; - isCwalRc = 1; -#endif - } - if(rc){ - if(!isCwalRc){ - rc = s2_errno_to_cwal_rc(errno, CWAL_RC_IO); - } - }else if(tgt){ - *tgt = s2_fstat_t_empty; - tgt->ctime = (uint64_t)buf.st_ctime; - tgt->mtime = (uint64_t)buf.st_mtime; - tgt->size = (uint64_t)buf.st_size; - tgt->perm = buf.st_mode & 0777 /* Unix file permissions are only the bottom 9 bits */; -#define TCHECK(SMACRO, TYPE) if(SMACRO(buf.st_mode)) tgt->type = S2_FSTAT_TYPE_ ## TYPE - TCHECK(S_ISDIR,DIR); - else TCHECK(S_ISREG,REGULAR); -#if defined(S_ISLNK) - else TCHECK(S_ISLNK,LINK); -#endif -#if defined(S_ISSOCK) - else TCHECK(S_ISSOCK,SOCKET); -#endif -#if defined(S_ISCHR) - else TCHECK(S_ISCHR,CHAR); -#endif -#if defined(S_ISBLK) - else TCHECK(S_ISBLK,BLOCK); -#endif -#if defined(S_ISFIFO) - else TCHECK(S_ISFIFO,FIFO); -#endif - else tgt->type = S2_FSTAT_TYPE_UNKNOWN; -#undef TCHECK - } - } - return rc; -#endif -} - -int s2_fstat_to_object( s2_engine * se, s2_fstat_t const * fst, cwal_value ** rv ){ - cwal_value * obj = 0; - cwal_value * v = 0; - cwal_value * keysV = 0; - cwal_array * keysA = 0; - char const * stashKey = "stat()keys"; - int rc = 0; - enum { - keyUNKNOWN = 0, - keyREGULAR, - keyDIR, - keyLINK, - keyBLOCK, - keyCHAR, - keyFIFO, - keySOCKET, - keyMTIME, - keyCTIME, - keySIZE, - keyPERM, - keyTYPE, - keyEND - }; - keysV = s2_stash_get(se, stashKey); - if(keysV){ - keysA = cwal_value_get_array(keysV); - }else{ - /* set up key names cache... */ - cwal_value * key = 0; - keysV = cwal_new_array_value(se->e); - if(!keysV){ - rc = CWAL_RC_OOM; - goto end; - } - keysA = cwal_value_get_array(keysV); - cwal_value_ref(keysV); - rc = s2_stash_set(se, stashKey, keysV); - cwal_value_unref(keysV); - if(rc) goto end; - else{ - assert(cwal_value_refcount(keysV)>0 && "stash is holding a ref"); - } - rc = cwal_array_reserve(keysA, (cwal_size_t)keyEND); - if(rc) goto end; -#define KEY(TYPE,STR) \ - key = cwal_new_string_value(se->e, STR, cwal_strlen(STR)); \ - if(!key) { rc = CWAL_RC_OOM; goto end; } \ - cwal_value_ref(key); \ - rc = cwal_array_set( keysA, (cwal_size_t)key##TYPE, key ); \ - cwal_value_unref(key); \ - key = 0; \ - if(rc) goto end - KEY(UNKNOWN,"unknown"); - KEY(REGULAR,"file"); - KEY(DIR,"dir"); - KEY(LINK,"link"); - KEY(BLOCK,"block"); - KEY(CHAR,"char"); - KEY(FIFO,"fifo"); - KEY(SOCKET,"socket"); - KEY(MTIME,"mtime"); - KEY(CTIME,"ctime"); - KEY(SIZE,"size"); - KEY(PERM,"perm"); - KEY(TYPE,"type"); -#undef KEY - }/* end key stash setup */ - - assert(!rc); - - obj = cwal_new_object_value(se->e); - if(!obj) { rc = CWAL_RC_OOM; goto end; } - cwal_value_ref(obj); - -#define VCHECK -#define VSET(KEY) \ - if(!v) { rc = CWAL_RC_OOM; goto end; } \ - cwal_value_ref(v); \ - rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)key##KEY), v); \ - cwal_value_unref(v); \ - v = 0; \ - if(rc) goto end -#define INTVAL(KEY,IV) \ - v = cwal_new_integer(se->e, (cwal_int_t)(IV)); \ - VSET(KEY); - - INTVAL(MTIME,fst->mtime); - INTVAL(CTIME,fst->ctime); - INTVAL(SIZE,fst->size); - INTVAL(PERM,fst->perm); -#undef INTVAL -#undef VCHECK -#undef VSET - - switch(fst->type){ -#define CASE(TYPE) case S2_FSTAT_TYPE_##TYPE: \ - v = cwal_array_get(keysA, (cwal_size_t)key##TYPE); \ - assert(v); \ - rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)keyTYPE), v); \ - break - CASE(REGULAR); - CASE(DIR); - CASE(LINK); - CASE(BLOCK); - CASE(CHAR); - CASE(FIFO); - CASE(SOCKET); - default: - v = cwal_array_get(keysA, (cwal_size_t)keyUNKNOWN); - assert(v); - rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)keyTYPE), v); - break; - } -#undef CASE - end: - if(rc){ - cwal_value_unref(obj); - }else{ - *rv = obj; - cwal_value_unhand(obj); - } - return rc; -} - -int s2_cb_fstat( cwal_callback_args const * args, cwal_value ** rv ){ - int rc = 0; - s2_fstat_t stbuf = s2_fstat_t_empty; - cwal_size_t fnLen = 0; - char const * fn = args->argc - ? cwal_value_get_cstr( args->argv[0], &fnLen ) - : 0; - char derefSymlinks = 1; - char doQuickCheck = 0; - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc; - *rv = 0; - if(!fn) { - rc = s2_cb_throw( args, CWAL_RC_MISUSE, - "Expecting string (filename) argument."); - goto end; - } - if(args->argc>1){ - if(cwal_value_undefined()==args->argv[1]){ - doQuickCheck = 1; - if(args->argc>2) derefSymlinks = cwal_value_get_bool(args->argv[2]); - }else{ - derefSymlinks = cwal_value_get_bool(args->argv[1]); - } - } - assert(!rc); - rc = s2_fstat( fn, fnLen, &stbuf, derefSymlinks ); - if(rc){ - if(doQuickCheck){ - *rv = cwal_value_false(); - rc = 0; - }else{ - rc = s2_cb_throw(args, rc, "stat(%.*s) failed with code %d (%s)", - (int)fnLen, fn, rc, cwal_rc_cstr(rc)); - } - goto end; - } - end: - if(!rc){ - if(doQuickCheck){ - /* We just wanted to check for stat()ability */ - if(!*rv) *rv = cwal_value_true(); - }else{ - s2_engine * se = s2_engine_from_args(args); - assert(se); - rc = s2_fstat_to_object( se, &stbuf, rv ); - if(rc){ - assert(CWAL_RC_OOM == rc); - } - } - } - return rc; -} - -int s2_chdir( char const * dir, cwal_size_t dirLen ){ -#if S2_HAVE_CHDIR - char fnBuf[S2_FILENAME_BUF_SIZE] = {0}; - int rc = 0; - if(dirLen >= (cwal_size_t)S2_FILENAME_BUF_SIZE) return CWAL_RC_RANGE; - memcpy( fnBuf, dir, dirLen ); - fnBuf[dirLen] = 0; - if(chdir(fnBuf)) rc = s2_errno_to_cwal_rc(0, CWAL_RC_IO); - return rc; -#else - if(dir && dirLen){/* unused */} - return CWAL_RC_UNSUPPORTED; -#endif -} - -int s2_cb_chdir( cwal_callback_args const * args, cwal_value **rv ){ - cwal_size_t strLen = 0; - int rc; - char const * str = args->argc - ? cwal_value_get_cstr(args->argv[0], &strLen) - : 0; - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc; - else if(!str) rc = s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a string (directory) argument."); - else{ - rc = s2_chdir( str, strLen ); - if(rc) rc = s2_cb_throw(args, rc, - "chdir() failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - else *rv = cwal_value_undefined(); - } - return rc; -} - -#ifdef _WIN32 -# define CHECKACCESS _access -# define CHECKRIGHTS (checkWrite ? 2 : 4) - /* - 2==writeable - 4==readable - 6==r/w - */ -#else - /* assume unix-like */ -# define CHECKACCESS access -# define CHECKRIGHTS (checkWrite ? W_OK : R_OK) -#endif - -char s2_file_is_accessible( char const * fn, char checkWrite ){ - return (0 == CHECKACCESS( fn, CHECKRIGHTS )); -} - -char s2_is_dir( char const * fn, char checkWrite ){ -#ifdef S2_OS_UNIX - struct stat buf; - if(checkWrite && 0!=CHECKACCESS(fn, CHECKRIGHTS)){ - return 0; - }else if(stat(fn, &buf)) return 0; - else return S_ISDIR(buf.st_mode) ? 1 : 0; -#else - /* - MISSING IMPL for Windows. Potentially requires re-encoding string - bytes, since we cannot know whether the ones we are given came - from a Windows console (in which case they might have some Code - Page encoding). Interested readers are referred to the fossil(1) - source tree, which jumps through several hoops in that regard. - */ - return 0; -#endif -} - -#undef CHECKACCESS -#undef CHECKRIGHTS - -static int s2_cb_fs_accessible( int isFile, cwal_callback_args const * args, cwal_value **rv ){ - char const * fn; - { - int const rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT); - if( rc ) return rc; - } - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], 0) - : 0; - if(!fn) return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a string argument."); - else{ - char const checkWrite = (args->argc>1) - ? cwal_value_get_bool(args->argv[1]) - : 0; - assert(fn); - *rv = (isFile ? s2_file_is_accessible(fn, checkWrite) : s2_is_dir(fn, checkWrite)) - ? cwal_value_true() - : cwal_value_false(); - return 0; - } -} - -int s2_cb_file_accessible( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_fs_accessible(1, args, rv); -} - -int s2_cb_dir_accessible( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_fs_accessible(0, args, rv); -} - - -int s2_cb_realpath( cwal_callback_args const * args, cwal_value **rv ){ -#if !S2_HAVE_REALPATH - if(rv){/*unused*/} - return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED, - "realpath() not supported in this build."); -#else - enum { BufSize = PATH_MAX + 1 }; - char buf[BufSize]; - char const * p; - cwal_size_t strLen = 0; - char const * str = args->argc - ? cwal_value_get_cstr(args->argv[0], &strLen) - : 0; - int rc; - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc; - if(!str) return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a string argument."); - p = realpath(str, buf); - if(!p){ - switch(errno){ - case ENOENT: - *rv = cwal_value_undefined(); - return 0; - default: - return cwal_exception_setf(args->engine, - s2_errno_to_cwal_rc( errno, - CWAL_RC_ERROR ), - "realpath('%.*s') failed with errno %d (%s)", - (int)strLen, str, errno, strerror(errno)); - } - } - *rv = cwal_new_string_value(args->engine, p, cwal_strlen(p)); - return *rv ? 0 : CWAL_RC_OOM; -#endif -} - -void s2_fclose(FILE *f){ - if(f - && f!=stdin - && f!=stdout - && f!=stderr){ - fclose(f); - } -} - -FILE *s2_fopen(const char *zName, const char *zMode){ - FILE *f; - if(zName && ('-'==*zName && !zName[1])){ - f = (strchr(zMode, 'w') - || strchr(zMode,'+') - || strchr(zMode,'a')) - ? stdout - : stdin - ; - }else{ - f = fopen(zName, zMode); - } - return f; -} - - - -int s2_passthrough_FILE( cwal_engine * e, FILE * file ){ - return cwal_stream( cwal_input_f_FILE, file, - cwal_output_f_cwal_engine, e ); -} - -int s2_passthrough_filename( cwal_engine * e, char const * filename ){ - int rc; - FILE * f = s2_fopen(filename, "r"); - if(!f) rc = CWAL_RC_IO; - else{ - rc = s2_passthrough_FILE(e, f); - s2_fclose(f); - } - return rc; -} - - -/** - Streams a file's contents directly to the script engine's output - channel, without (unless the file is small) reading the whole file - into a buffer first. - - Script usage: - - passthrough(filename) -*/ -static int s2_cb_file_passthrough( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - cwal_size_t len; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], &len) - : NULL; - if(!fn){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - }else if(!s2_file_is_accessible(fn, 0)){ - return s2_cb_throw(args, CWAL_RC_NOT_FOUND, - "Cannot find file: %s", fn); - }else{ - FILE * fi = s2_fopen(fn, "r"); - int rc; - if(!fi){ - rc = s2_cb_throw(args, CWAL_RC_IO, - "Could not open file for reading: %s", - fn); - }else{ - rc = cwal_stream( cwal_input_f_FILE, fi, - cwal_output_f_cwal_engine, - args->engine ); - s2_fclose(fi); - } - if(!rc) *rv = args->self; - return rc; - } -} - -int s2_install_fs( s2_engine * se, cwal_value * tgt, - char const * name ){ - cwal_value * v; - cwal_value * sub; - int rc; - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - - if(name && *name){ - sub = cwal_new_object_value(se->e); - if(!sub) return CWAL_RC_OOM; - if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){ - cwal_value_unref(sub); - return rc; - } - }else{ - sub = tgt; - } - - { - s2_func_def const funcs[] = { - S2_FUNC2("stat", s2_cb_fstat), - S2_FUNC2("realpath", s2_cb_realpath), - S2_FUNC2("passthrough", s2_cb_file_passthrough), - S2_FUNC2("mkdir", s2_cb_mkdir), - S2_FUNC2("getcwd", s2_cb_getcwd), - S2_FUNC2("fileIsAccessible", s2_cb_file_accessible), - S2_FUNC2("dirIsAccessible", s2_cb_dir_accessible), - S2_FUNC2("chdir", s2_cb_chdir), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, sub, funcs, 0); - if(rc) goto end; - } - -#define SET(NAME) \ - if(!v) { rc = CWAL_RC_OOM; goto end; } \ - cwal_value_ref(v); \ - rc = cwal_prop_set( sub, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - v = 0; \ - if(rc) goto end; - - v = cwal_new_string_value(se->e, - S2_DIRECTORY_SEPARATOR, - sizeof(S2_DIRECTORY_SEPARATOR)- - sizeof(S2_DIRECTORY_SEPARATOR[0]) ); - SET("dirSeparator"); - - end: -#undef SET - return rc; -} - -#undef MARKER -/* end of file fs.c */ -/* start of file hash.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -static int s2_new_hash( cwal_engine * e, cwal_int_t hashSize, - int setDotFlag /* experiment! */, cwal_value ** rv ){ - cwal_hash * h = 0; - cwal_value * hv = 0; - if(hashSize<=0) hashSize = 17; - else if(hashSize>7919) hashSize = 7919; -#if 0 - else hashSize = s2_hash_next_prime(hashSize); -#endif - h = cwal_new_hash(e, hashSize); - hv = h - ? cwal_hash_value(h) - : NULL; - if(!hv){ - assert(!h); - return CWAL_RC_OOM; - } - else { - assert(cwal_value_is_hash(hv)); - assert(cwal_value_prototype_get(e,hv)); - if(setDotFlag){ - s2_hash_dot_like_object(hv, 1); - } - *rv = hv; - return 0; - } -} - - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \ - assert(se) - -#define THIS_HASH \ - cwal_hash * h = 0; \ - cwal_value * hv = 0; \ - ARGS_SE; \ - h = cwal_value_hash_part(se->e, args->self); \ - hv = h ? cwal_hash_value(h) : 0; \ - if(!h || !hv){ \ - return s2_throw( se, CWAL_RC_TYPE, \ - "'this' is-not-a Hash." ); \ - } (void)0 - -static int s2_cb_hash_create( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t hsz = 0; - int const setDotFlag = -#if 1 - 0 -#else - (args->argc>1) - ? cwal_value_get_bool(args->argv[1]) - : 0 -#endif - ; - hsz = (args->argc>0) - ? cwal_value_get_integer(args->argv[0]) - : 0; - return (hsz<0) - ? cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Expecting a positive integer value " - "for hash table size.") - : s2_new_hash(args->engine, hsz, setDotFlag, rv ); -} - -/** - Internal impl of s2_cp_hash_keys/values(). mode==0 means - keys, anything else means values. -*/ -static int s2_cb_hash_kv( cwal_callback_args const * args, - char mode, - cwal_value **rv ){ - int rc; - cwal_array * ar; - THIS_HASH; - ar = cwal_new_array(args->engine); - if(!ar) return CWAL_RC_OOM; - if(!mode){ - rc = cwal_hash_visit_keys( h, - s2_value_visit_append_to_array, - ar ); - }else{ - rc = cwal_hash_visit_values( h, - s2_value_visit_append_to_array, - ar ); - } - if(!rc) *rv = cwal_array_value(ar); - else cwal_array_unref(ar); - return rc; -} - -static int s2_cb_hash_keys( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_hash_kv(args, 0, rv); -} - -static int s2_cb_hash_values( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_hash_kv(args, 1, rv); -} - -static int s2_cb_hash_entry_count( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_HASH; - *rv = cwal_new_integer(args->engine, - (cwal_int_t)cwal_hash_entry_count(h)); - return *rv ? 0 : CWAL_RC_OOM; -} - - -int s2_cb_hash_insert( cwal_callback_args const * args, cwal_value **rv ){ - THIS_HASH; - if(2!=args->argc) return s2_throw(se, - CWAL_RC_MISUSE, - "insert() requires (KEY,VALUE) " - "arguments."); - else{ - int rc = 0; - rc = cwal_hash_insert_v( h, args->argv[0], - args->argv[1], 1 ); - if(!rc) *rv = args->argv[1]; - else switch(rc){ - case CWAL_RC_ACCESS: - rc = s2_throw(se, rc, "May not modify a hash while it " - "is being iterated over."); - break; - default: - break; - } - /* if(!rc) *rv = args->self; */ - return rc; - } -} - -int s2_cb_hash_get( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * v; - THIS_HASH; - if(1!=args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, - "get() requires (KEY) argument."); - } - v = cwal_hash_search_v( h, args->argv[0] ); - *rv = v ? v : cwal_value_undefined(); - return 0; -} - -int s2_cb_hash_has( cwal_callback_args const * args, cwal_value **rv ){ - THIS_HASH; - if(!args->argc) return s2_throw(se, CWAL_RC_MISUSE, - "has() expects 1 argument."); - else { - *rv = cwal_hash_search_v( h, args->argv[0] ) - ? cwal_value_true() - : cwal_value_false(); - return 0; - } -} - -static int s2_cb_hash_clear( cwal_callback_args const * args, cwal_value **rv ){ - char clearProps; - THIS_HASH; - clearProps = (args->argc>0) - ? cwal_value_get_bool(args->argv[0]) - : 0; - cwal_hash_clear(h, clearProps); - *rv = args->self; - return 0; -} - - -int s2_cb_hash_remove( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - THIS_HASH; - if(1!=args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, - "remove() requires (KEY) argument."); - } - rc = cwal_hash_remove_v( h, args->argv[0] ); - if(!rc) *rv = cwal_value_true(); - else switch(rc){ - case CWAL_RC_ACCESS: - rc = s2_throw(se, rc, "May not modify a hash while it " - "is being iterated over."); - break; - case CWAL_RC_NOT_FOUND: - *rv = cwal_value_false(); - rc = 0; - break; - default: - break; - } /* seriously, emacs? You're going to do that to me? */ - return rc; -} - -static int s2_cb_hash_take_props( cwal_callback_args const * args, cwal_value **rv ){ - int const overwritePolicy = (args->argc>1) - ? (int)cwal_value_get_integer(s2_value_unwrap(args->argv[1])) - : 1; - cwal_value * src = args->argc ? args->argv[0] : 0; - THIS_HASH; - if(!src || !cwal_props_can(src)){ - return s2_cb_throw(args, src ? CWAL_RC_TYPE : CWAL_RC_MISUSE, - "Expecting a Container argument."); - } - *rv = args->self; - return cwal_hash_take_props( h, src, overwritePolicy ); -} - - - -/** - Internal cwal_kvp_visitor_f() implementation which requires state - to be a (cwal_hash*), into which it inserts/overwrites kvp's - key/value. -*/ -static int s2_kvp_visitor_hash_insert( cwal_kvp const * kvp, void * state ){ - return cwal_hash_insert_v( (cwal_hash *)state, - cwal_kvp_key(kvp), cwal_kvp_value(kvp), 1 ); -} - -/* in s2_protos.c */ -int s2_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ ); - -/** - Script usages: - - obj.eachEntry(Function): - - The given function is called once per property, passed the key and - value. - - obj.eachEntry(Object, Function): - - Functionally equivalent to obj.eachEntry(proc(k,v){otherObj.func(k,v)}) - - obj.eachEntry(Hash): - - Functionally equivalent to: obj.eachEntry(targetHash, targetHash.insert) - - *rv will be set to args->self on success. -*/ -static int s2_cb_hash_each_entry( cwal_callback_args const * args, cwal_value **rv ){ - cwal_function * f; - int fIndex = (args->argc>1) ? 1 : args->argc ? 0 : -1; - cwal_value * theThis = (args->argc>1) ? args->argv[0] : args->self; - cwal_hash * otherHash = 0; - THIS_HASH; - - f = (fIndex>=0) - ? cwal_value_get_function(args->argv[fIndex]) - : 0; - if(!f){ - if(! (otherHash = (1==args->argc) - ? cwal_value_get_hash(args->argv[0]) - : 0) ){ - return s2_throw(se, CWAL_RC_MISUSE, - "'eachEntry' expects (Hash|Function) " - "or (Object, Function) arguments."); - } - }else if(!cwal_value_may_iterate(hv)){ - return cwal_exception_setf(args->engine, CWAL_RC_ACCESS, - "Hashtable is currently iterating."); - } - - if(otherHash){ - return cwal_hash_visit_kvp(h, s2_kvp_visitor_hash_insert, otherHash); - }else{ - s2_kvp_each_state state = s2_kvp_each_state_empty; - int rc; - state.e = args->engine; - state.callback = f; - state.self = theThis; - rc = cwal_hash_visit_kvp( h, s2_kvp_visitor_prop_each, &state ); - if(S2_RC_END_EACH_ITERATION==rc) rc = 0; - if(!rc) *rv = args->self; - return rc; - } -} - -static int s2_cb_hash_resize( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_int_t hsz; - THIS_HASH; - hsz = args->argc - ? cwal_value_get_integer(args->argv[0]) - : -1; - if(hsz<1){ - return s2_cb_throw(args, args->argc ? CWAL_RC_RANGE : CWAL_RC_MISUSE, - "Expecting a positive hash table size value."); - } - rc = cwal_hash_resize(h, (cwal_size_t)hsz); - if(!rc) *rv = args->self; - return rc; -} - -static int s2_cb_hash_grow_if_loaded( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - cwal_double_t load; - THIS_HASH; - load = args->argc - ? cwal_value_get_double(args->argv[0]) - : 0.8; - rc = cwal_hash_grow_if_loaded(h, load); - if(!rc) *rv = args->self; - return rc; -} - -static int s2_cb_hash_size( cwal_callback_args const * args, cwal_value **rv ){ - THIS_HASH; - *rv = cwal_new_integer(args->engine, (cwal_int_t)cwal_hash_size(h)); - return *rv ? 0 : CWAL_RC_OOM; -} - - -static int s2_cb_hash_has_entries( cwal_callback_args const * args, cwal_value **rv ){ - THIS_HASH; - *rv = cwal_new_bool( cwal_hash_entry_count(h) ? 1 : 0 ); - return 0; -} - -cwal_value * s2_prototype_hash( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_HASH ); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_HASH, proto ); - if(!rc) rc = s2_prototype_stash(se, "Hash", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_HASH)); - /* MARKER(("Setting up OBJECT prototype.\n")); */ - -#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define RC if(rc) goto end; - - { - const s2_func_def funcs[] = { - S2_FUNC2("clearEntries", s2_cb_hash_clear), - S2_FUNC2("containsEntry", s2_cb_hash_has), - S2_FUNC2("eachEntry", s2_cb_hash_each_entry), - S2_FUNC2("entryCount", s2_cb_hash_entry_count), - S2_FUNC2("entryKeys", s2_cb_hash_keys), - S2_FUNC2("entryValues", s2_cb_hash_values), - S2_FUNC2("growIfLoaded", s2_cb_hash_grow_if_loaded), - S2_FUNC2("hashSize", s2_cb_hash_size), - S2_FUNC2("insert", s2_cb_hash_insert), - S2_FUNC2("hasEntries", s2_cb_hash_has_entries), - S2_FUNC2("remove", s2_cb_hash_remove), - S2_FUNC2("resize", s2_cb_hash_resize), - S2_FUNC2("search", s2_cb_hash_get), - S2_FUNC2("takeProperties", s2_cb_hash_take_props), - S2_FUNC2("new", s2_cb_hash_create), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0 ); - if(rc) goto end; - else { - cwal_value * fv = 0; - s2_get(se, proto, "new", 3, &fv); - assert(fv && "we JUST put this in there!"); - rc = s2_ctor_method_set( se, proto, - cwal_value_get_function(fv) ); - } - } - -#undef FUNC2 -#undef CHECKV -#undef RC - end: - return rc ? NULL : proto; -} - - -#undef MARKER -#undef THIS_HASH -#undef ARGS_SE -/* end of file hash.c */ -/* start of file io.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -int s2_cb_flush( cwal_callback_args const * args, cwal_value **rv ){ - int rc = cwal_output_flush(args->engine); - if(rc){ - rc = cwal_exception_setf(args->engine, rc, - "Flushing output failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - }else{ - *rv = cwal_value_undefined(); - } - /* Weird: if i output from here, my error messages caught - via handling linenoise input are flushed immediately, - else they are not. - */ - /* MARKER(("Flushed %d\n",rc)); */ - return rc; -} - -int s2_cb_print_helper( cwal_callback_args const * args, - cwal_value **rv, - uint16_t skipArgCount, - cwal_flags32_t flags ){ - uint16_t i, n; - int rc = 0; - char const * sep = " "; - cwal_engine * e = args->engine; - cwal_size_t const sepLen = cwal_strlen(sep); - char const addSpace = (S2_PRINT_OPT_SPACE & flags) ? 1 : 0; - /* dump_val(args->self, "'this' for print()"); */ - /* MARKER(("s2_cb_print() called with %"PRIu16" arg(s).\n", args->argc)); */ - if(S2_PRINT_OPT_RETURN_THIS & flags - && S2_PRINT_OPT_RETURN_CALLEE & flags){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "s2_cp_print_helper() cannot handle both the " - "S2_PRINT_OPT_RETURN_THIS and " - "S2_PRINT_OPT_RETURN_CALLEE flags. " - "Pick one or the other."); - } - for(i = skipArgCount, n = 0; !rc && (i < args->argc); ++i ){ - cwal_value * v = (S2_PRINT_OPT_UNWRAP & flags) - ? s2_value_unwrap(args->argv[i]) - : args->argv[i]; - if(addSpace && n++){ - rc = cwal_output(e, sep, sepLen); - if(rc) break; - } - /* s2_dump_val(v,"arg"); */ - switch(cwal_value_type_id(v)){ - case CWAL_TYPE_ARRAY: - case CWAL_TYPE_BOOL: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_EXCEPTION: - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_NULL: - case CWAL_TYPE_OBJECT: - case CWAL_TYPE_TUPLE: - rc = cwal_json_output_engine( e, v, NULL ); - break; - case CWAL_TYPE_UNDEF: - rc = cwal_output(e, "undefined", 9); - break; - case CWAL_TYPE_STRING:{ - cwal_size_t slen = 0; - char const * cstr = cwal_value_get_cstr(v, &slen); - rc = slen ? cwal_output(e, cstr, slen) : 0; - break; - } - case CWAL_TYPE_BUFFER:{ - cwal_buffer const * vb = cwal_value_get_buffer(v); - rc = vb->used ? cwal_output(e, vb->mem, vb->used) : 0; - break; - } - case CWAL_TYPE_HASH: - case CWAL_TYPE_FUNCTION: - case CWAL_TYPE_NATIVE: - case CWAL_TYPE_UNIQUE: - rc = cwal_outputf(e, "%s@0x%p", cwal_value_type_name2(v, 0), - (void const*)v); - break; - default: - break; - } - } - if(rc && (CWAL_RC_EXCEPTION!=rc)){ - rc = cwal_exception_setf(args->engine, rc, "Output error #%d (%s).", - rc, cwal_rc_cstr(rc)); - } - else if(!rc && (S2_PRINT_OPT_NEWLINE & flags)){ - cwal_output(args->engine, "\n", 1); - } - if(S2_PRINT_OPT_RETURN_CALLEE & flags){ - *rv = cwal_function_value(args->callee); - }else if(S2_PRINT_OPT_RETURN_THIS & flags){ - *rv = args->self; - }else{ - *rv = cwal_value_undefined(); - } - cwal_output_flush(args->engine); - return rc; -} - - -int s2_cb_print( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_print_helper(args, rv, 0, - S2_PRINT_OPT_SPACE - | S2_PRINT_OPT_NEWLINE - | S2_PRINT_OPT_RETURN_CALLEE ); -} - -int s2_cb_write( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_print_helper(args, rv, 0, - S2_PRINT_OPT_RETURN_CALLEE); -} - -int s2_cb_import_script(cwal_callback_args const * args, cwal_value ** rv){ - int i, rc = 0; - s2_engine * se = s2_engine_from_args(args); - cwal_value * xrv = 0; - assert(se); - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT - | S2_DISABLE_FS_READ)) ) return rc; -#if 0 - /* - experimenting with a use for this in require.s2. Doesn't - work - this gets overwritten by that point and passing this - through the API would go through way too many layers and impact - too many things. - - _Seems_ to work okay for the basic case, though, as long as one - doesn't call this function from cwal_function_call_in_scope(), - using a scope which already has a 'this'. In the "normal" case - (when this function is called from s2 scripts) 'this' is the LHS - of the s2.import() call (or is this function if the import() - reference is used standalone), and we're overwriting that one (if - it's set) in its call-local scope. - - Not yet sure i want to export our "this" this way, but it would be - potentially useful in a few cases. - */ - rc = s2_var_set( se, 0, "this", 4, args->self); - if(rc) return rc; -#endif - - /* - TODO: - - - Create a local array to hold a list of all filenames passed in. - - - Before running each script, see if it's in that list. If it is, - assume recursion and fail. - - - Else eval it. - - - At the end of the loop, free the name list. - - Hmmm. Each sub-import needs access to the same list. Where to store it - so that it's reachable recursively? In args->self['importList'] and - args->self['importDepth']? - */ - *rv = 0; - for( i = 0; !rc && i < args->argc; ++i ){ - cwal_value const * arg = args->argv[i]; - cwal_size_t nLen = 0; - char const * fn = arg ? cwal_value_get_cstr(arg, &nLen) : 0; - if(!fn){ - rc = s2_cb_throw( args, CWAL_RC_TYPE, - "Expecting a STRING value, but got '%s'.", - arg ? cwal_value_type_name(arg) : ""); - }else{ - cwal_value_unref(xrv); - xrv = 0; - rc = s2_eval_filename(se, 1, fn, nLen, &xrv); - cwal_value_ref(xrv); - } - } - switch(rc){ - case 0: - if(xrv){ - cwal_value_unhand(xrv); - *rv = xrv; - assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv)) - && "Seems like *rv was cleaned up too early."); - }else{ - *rv = cwal_value_undefined(); - } - break; - case CWAL_RC_RETURN: - *rv = cwal_propagating_take(se->e); - assert(*rv && "Misuse of CWAL_RC_RETURN!"); - assert(xrv != *rv && "But... how???"); - cwal_value_unref(xrv); - s2_engine_err_reset(se); - rc = 0; - break; - default: - cwal_value_unref(xrv); - break; - } - return rc; -} - -int s2_install_io( s2_engine * se, cwal_value * tgt, - char const * name ){ - cwal_value * sub; - int rc; - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - - if(name && *name){ - sub = cwal_new_object_value(se->e); - if(!sub) return CWAL_RC_OOM; - cwal_value_ref(sub); - rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub); - cwal_value_unref(sub); - if(rc) return rc; - }else{ - sub = tgt; - } - cwal_value_ref(sub); - { - s2_func_def const funcs[] = { - S2_FUNC2("flush", s2_cb_flush), - S2_FUNC2("output", s2_cb_write), - S2_FUNC2("print", s2_cb_print), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, sub, funcs, 0); - if(rc) goto end; - /** - Add output.'operator<<' as a proxy for output(). Why? Because - (A) we end up doing something equivalent in a surprising amount - of client code and (B) to provide a default operator<< impl for - s2.tmpl(). Because of how s2 tokenizes parens groups, x< -#include /* strlen() */ - - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - - - -/** @def S2_HAVE_DLOPEN - - If set to true, use dlopen() and friends. Requires - linking to -ldl on most platforms. - - Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN may be - true. -*/ -/** @def S2_HAVE_LTDLOPEN - - If set to true, use lt_dlopen() and friends. Requires - linking to -lltdl on most platforms. - - Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN may be - true. -*/ - -/* - #define S2_HAVE_DLOPEN 1 - #define S2_HAVE_LTDLOPEN 0 -*/ - -#if !defined(S2_HAVE_DLOPEN) -#define S2_HAVE_DLOPEN 0 -#endif - -#if !defined(S2_HAVE_LTDLOPEN) -#define S2_HAVE_LTDLOPEN 0 -#endif - -#if !defined(S2_ENABLE_MODULES) -# define S2_ENABLE_MODULES (S2_HAVE_LTDLOPEN || S2_HAVE_DLOPEN) -#endif - -#ifdef S2_OS_WINDOWS -# include -#else -# include /* access() */ -#endif - -#if S2_HAVE_DLOPEN && S2_HAVE_LTDLOPEN -#error Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN should be true. -#endif - -#if !S2_ENABLE_MODULES -typedef int dl_handle_t /* dummy placeholder */; -#elif S2_HAVE_DLOPEN -typedef void * dl_handle_t; -# include /* this actually has a different name on some platforms! */ -#elif S2_HAVE_LTDLOPEN -# include -typedef lt_dlhandle dl_handle_t; -#else -# error "We have no dlopen() impl for this configuration." -#endif - -#if S2_ENABLE_MODULES -static void s2_dl_init(){ - static char once = 0; - if(!once && ++once){ -# if S2_HAVE_LTDLOPEN - lt_dlinit(); - lt_dlopen( 0 ); -# elif S2_HAVE_DLOPEN - dlopen( 0, RTLD_NOW | RTLD_GLOBAL ); -# endif - } -} -#endif - -#if S2_ENABLE_MODULES -static dl_handle_t dl_open( char const * fname, char const **errMsg ){ - dl_handle_t soh; -#if S2_HAVE_LTDLOPEN - soh = lt_dlopen( fname ); -#elif S2_HAVE_DLOPEN - soh = dlopen( fname, RTLD_NOW | RTLD_GLOBAL ); -#endif - if(!soh && errMsg){ -#if S2_HAVE_LTDLOPEN - *errMsg = lt_dlerror(); -#elif S2_HAVE_DLOPEN - *errMsg = dlerror(); -#endif - } - return soh; -} -#endif - -#if S2_ENABLE_MODULES -static s2_loadable_module const * s2_dl_sym( dl_handle_t soh, char const * mname ){ - void * sym = -#if S2_HAVE_LTDLOPEN - lt_dlsym( soh, mname ) -#elif S2_HAVE_DLOPEN - dlsym( soh, mname ) -#else - NULL -#endif - ; - return sym ? *((s2_loadable_module const **)sym) : NULL; -} -#endif - -#define S2_CLOSE_DLLS 1 -/* - Years of practice have shown that it is literally impossible to - safely close DLLs because simply opening one may trigger arbitrary - code (at least for C++ DLLs) which "might" be used by the - application. e.g. some classloaders use DLL initialization to inject - new classes into the application without the app having to do anything - more than open the DLL. - - So s2 does not close DLLs. Except (...sigh...) to try to please - valgrind. -*/ -#if S2_ENABLE_MODULES -static void s2_dl_close( dl_handle_t soh ){ -#if S2_CLOSE_DLLS - /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */ -#if S2_HAVE_LTDLOPEN - lt_dlclose( soh ); -#elif S2_HAVE_DLOPEN - dlclose( soh ); -#endif -#endif -} -#endif - - -#if S2_ENABLE_MODULES -/** - Looks for a symbol in the given DLL handle. If symName is NULL or - empty, the symbol "s2_module" is used, else the symbols - ("s2_module_" + symName) is used. If it finds one, it casts it to - s2_loadable_module and returns it. On error it may update se->err - with the error information. -*/ -static s2_loadable_module const * -s2_module_fish_out_entry_pt(s2_engine * se, - dl_handle_t soh, - char const * symName){ - enum { MaxLen = 128 }; - char buf[MaxLen] = {0}; - cwal_size_t const slen = symName ? cwal_strlen(symName) : 0; - s2_loadable_module const * mod; - if(slen > (MaxLen-20)){ - s2_engine_err_set(se, CWAL_RC_RANGE, - "DLL symbol name '%.*s' is too long.", - (int)slen, symName); - return 0; - } - if(!slen){ - memcpy(buf, "s2_module", 9); - buf[9] = 0; - }else{ - sprintf(buf,"s2_module_%s", symName); - } - mod = (s2_loadable_module*)s2_dl_sym( soh, buf ); - /* MARKER(("s2_module_fish_out_entry_pt [%s] ==> %p\n",buf, - (void const *)mod)); */ - return mod; -} -#endif/*S2_ENABLE_MODULES*/ - - -#if S2_ENABLE_MODULES -/** - Tries to dlsym() the given s2_loadable_module symbol from the given - DLL handle. On success, 0 is returned and *mod is assigned to the - memory. On error, non-0 is returned and se's error state may be - updated. - - Ownership of the returned module ostensibly lies with se, but - that's not entirely true. If S2_CLOSE_DLLS is true then a copy of - the module's pointer is stored in se for later closing. The memory - itself is owned by the module loader, and "should" stay valid - until the DLL is closed. -*/ -static int s2_module_get_sym( s2_engine * se, - dl_handle_t soh, - char const * symName, - s2_loadable_module const ** mod ){ - - s2_loadable_module const * lm; - int rc; - s2_engine_err_reset(se); - lm = s2_module_fish_out_entry_pt(se, soh, symName); - rc = s2_engine_err_has(se); - if(rc) return rc; - else if(!lm){ - s2_dl_close(soh); - return s2_engine_err_set( se, CWAL_RC_NOT_FOUND, - "Did not find module entry point symbol '%s'.", - symName); - } - *mod = lm; - if(S2_CLOSE_DLLS){ - /* Stash soh for potential closing later on. */ - cwal_size_t i = 0; - char found = 0; - for( ; i < se->modules.count; ++i ){ - if(soh == se->modules.list[i]){ - found = 1; - break; - } - } - if(!found){ - int const rc = cwal_list_append(se->e, - &se->modules, soh); - if(rc){ - s2_dl_close(soh); - lm = 0; - /* This is an allocation error, so don't - bother updating s2_engine::err. - */ - } - } - }/*S2_CLOSE_DLLS*/ - return rc; -} -#endif/*S2_ENABLE_MODULES*/ - -#if !S2_ENABLE_MODULES -static int s2_module_no_modules( s2_engine * se ){ - return s2_engine_err_set(se, CWAL_RC_UNSUPPORTED, - "No dlopen() equivalent is installed " - "for this build configuration."); -} -#endif - -int s2_module_init( s2_engine * se, - s2_loadable_module const * mod, - cwal_value ** rv){ - int rc; - cwal_scope sc = cwal_scope_empty; - if(!mod->init) return CWAL_RC_MISUSE; - rc = cwal_scope_push2(se->e, &sc); - if(!rc){ - cwal_value * xrv = 0; - rc = mod->init( se, &xrv ); - cwal_scope_pop2(se->e, (rc||!rv) ? 0 : xrv); - if(!rc && rv) *rv = xrv; - } - return rc; -} - -int s2_module_extract( s2_engine * se, - char const * fname, - char const * symName, - s2_loadable_module const ** mod ){ -#if !S2_ENABLE_MODULES - if(fname || symName || mod){/*avoid unused param warning*/} - return s2_module_no_modules(se); -#else -#if !S2_HAVE_LTDLOPEN && !S2_HAVE_DLOPEN -# error "We have no dlopen() and friends impl for this configuration." -#endif - if(!se || !fname || !*fname || !mod) return CWAL_RC_MISUSE; - else { - dl_handle_t soh; - char const * errMsg = 0; - s2_dl_init(); - soh = dl_open( fname, &errMsg ); - if(!soh){ - if(errMsg){ - return s2_engine_err_set(se, CWAL_RC_ERROR, - "DLL open failed: %s", - errMsg); - }else{ - return CWAL_RC_ERROR; - } - } - else { - s2_loadable_module const * x = 0; - int const rc = s2_module_get_sym( se, soh, symName, &x ); - if(!rc){ - assert(x); - if(mod) *mod = x; - } - return rc; - } - } -#endif -} - - -int s2_module_load( s2_engine * se, - char const * fname, - char const * symName, - cwal_value ** rv ){ -#if !S2_ENABLE_MODULES - if(fname || symName || rv){/*avoid unused param warning*/} - return s2_module_no_modules(se); -#else -# if !S2_HAVE_LTDLOPEN && !S2_HAVE_DLOPEN -# error "We have no dlopen() and friends impl for this configuration." -# endif - if(!se || !fname || !*fname || !rv) return CWAL_RC_MISUSE; - else { - s2_loadable_module const * mod = 0; - int rc = s2_module_extract( se, fname, symName, &mod ); - if(!rc){ - assert(mod); - rc = s2_module_init(se, mod, rv); - } - return rc; - } -#endif -} - -int s2_cb_module_load( cwal_callback_args const * args, - cwal_value **rv ){ - s2_engine * se; - int rc = 0; -#if !S2_ENABLE_MODULES - se = s2_engine_from_args(args); - assert(se); - rc = s2_module_no_modules(se); - if(rv){/*avoid unused param warning*/} - return CWAL_RC_UNSUPPORTED==rc - ? s2_throw_err( se, 0, 0, 0, 0 ) - : rc /* presumably CWAL_RC_OOM */; -#else - char const * fn; - char const * sym = NULL; - cwal_value * mod = 0; - se = s2_engine_from_args(args); - assert(se); - rc = s2_disable_check_throw(se, S2_DISABLE_FS_READ); - if(rc) return rc; - fn = cwal_string_cstr(cwal_value_get_string(args->argv[0])); - if(!fn){ - goto misuse; - } - if(args->argc>1){ - if(cwal_value_is_string(args->argv[1])){ - sym = cwal_value_get_cstr(args->argv[1], 0); - }else{ - goto misuse; - } - } - rc = s2_module_load(se, fn, sym, &mod); - /* MARKER(("load_module(%s, %s) rc=%d\n", fn, sym, rc)); */ - switch(rc){ - case 0: - assert(mod); - *rv = mod; - break; - case CWAL_RC_EXCEPTION: - case CWAL_RC_INTERRUPTED: - case CWAL_RC_OOM: - break; - case CWAL_RC_BREAK: - case CWAL_RC_RETURN: - s2_propagating_set(se, 0); - CWAL_SWITCH_FALL_THROUGH; - default:{ - /* We are pre-empting various codes here, e.g. CWAL_RC_EXIT, - CWAL_RC_FATAL, and CWAL_RC_ASSERT. We do not want plugin - initialization to be able to quit the top-running script that - way (e.g. a failed s2 assert in some init script code). */ - if(se->e->err.code){ - rc = s2_throw_err(se, 0, 0, 0, 0); - s2_engine_err_reset(se); - } - else{ - rc = s2_cb_throw(args, rc, - "Loading module failed with code " - "%d (%s).", rc, cwal_rc_cstr(rc)); - } - break; - } - } - return rc; - misuse: - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Module loader expects " - "(string [,string]]) arguments."); -#endif -} - - -void s2_modules_close( s2_engine * se ){ - if(se){ -#if !S2_ENABLE_MODULES - assert(!se->modules.list); -#else - if(se->modules.list){ - /* dlcose() does not seem to care whether they are closed - in reverse-opened order or not, and it leaks all of - them on my box. lt_dlclose() leaks even more! - */ -#if S2_CLOSE_DLLS - int i; - assert(se->modules.count); - i = ((int)se->modules.count) - 1; - for( ; i >= 0; --i ){ - void * soh = se->modules.list[i]; - assert(soh); - s2_dl_close(soh); - } - cwal_list_reserve( se->e, &se->modules, 0 ); -#else - /* - Closing DLLs is NOT generically safe because we may - stomp resources used elsewhere. It can't be done 100% - reliably at this level, i am fully convinced. Let the OS - clean them up. - */ -#endif - assert(!se->modules.list); - assert(!se->modules.count); - assert(!se->modules.alloced); - } -#endif - } -} - -#undef MARKER -#undef S2_CLOSE_DLLS -/* end of file mod.c */ -/* start of file number.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include -#include /* sprintf() */ - -#if 1 -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args) - - -#define THIS_DOUBLE \ - cwal_double_t self; \ - ARGS_SE; \ - assert(se); \ - if(!cwal_value_is_number(args->self)){ \ - return s2_throw( se, CWAL_RC_TYPE, \ - "'this' is-not-a Number." ); \ - }\ - self = cwal_value_get_double(args->self) - -#define THIS_INT \ - cwal_int_t self; \ - ARGS_SE; \ - assert(se); \ - if(!cwal_value_is_number(args->self)){ \ - return s2_throw( se, CWAL_RC_TYPE, \ - "'this' is-not-a Number." ); \ - }\ - self = cwal_value_get_integer(args->self) - -static int s2_cb_int_to_dbl( cwal_callback_args const * args, cwal_value **rv ){ - THIS_INT; - *rv = cwal_value_is_double(args->self) - ? args->self - : cwal_new_double( args->engine, (cwal_int_t)self ) - ; - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_dbl_to_int( cwal_callback_args const * args, cwal_value **rv ){ - THIS_DOUBLE; - *rv = cwal_value_is_integer(args->self) - ? args->self - : cwal_new_integer( args->engine, (cwal_int_t)self ) - ; - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_cb_format_self_using_arg( cwal_callback_args const * args, cwal_value **rv ){ - cwal_engine * e = args->engine; - cwal_size_t fmtLen = 0; - char const * fmtStr; - fmtStr = args->argc - ? cwal_value_get_cstr(args->argv[0], &fmtLen) - : 0; -#if 0 - if(fmtStr && fmtLen && '%'==*fmtStr){ - ++fmtStr; - --fmtLen; - } -#endif - if(!fmtLen){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a single cwal_buffer_format()-compatible " - "string argument, minus the '$1%%' prefix."); - }else{ - int rc; - cwal_buffer fmtBuf = cwal_buffer_empty; - char * zFmt = 0; - cwal_buffer_printf( e, &fmtBuf, "%%1$%.*s", - (int)fmtLen, fmtStr); - zFmt = (char *)fmtBuf.mem; - fmtBuf = cwal_buffer_empty; - rc = cwal_buffer_format(e, &fmtBuf, zFmt, cwal_strlen(zFmt), - 1, &args->self); - if(rc){ - rc = cwal_exception_setf(e, rc, - "Formatted conversion from number " - "to string using format '%s' failed: %.*s", - zFmt, - (int)fmtBuf.used, - (char const *)fmtBuf.mem); - }else{ - *rv = cwal_buffer_to_zstring_value(e, &fmtBuf); - if(!*rv) rc = CWAL_RC_OOM; - } - cwal_free(e, zFmt); - zFmt = 0; - cwal_buffer_clear(e, &fmtBuf); - return rc; - } -} - -static int s2_cb_num_to_string( cwal_callback_args const * args, cwal_value **rv ){ - cwal_engine * e = args->engine; - int rc = 0; - if(args->argc){ - rc = s2_cb_format_self_using_arg(args, rv); - }else{ - enum { BufLen = 100 }; - char buf[BufLen] = {0}; - cwal_size_t bLen = (cwal_size_t)BufLen; - if(cwal_value_is_double(args->self)){ - rc = cwal_double_to_cstr( cwal_value_get_double(args->self), - buf, &bLen ); - }else{ - rc = cwal_int_to_cstr( cwal_value_get_integer(args->self), - buf, &bLen ); - } - if(rc){ - return cwal_exception_setf(e, rc, - "Conversion from number to string failed."); - } - *rv = cwal_new_string_value(args->engine, buf, bLen); - if(!*rv) rc = CWAL_RC_OOM; - } - return rc; -} - -static int s2_cb_int_to_utf8char( cwal_callback_args const * args, cwal_value **rv ){ - enum { BufLen = 7 }; - unsigned char buf[BufLen] = {0,0,0, 0,0,0, 0}; - cwal_size_t bLen = (cwal_size_t)BufLen; - int rc; - THIS_INT; - rc = cwal_utf8_char_to_cstr((unsigned int)self, buf, bLen); - if(rc<0){ -#if 1 - *rv = cwal_value_undefined(); - return 0 /* arguably we should throw */; -#else - return s2_throw(se, CWAL_RC_RANGE, - "Integer value %"CWAL_INT_T_PFMT" is not " - "a valid UTF8 character.", (cwal_int_t)self); -#endif - } - *rv = cwal_new_string_value(args->engine, (char const *)buf, - cwal_strlen((char const *)buf)); - return *rv ? 0 : CWAL_RC_OOM; -} - - -int s2_cstr_parse_number( cwal_engine * e, char const * src, - cwal_int_t slen, cwal_value ** rv ){ - cwal_int_t inty = 0; - if(s2_cstr_parse_int(src, slen, &inty)){ - *rv = cwal_new_integer(e, inty); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - cwal_double_t dbl = 0.0; - if(s2_cstr_parse_double(src, slen, &dbl)){ - *rv = cwal_new_double(e, dbl); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - *rv = 0; - return 0; - } - } -} - -static int s2_cb_parse_int( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * arg; - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting an argument to parse as an integer."); - } - arg = args->argv[0]; - switch(cwal_value_type_id(arg)){ - case CWAL_TYPE_INTEGER: - *rv = args->argv[0]; - return 0; - case CWAL_TYPE_DOUBLE: - *rv = cwal_new_integer(args->engine, - (cwal_double_t)cwal_value_get_double(arg)); - return *rv ? 0 : CWAL_RC_OOM; - case CWAL_TYPE_BOOL: - *rv = cwal_new_integer(args->engine, - cwal_value_get_bool(arg) ? 1 : 0); - return 0; - default:{ - cwal_size_t slen = 0; - char const * str = cwal_value_get_cstr(arg, &slen); - if(str && slen){ - cwal_int_t inty = 0; - if(s2_cstr_parse_int(str, (cwal_int_t)slen, &inty)){ - *rv = cwal_new_integer(args->engine, inty); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - cwal_double_t dbl = 0.0; - if(s2_cstr_parse_double(str, (cwal_int_t)slen, &dbl)){ - *rv = cwal_new_integer(args->engine, (cwal_int_t)dbl); - return *rv ? 0 : CWAL_RC_OOM; - } - } - } - *rv = cwal_value_undefined(); - return 0; - } - } -} - - -static int s2_cb_parse_double( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * arg; - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting an argument to parse as a number."); - } - arg = args->argv[0]; - switch(cwal_value_type_id(arg)){ - case CWAL_TYPE_INTEGER: - *rv = cwal_new_double(args->engine, - (cwal_double_t)cwal_value_get_integer(arg)); - return *rv ? 0 : CWAL_RC_OOM; - case CWAL_TYPE_DOUBLE: - *rv = args->argv[0]; - return 0; - case CWAL_TYPE_BOOL: - *rv = cwal_new_double(args->engine, - cwal_value_get_bool(arg) ? 1 : 0); - return 0; - default:{ - cwal_size_t slen = 0; - char const * str = cwal_value_get_cstr(arg, &slen); - if(str){ - cwal_double_t dbl = 0.0; - if(s2_cstr_parse_double(str, (cwal_int_t)slen, &dbl)){ - *rv = cwal_new_double(args->engine, dbl); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - cwal_int_t dd = 0; - if(s2_cstr_parse_int(str, (cwal_int_t)slen, &dd)){ - *rv = cwal_new_double(args->engine, (cwal_double_t)dd); - return *rv ? 0 : CWAL_RC_OOM; - } - } - } - *rv = cwal_value_undefined(); - return 0; - } - } -} - -static int s2_cb_parse_number( cwal_callback_args const * args, cwal_value **rv ){ - cwal_value * arg; - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting an argument to parse as a number."); - } - arg = args->argv[0]; - switch(cwal_value_type_id(arg)){ - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE: - *rv = args->argv[0]; - return 0; - case CWAL_TYPE_BOOL: - *rv = cwal_new_integer(args->engine, - cwal_value_get_bool(arg) ? 1 : 0); - return 0; - default:{ - cwal_size_t slen = 0; - char const * str = cwal_value_get_cstr(arg, &slen); - if(str && slen){ - int rc; - *rv = 0; - rc = s2_cstr_parse_number(args->engine, str, - (cwal_int_t)slen, rv); - if(!rc && !*rv) *rv = cwal_value_undefined(); - assert( *rv ? 1 : rc ); - return *rv ? 0 : rc; - }else{ - *rv = cwal_value_undefined(); - return 0; - } - } - } -} - -int s2_cb_rand_int( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t const v = (cwal_int_t)(rand() % CWAL_INT_T_MAX /* in case we're running on a "small" build */); - *rv = cwal_new_integer( args->engine, v); - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_cb_srand_int( cwal_callback_args const * args, cwal_value **rv ){ - if( 1 != args->argc ){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting exactly 1 integer value."); - }else{ - srand( (unsigned int)cwal_value_get_integer(args->argv[0]) ); - *rv = cwal_value_undefined(); - return 0; - } -} - -static int s2_cb_nth_prime( cwal_callback_args const * args, cwal_value **rv ){ - static const int max = 1000; - int const ndx = args->argc - ? (int)cwal_value_get_integer(args->argv[0]) - : -1; - if(ndx < 1 || ndx>max){ - return s2_cb_throw(args, args->argc ? CWAL_RC_RANGE : CWAL_RC_MISUSE, - "Expecting an integer value between 1 and %d.", - max); - } - *rv = cwal_new_integer(args->engine, - (cwal_int_t)cwal_first_1000_primes()[ndx-1]); - return *rv ? 0 : CWAL_RC_OOM; -} - - -/** - Base prototype for integer and double. -*/ -static cwal_value * s2_prototype_number( s2_engine * se ){ - static char const * stashKey = "Number"; - int rc = 0; - cwal_value * proto; - proto = s2_prototype_stashed(se, stashKey); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_prototype_set(proto, 0 /* so we don't inherit Object!*/); - if(!rc) rc = s2_prototype_stash(se, stashKey, proto); - if(!rc){ - const s2_func_def funcs[] = { - S2_FUNC2("compare", s2_cb_value_compare), - S2_FUNC2("nthPrime", s2_cb_nth_prime), - S2_FUNC2("parseDouble", s2_cb_parse_double), - S2_FUNC2("parseInt", s2_cb_parse_int), - S2_FUNC2("parseNumber", s2_cb_parse_number), - S2_FUNC2("toDouble", s2_cb_int_to_dbl), - S2_FUNC2("toInt", s2_cb_dbl_to_int), - S2_FUNC2("toJSONString", s2_cb_this_to_json_token), - S2_FUNC2("toString", s2_cb_num_to_string), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - end: - return rc ? NULL : proto; -} - - -cwal_value * s2_prototype_integer( s2_engine * se ){ - static char const * stashKey = "Integer"; - int rc = 0; - cwal_value * proto; - cwal_value * v = 0; - cwal_value * numProto = s2_prototype_number(se); - if(!numProto) return 0; - proto = s2_prototype_stashed(se, stashKey); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash(se, stashKey, proto); - if(!rc) rc = cwal_prototype_base_set(se->e, CWAL_TYPE_INTEGER, proto ); - if(rc) goto end; - cwal_value_prototype_set(proto, numProto); - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_INTEGER)); - -#define SETV(NAME) \ - if(!v){ rc = CWAL_RC_OOM; goto end; } \ - cwal_value_ref(v); \ - rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - v = 0; \ - if(rc) goto end - - v = cwal_new_integer(se->e, CWAL_INT_T_MIN); - SETV("INT_MIN"); - v = cwal_new_integer(se->e, CWAL_INT_T_MAX); - SETV("INT_MAX"); - - { - const s2_func_def funcs[] = { - S2_FUNC2("toChar", s2_cb_int_to_utf8char), - S2_FUNC2("rand", s2_cb_rand_int), - S2_FUNC2("srand", s2_cb_srand_int), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - -#undef SETV - end: - return rc ? NULL : proto; -} - - -static int s2_cb_dbl_floor( cwal_callback_args const * args, cwal_value **rv ){ - THIS_DOUBLE; -#if 1 - if(!self){ - *rv = cwal_new_integer( args->engine, (cwal_int_t)self ); - return 0; - } - *rv = cwal_new_integer(args->engine, - (cwal_int_t)(self>0 ? self : (self-1))); -#else - /* Requires C99 */ - *rv = cwal_new_double( args->engine, (cwal_int_t)floor((double)self)); -#endif - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_dbl_ceil( cwal_callback_args const * args, cwal_value **rv ){ - THIS_DOUBLE; -#if 1 - if(!self){ - *rv = cwal_new_integer( args->engine, (cwal_int_t)self); - return 0; - }else if(self<=0){ - *rv = cwal_new_integer(args->engine, (cwal_int_t)(self)); - }else{ - *rv = cwal_new_integer(args->engine, - (cwal_int_t)(((cwal_double_t)(cwal_int_t)self)==self - ? self : self+1)); - } -#else - /* Requires C99 */ - *rv = cwal_new_integer( args->engine, (cwal_int_t)ceil((double)self)); -#endif - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_dbl_format( cwal_callback_args const * args, cwal_value **rv ){ - int left = 0, right = 0; - enum { BufLen = 200 }; - char buf[BufLen] = {0}; - THIS_DOUBLE; - if(args->argc>0){ - left = (int)cwal_value_get_integer(args->argv[0]); - if(args->argc>1) right = (int)cwal_value_get_integer(args->argv[1]); - else{ - right = left; - left = 0; - } - } - sprintf( buf, "%%%d.%d"CWAL_DOUBLE_T_PFMT, left, right ); - *rv = cwal_string_value( cwal_new_stringf( args->engine, buf, self ) ); - return *rv ? 0 : CWAL_RC_OOM; -} - -cwal_value * s2_prototype_double( s2_engine * se ){ - static char const * stashKey = "Double"; - int rc = 0; - cwal_value * proto; - cwal_value * numProto = s2_prototype_number(se); - if(!numProto) return 0; - proto = s2_prototype_stashed(se, stashKey); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash(se, stashKey, proto); - if(!rc) rc = cwal_prototype_base_set(se->e, CWAL_TYPE_DOUBLE, proto ); - if(rc) goto end; - cwal_value_prototype_set(proto, numProto); - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_DOUBLE)); - -#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define RC if(rc) goto end; - - { - const s2_func_def funcs[] = { - S2_FUNC2("ceil", s2_cb_dbl_ceil), - S2_FUNC2("floor", s2_cb_dbl_floor), - S2_FUNC2("format", s2_cb_dbl_format), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - -#if 0 - /* FUNC2("round", s2_cb_dbl_round); */ -#endif - -#undef FUNC2 -#undef CHECKV -#undef RC - end: - return rc ? NULL : proto; -} - -#undef MARKER -#undef ARGS_SE -#undef THIS_INT -#undef THIS_DOUBLE -/* end of file number.c */ -/* start of file ob.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include -#include - -#if 0 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -/** - Internal state for the OB APIs. -*/ -struct S2ObBuffer { - s2_engine * se; - cwal_buffer buf; -}; -typedef struct S2ObBuffer S2ObBuffer; - -static const int S2ObBuffer_typeid = 0; - -static void s2_S2ObBuffer_finalize( cwal_engine * e, void * m ){ - S2ObBuffer * st = (S2ObBuffer *)m; - cwal_buffer_reserve(e, &st->buf, 0); - st->se = NULL; - cwal_free2(e, m, sizeof(S2ObBuffer)); -} - -static int s2_S2ObBuffer_output_f( void * state, void const * src, cwal_size_t n ){ - int rc = 0; - S2ObBuffer * ob = (S2ObBuffer*) state; - if((ob->buf.used+n) >= ob->buf.capacity - /* >= b/c of implicitly added NUL */){ - cwal_size_t nSize = ob->buf.capacity - ? ((ob->buf.capacity+n+1) * 3/2) - : n*2; - cwal_size_t const oflow = nSize; - if(oflow > nSize) goto overflow; - else if(!nSize) nSize = 50/*arbitrary*/; - /* MARKER(("nSize=%d\n", (int)nSize)); */ - if(nSize == (ob->buf.used+n)){ - /* corner case: the buffer API appends a NUL, so account for - that here to avoid yet another reallocation in - cwal_buffer_append(). */ - ++nSize; - if(!nSize) goto overflow; - } - /* MARKER(("nSize=%d\n", (int)nSize)); */ - rc = cwal_buffer_reserve(ob->se->e, &ob->buf, nSize); - } - return rc ? rc : cwal_buffer_append(ob->se->e, &ob->buf, src, n); - overflow: - return cwal_exception_setf(ob->se->e, CWAL_RC_RANGE, - "cwal_size_t overflow in buffer " - "size calculation."); -} - -static int s2_ob_push_outputer( s2_engine * se, cwal_outputer const * out ){ - if(!se || !out) return CWAL_RC_MISUSE; - else{ - int rc; - cwal_outputer * co = - (cwal_outputer *)cwal_malloc2(se->e, sizeof(cwal_outputer)); - if(co){ - rc = cwal_list_append( se->e, &se->ob, co ); - }else{ - rc = CWAL_RC_OOM; - } - if(rc){ - if(co) cwal_free2(se->e, co, sizeof(cwal_outputer)); - }else{ - *co = se->e->vtab->outputer /* Store the old one */; - se->e->vtab->outputer = *out /* Replace it with the new one. */; - } - return rc; - } -} - -#if 0 -/** - This is how we "could" integrate OB's flush() with cwal_output(), - but doing so causes functions which flush (e.g. the default print() - impl) to Do The Wrong Thing when we do that. So s2.io.flush() - becomes a no-op while buffering is on. - */ -int s2_ob_flush( s2_engine * ie ); -static int s2_S2ObBuffer_flush(void * ob){ - return s2_ob_flush( ((S2ObBuffer*)ob)->ie ); -} -#endif - -cwal_size_t s2_ob_level( s2_engine * se ){ - return se ? se->ob.count : 0; -} - -static S2ObBuffer * s2_ob_state_ptr( cwal_outputer const * from ){ - S2ObBuffer * rc = from->state.typeID == &S2ObBuffer_typeid - ? (S2ObBuffer*)from->state.data - : NULL; - assert(rc && "Seems someone has mucked with a cwal_outputer instance"); - return rc; -} - -#define s2__ob_current_buffer(SE) s2_ob_state_ptr(&(SE)->e->vtab->outputer) - -int s2_ob_push( s2_engine * se ){ - int rc; - cwal_outputer out = cwal_outputer_empty; - S2ObBuffer * ob; - assert(se); - ob = (S2ObBuffer *)cwal_malloc2(se->e, sizeof(S2ObBuffer)); - if(!ob) return CWAL_RC_OOM; - ob->se = se; - ob->buf = cwal_buffer_empty; - out.state.data = ob; - out.state.finalize = s2_S2ObBuffer_finalize; - out.state.typeID = &S2ObBuffer_typeid; - out.output = s2_S2ObBuffer_output_f; - /* out.flush = s2_S2ObBuffer_flush; */ - rc = s2_ob_push_outputer( se, &out ); - if(rc){ - cwal_free2( se->e, ob, sizeof(S2ObBuffer) ); - } - return rc; -} - -int s2_ob_reserve( s2_engine * se, cwal_size_t reserveBufSize ){ - if(!se) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - S2ObBuffer * const ob = s2__ob_current_buffer(se); - return cwal_buffer_reserve(se->e, &ob->buf, reserveBufSize); - } -} - -int s2_ob_pop( s2_engine * se ){ - if(!se) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - cwal_size_t const i = se->ob.count-1; - cwal_outputer * ob = (cwal_outputer *)se->ob.list[i]; - cwal_outputer * io = &se->e->vtab->outputer; - if(io->state.finalize){ - io->state.finalize(se->e, io->state.data); - } - *io = *ob; - cwal_free2(se->e, ob, sizeof(cwal_outputer)); - --se->ob.count; - return 0; - } -} - -int s2_cb_ob_pop( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_int_t take = 0; - s2_engine * se = s2_engine_from_args(args); - assert(se); - if(!s2_ob_level(se)){ - return cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Cannot pop output buffer: stack is empty."); - } - else if(args->argc) take = cwal_value_get_integer(args->argv[0]); - - if(!take){ - rc = s2_ob_pop( se ); - if(!rc) *rv = args->self; - }else{ - rc = (take<0) - ? s2_cb_ob_take_string(args,rv) - : s2_cb_ob_take_buffer(args,rv); - if(!rc) rc = s2_ob_pop(se); - } - if(rc && (CWAL_RC_OOM!=rc) - && (CWAL_RC_EXCEPTION!=rc) - && (CWAL_RC_INTERRUPTED!=rc)){ - rc = cwal_exception_setf(args->engine, - rc, "Error #%d (%s) popping buffer.", - rc, cwal_rc_cstr(rc)); - } - return rc; -} - -int s2_cb_ob_push( cwal_callback_args const * args, cwal_value **rv ){ - s2_engine * se = s2_engine_from_args(args); - int rc; - assert(se); - rc = s2_ob_push( se ); - if(rc){ - return s2_cb_throw(args, rc, "s2_ob_push() failed."); - }else{ - int rc = 0; - if(args->argc - && cwal_value_is_integer(args->argv[0])){ - cwal_int_t const sz = cwal_value_get_integer(args->argv[0]); - if(sz<0){ - rc = s2_cb_throw(args, CWAL_RC_RANGE, - "Expecting an integer argument >= 0."); - }else{ - rc = s2_ob_reserve(se, (cwal_size_t)sz); - if(rc){ - assert(CWAL_RC_OOM == rc); - s2_ob_pop(se); - } - } - } - if(!rc) *rv = args->self; - return rc; - } -} - -int s2_ob_get( s2_engine * se, cwal_buffer ** tgt ){ - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - S2ObBuffer * const ob = s2__ob_current_buffer(se); - *tgt = &ob->buf; - return 0; - } -} - -int s2_ob_take( s2_engine * se, cwal_buffer * tgt ){ - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - S2ObBuffer * const ob = s2__ob_current_buffer(se); - if(tgt != &ob->buf){ - void * self = tgt->self; - *tgt = ob->buf; - tgt->self = self; - } - ob->buf = cwal_buffer_empty; - return 0; - } -} - -int s2_ob_clear( s2_engine * se, char releaseBufferMem ){ - if(!se) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - S2ObBuffer * const ob = s2__ob_current_buffer(se); - if(releaseBufferMem) cwal_buffer_reserve(se->e, &ob->buf, 0); - else ob->buf.used = 0; - return 0; - } -} - -int s2_ob_flush( s2_engine * se ){ - if(!se) return CWAL_RC_MISUSE; - else if(!se->ob.count) return CWAL_RC_RANGE; - else{ - int rc = 0; - cwal_size_t const i = se->ob.count-1; - cwal_outputer * to = (cwal_outputer *)se->ob.list[i]; - S2ObBuffer * const ob = s2__ob_current_buffer(se); - assert(to != &se->e->vtab->outputer); - if(ob->buf.used){ - rc = to->output( to->state.data, ob->buf.mem, - ob->buf.used ); - } - ob->buf.used = 0; - return rc; - } -} - -int s2_cb_ob_level( cwal_callback_args const * args, cwal_value **rv ){ - s2_engine * se = s2_engine_from_args(args); - assert(se); - *rv = cwal_new_integer(args->engine, (cwal_int_t)se->ob.count); - return *rv ? 0 : CWAL_RC_OOM; -} - -#define OB_THROW_IF_NO_OB if(!se->ob.count){ \ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, \ - "Output buffering is not in effect."); \ - }(void)0 - -int s2_cb_ob_get( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - cwal_buffer * buf = NULL; - assert(se); - OB_THROW_IF_NO_OB; - rc = s2_ob_get(se, &buf); - if(rc) return rc; - else{ - assert(buf); - } - *rv = cwal_new_string_value(args->engine, - (char const *)buf->mem, - buf->used); - return *rv ? 0 : CWAL_RC_OOM; -} - - -/*** -int s2_cb_ob_reserve( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t const sz = args->argc ? cwal_value_get_integer(args->argv[0]) : -1; - if(sz<0) return s2_cb_throw(args, CWAL_RC_RANGE, - "Expecting an integer value >= 0."); - else{ - s2_engine * se = s2_engine_from_args(args); - int const rc = s2_ob_reserve( se, (cwal_size_t)sz ); - assert(se); - return rc ? s2_cb_throw(args, rc, "s2_ob_reserve() failed " - "with code %d (%s).", rc, cwal_rc_cstr(rc)) - : 0; - } -} -***/ - -int s2_cb_ob_take_string( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - cwal_buffer buf = cwal_buffer_empty; - assert(se); - OB_THROW_IF_NO_OB; - rc = s2_ob_take(se, &buf); - if(rc) return rc; -#if 0 - *rv = cwal_new_string_value(args->engine, - (char const *)buf.mem, - buf.used); - rc = *rv ? 0 : CWAL_RC_OOM; -#else - { /* Finally get to use a z-string... */ - cwal_string * s = cwal_buffer_to_zstring(args->engine, &buf); - if(!s) rc = CWAL_RC_OOM; - else{ - *rv = cwal_string_value(s); - /* we just took over ownership of buf.mem */; - assert(!buf.mem); - assert(!buf.used); - assert(!buf.capacity); - } - } -#endif - cwal_buffer_reserve(args->engine, &buf, 0); - return rc; -} - -int s2_cb_ob_take_buffer( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - cwal_buffer * buf; - cwal_value * bv; - assert(se); - OB_THROW_IF_NO_OB; - bv = cwal_new_buffer_value(args->engine, 0); - if(!bv) return CWAL_RC_OOM; - buf = cwal_value_get_buffer(bv); - rc = s2_ob_take(se, buf); - if(rc) cwal_value_unref(bv); - else *rv = bv; - return rc; -} - -int s2_cb_ob_clear( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - assert(se); - OB_THROW_IF_NO_OB; - rc = s2_ob_clear( se, 0 ); - if(rc) rc = cwal_exception_setf(args->engine, rc, - "s2_ob_clear() failed."); - else *rv = args->self; - return rc; -} - -int s2_cb_ob_capture( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - s2_engine * se = s2_engine_from_args(args); - cwal_value * arg; - cwal_function * fCallback; - char const * sCallback; - cwal_size_t nCallback = 0; - cwal_size_t const oldLevel = s2_ob_level(se); - cwal_int_t mode = -1; - cwal_value * xrv = 0; - assert(se); - if(0==args->argc || args->argc>2){ - misuse: - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting (string|function [,int mode]) arguments."); - } - arg = args->argv[0]; - sCallback = cwal_value_is_string(arg) - ? cwal_value_get_cstr(arg, &nCallback) - : 0 /* disallow buffer input b/c of potential for Undefined Behaviour - if it's modified during the buffering process */; - if(!sCallback){ - fCallback = cwal_value_get_function(arg); - if(!fCallback){ - goto misuse; - } - } - if(args->argc>1){ - arg = args->argv[1]; - if(cwal_value_is_buffer(arg)){ - mode = 1; - }else{ - mode = cwal_value_get_integer(arg); - } - } - rc = s2_ob_push(se); - if(rc) return rc; - - if(sCallback){ - rc = s2_eval_cstr(se, 0, "OB capture() callback", - sCallback, (int)nCallback, 0); - }else{ - assert(fCallback); - rc = cwal_function_call(fCallback, args->self, 0, 0, 0); - } - if(!rc){ - if(se->ob.count <= oldLevel){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Too many OB levels were popped via " - "the callback."); - } - } - while(se->ob.count > oldLevel+1){ - if(mode){ - int const rc2 = rc ? rc : s2_ob_flush(se); - if(rc2 && !rc){ - rc = rc2; - /* Don't break - keep popping so that our API guarantees - hold. */ - } - } - s2_ob_pop(se)/*cannot fail*/; - } - if(!rc){ - assert(oldLevel + 1 == se->ob.count - && "Should have been caught above."); - if(0==mode){ - xrv = cwal_value_undefined(); - }else if(0 > mode){ - rc = s2_cb_ob_take_string(args,&xrv); - }else{ - assert(args->argc>0); - arg = args->argv[1]; - if(cwal_value_is_buffer(arg)){ - cwal_buffer * const tgt = cwal_value_get_buffer(arg); - S2ObBuffer * const ob = s2__ob_current_buffer(se); - /* Potential TODO/optimization: this lazy/easy approach - captures (via the above s2_ob_flush()/pop combination) all - captured OB levels into the current OB buffer layer before - copying the memory to tgt. We "could" instead walk UP the - OB stack, before popping it, and append each layer's memory - to tgt (for the first level, if tgt->mem is NULL, we could - transfer ownership of the OB buffer's memory to tgt). We - would need to walk up, not down, the stack so that the - order of the buffers is properly retained. - */ - if(tgt->mem){ - rc = cwal_buffer_append(args->engine, tgt, ob->buf.mem, - ob->buf.used); - }else{ - /* Optimization: simply change ownership of the memory... */ - s2_buffer_swap(tgt, &ob->buf); - assert(!ob->buf.mem); - } - if(!rc) xrv = arg; - }else{ - rc = s2_cb_ob_take_buffer(args,&xrv); - } - } - } - s2_ob_pop(se) /* ignore result - cannot fail */; - if(rc){ - assert(!xrv); - }else{ - assert(xrv); - *rv = xrv; - } - return rc; -} - -int s2_cb_ob_flush( cwal_callback_args const * args, cwal_value **rv ){ - s2_engine * se = s2_engine_from_args(args); - int rc; - assert(se); - OB_THROW_IF_NO_OB; - rc = s2_ob_flush( se ); - if(rc) rc = cwal_exception_setf(args->engine, rc, - "s2_ob_flush() failed."); - else *rv = args->self; - return rc; -} - -int s2_install_ob( s2_engine * se, cwal_value * tgt ){ - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - else{ - s2_func_def const funcs[] = { - S2_FUNC2("takeString", s2_cb_ob_take_string), - S2_FUNC2("takeBuffer", s2_cb_ob_take_buffer), - /* S2_FUNC2("reserve", s2_cb_ob_reserve), */ - S2_FUNC2("push", s2_cb_ob_push), - S2_FUNC2("pop", s2_cb_ob_pop), - S2_FUNC2("level", s2_cb_ob_level), - S2_FUNC2("getString", s2_cb_ob_get), - S2_FUNC2("flush", s2_cb_ob_flush), - S2_FUNC2("clear", s2_cb_ob_clear), - S2_FUNC2("capture", s2_cb_ob_capture), - s2_func_def_empty_m - }; - return s2_install_functions(se, tgt, funcs, 0); - } -} - -int s2_install_ob_2( s2_engine * se, cwal_value * tgt, - char const * name ){ - if(!se || !tgt || !name || !*name) return CWAL_RC_MISUSE; - else { - int rc; - cwal_value * ob = cwal_new_object_value(se->e); - if(!ob) rc = CWAL_RC_OOM; - else if((rc = s2_install_ob(se, ob)) - || - (rc = cwal_prop_set(tgt, name, cwal_strlen(name), ob))){ - cwal_value_unref(ob); - } - return rc; - } -} - -#undef OB_THROW_IF_NO_OB -#undef s2__ob_current_buffer -#undef MARKER -/* end of file ob.c */ -/* start of file ops.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -#define s2__pop_val s2_engine_pop_value -#define s2__pop_tok s2_engine_pop_token - -/** - Operator for unary and binary addition and subtraction. - - Required stack (from top to bottom): - Binary: RHS LHS - Unary: RHS -*/ -static int s2_op_f_addsub( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Operator for multiplication, divisision, and modulus. - - Required stack (from top to bottom): RHS LHS -*/ -static int s2_op_f_multdivmod( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Operator for bitwise OR, AND, XOR, ~, and bitshift << and >>. - - Required stack (from top to bottom): RHS LHS - except for the unary ~ op, which expects 1 operand. -*/ -static int s2_op_f_bitwiseshift( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -static int s2_op_f_not( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -static int s2_op_f_andor( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Operator for comparison ops. - - Required stack (from top to bottom): RHS LHS -*/ -static int s2_op_f_cmp( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -static int s2_op_f_parens( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Operator for comma. It simply discards its lhs operand - and evaluates to its rhs. - - Required stack (from top to bottom): RHS LHS -*/ -static int s2_op_f_comma( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - - -/** - Operator for the dot and hash operators. - - Required stack (top to bottom): LHS -*/ -static int s2_op_f_dot( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Operator for the dot-length operator (X.#). - - Required stack (top to bottom): LHS -*/ -static int s2_op_f_dot_length( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Op impl for the arrow operator (X->Y). Fail if used on any value - which does not overload it. -*/ -static int s2_op_f_oload_arrow( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Impl for the X=Y family of operators. - - Required stack (top to bottom): RHS, KEY -*/ -static int s2_op_f_assign2( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - Impl for the X.Y=Z family of operators. - - Required stack (top to bottom): RHS, KEY, OBJECT -*/ -static int s2_op_f_assign3( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ); - - -static int s2_op_f_incrdecr( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -static int s2_op_f_array_append( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** - A dummy op for experimentation. - - Required stack (from top to bottom): - - ... S2_T_MarkVariadicStart -*/ -static int s2_op_f_foo( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - - -/** - Where the global list of operators is stored. When adding new - membmers, MAKE 100% SURE that: - - a) the entries in the following array line up with the members. - - b) update s2_ttype_op() to return the new member for the appropriate - token type(s). - */ -static const struct S2_OPS__ { - const s2_op add1; - const s2_op add2; - const s2_op sub1; - const s2_op sub2; - const s2_op multiply; - const s2_op divide; - const s2_op modulo; - - const s2_op bitOr; - const s2_op bitXOr; - const s2_op bitAnd; - const s2_op bitNegate; - const s2_op bitShiftLeft; - const s2_op bitShiftRight; - - const s2_op logNot; - const s2_op logAnd; - const s2_op logOr; - const s2_op logOr3; - const s2_op elvis; - /* - logical && and || are partially handled at the tokenizer level to - support short-circuiting. - */ - - const s2_op cmpLT; - const s2_op cmpLE; - const s2_op cmpEq; - const s2_op cmpEqStrict; - const s2_op cmpNotEq; - const s2_op cmpNotEqStrict; - const s2_op cmpGT; - const s2_op cmpGE; - const s2_op inherits; - const s2_op notInherits; - const s2_op contains; - const s2_op notContains; - - const s2_op assign; - const s2_op assignConst; - const s2_op assignMember; - const s2_op assignMemberConst; - const s2_op assignPlus; - const s2_op assignPlus3; - const s2_op assignMinus; - const s2_op assignMinus3; - const s2_op assignMult; - const s2_op assignMult3; - const s2_op assignDiv; - const s2_op assignDiv3; - const s2_op assignModulo; - const s2_op assignModulo3; - const s2_op assignShiftLeft; - const s2_op assignShiftLeft3; - const s2_op assignShiftRight; - const s2_op assignShiftRight3; - const s2_op assignXOr; - const s2_op assignXOr3; - const s2_op assignAnd; - const s2_op assignAnd3; - const s2_op assignOr; - const s2_op assignOr3; - - const s2_op incrPre; - const s2_op incrPost; - const s2_op decrPre; - const s2_op decrPost; - - const s2_op parenOpen; - const s2_op parenClose; - - const s2_op comma; - const s2_op dot; - const s2_op dotDot; - const s2_op dotLength; - const s2_op dotHash; - const s2_op arrow; - const s2_op arrow2; - const s2_op colon2; - const s2_op arrayAppend; - - /** - We need a ternary s2_op to support the ternary operator, but it - is not used by the stack machine itself. - */ - const s2_op ternary; - - const s2_op rhsEval; - - const s2_op foo; - const s2_op _sentry_; -} S2_OPS = { -/*{ sym, id, arity, assoc, prec, placement, call(), inferLeft, inferRight, derivedFromOp } */ - { "+X", S2_T_OpPlusUnary, 1, 1, S2_PR_PlusUnary, -1, s2_op_f_addsub, 0, 0, 0 }, - { "X+Y", S2_T_OpPlus, 2, -1, S2_PR_Plus, 0, s2_op_f_addsub, 0, 0, 0 }, - - { "-X", S2_T_OpMinusUnary, 1, 1, S2_PR_MinusUnary, -1, s2_op_f_addsub, 0, 0, 0 }, - { "X-Y", S2_T_OpMinus, 2, -1, S2_PR_Minus, 0, s2_op_f_addsub, 0, 0, 0 }, - - { "X*Y", S2_T_OpMultiply, 2, -1, S2_PR_Multiply, 0, s2_op_f_multdivmod, 0, 0, 0 }, - { "X/Y", S2_T_OpDivide, 2, -1, S2_PR_Divide, 0, s2_op_f_multdivmod, 0, 0, 0 }, - { "X%Y", S2_T_OpModulo, 2, -1, S2_PR_Modulo, 0, s2_op_f_multdivmod, 0, 0, 0 }, - - { "X|Y", S2_T_OpOrBitwise, 2, -1, S2_PR_BitwiseOr, 0, s2_op_f_bitwiseshift, 0, 0, 0 }, - { "X^Y", S2_T_OpXOr, 2, -1, S2_PR_BitwiseXor, 0, s2_op_f_bitwiseshift, 0, 0, 0 }, - { "X&Y", S2_T_OpAndBitwise, 2, -1, S2_PR_BitwiseAnd, 0, s2_op_f_bitwiseshift, 0, 0, 0 }, - { "~X", S2_T_OpNegateBitwise, 1, 1, S2_PR_BitwiseNegate, -1, s2_op_f_bitwiseshift, 0, 0, 0 }, - { "X<>Y", S2_T_OpShiftRight, 2, -1, S2_PR_ShiftRight, 0, s2_op_f_bitwiseshift, 0, 0, 0 }, - - { "!X", S2_T_OpNot, 1, 1, S2_PR_LogicalNot, -1, s2_op_f_not, 0, 0, 0 }, - /* &&, ||, and ||| are handled at the parser level for - short-cicuiting, but the stack machine supports them directly - without short-circuiting. Why? It's a stack machine, so the RHS - has already been processed (or the stack wouldn't be running), so - short-circuiting evaluation is not possible at that - point. - */ - { "X&&Y", S2_T_OpAnd, 2, -1, S2_PR_LogicalAnd, 0, s2_op_f_andor, 0, 0, 0 }, - { "X||Y", S2_T_OpOr, 2, -1, S2_PR_LogicalOr, 0, s2_op_f_andor, 0, 0, 0 }, - { "X|||Y", S2_T_OpOr3, 2, -1, S2_PR_LogicalOr3, 0, s2_op_f_andor, 0, 0, 0 }, - { "X?:Y", S2_T_OpElvis, 2, -1, S2_PR_LogicalOrElvis, 0, s2_op_f_andor, 0, 0, 0 }, - - { "X !(X>Y) */ }, - { "X==Y", S2_T_CmpEq, 2, -1, S2_PR_CmpEq, 0, s2_op_f_cmp, 0, 0, 0 }, - { "X===Y", S2_T_CmpEqStrict, 2, -1, S2_PR_CmpEqStrict, 0, s2_op_f_cmp, 0, 0, 0 }, - { "X!=Y", S2_T_CmpNotEq, 2, -1, S2_PR_CmpNotEq, 0, s2_op_f_cmp, 0, 0, - S2_T_CmpEq }, - { "X!==Y", S2_T_CmpNotEqStrict, 2, -1, S2_PR_CmpNotEqStrict, 0, s2_op_f_cmp, 0, 0, - S2_T_CmpEqStrict }, - { "X>Y", S2_T_CmpGT, 2, -1, S2_PR_CmpGT, 0, s2_op_f_cmp, 0, 0, 0 }, - { "X>=Y", S2_T_CmpGE, 2, -1, S2_PR_CmpGE, 0, s2_op_f_cmp, - S2_T_CmpGT, S2_T_CmpEq, S2_T_CmpLT /* i.e. X>=Y ==> !(X>=Y", S2_T_OpShiftRightAssign, 2, 1, S2_PR_OpAssign, 0, s2_op_f_assign2, 0, 0, - S2_T_OpShiftRight }, - { "X.Y>>=Z", S2_T_OpShiftRightAssign3, 3, 1, S2_PR_OpAssign, 0, s2_op_f_assign3, 0, 0, - S2_T_OpShiftRight }, - { "X^=Y", S2_T_OpXOrAssign, 2, 1, S2_PR_OpAssign, 0, s2_op_f_assign2, 0, 0, - S2_T_OpXOr }, - { "X.Y^=Z", S2_T_OpXOrAssign3, 3, 1, S2_PR_OpAssign, 0, s2_op_f_assign3, 0, 0, - S2_T_OpXOr }, - { "X&=Y", S2_T_OpAndAssign, 2, 1, S2_PR_OpAssign, 0, s2_op_f_assign2, 0, 0, - S2_T_OpAnd }, - { "X.Y&=Z", S2_T_OpAndAssign3, 3, 1, S2_PR_OpAssign, 0, s2_op_f_assign3, 0, 0, - S2_T_OpAnd }, - { "X|=Y", S2_T_OpOrAssign, 2, 1, S2_PR_OpAssign, 0, s2_op_f_assign2, 0, 0, - S2_T_OpOr }, - { "X.Y|=Z", S2_T_OpOrAssign3, 3, 1, S2_PR_OpAssign, 0, s2_op_f_assign3, 0, 0, - S2_T_OpOr }, - - { "++X", S2_T_OpIncrPre, 1, 1, S2_PR_IncrDecr, -1, s2_op_f_incrdecr, 0, 0, 0 }, - { "X++", S2_T_OpIncrPost, 1, -1, S2_PR_IncrDecr, 1, s2_op_f_incrdecr, 0, 0, 0 }, - { "--X", S2_T_OpDecrPre, 1, 1, S2_PR_IncrDecr, -1, s2_op_f_incrdecr, 0, 0, 0 }, - { "X--", S2_T_OpDecrPost, 1, -1, S2_PR_IncrDecr, 1, s2_op_f_incrdecr, 0, 0, 0 }, - - { "(", S2_T_ParenOpen, 0, -1, S2_PR_ParensOpen, -1, s2_op_f_parens, 0, 0, 0 }, - { ")", S2_T_ParenClose, 0, -1, S2_PR_ParensClose, 1, s2_op_f_parens, 0, 0, 0 }, - - { "X,Y", S2_T_Comma, 2, -1, S2_PR_Comma, 0, s2_op_f_comma, 0, 0, 0 }, - { "X.Y", S2_T_OpDot, 2, -1, S2_PR_DotDeref, 0, s2_op_f_dot, 0, 0, 0 }, - { "X..", S2_T_OpDotDot, 2, -1, S2_PR_DotDot, 0, 0, 0, 0, 0 }, - { "X.#", S2_T_OpDotLength, 1, -1, S2_PR_DotDeref, 1, s2_op_f_dot_length, 0, 0, 0 }, - { "X#Y", S2_T_OpHash, 2, -1, S2_PR_DotDeref, 0, s2_op_f_dot, 0, 0, 0 }, - { "X->Y", S2_T_OpArrow, 2, -1, S2_PR_OpArrow, 0, s2_op_f_oload_arrow, 0, 0, 0 }, - /* S2_T_OpArrow2 is an internal-use pseudo-operator used to tell s2_eval_expr_impl() that - it should stop processing. It is ONLY syntactically valid when a caller of s2_eval_expr_impl() - passes this op as the "fromLhsOp" parameter. - - 201608: Is that still true? i'd like to make => overrideable, but that would break - foreach(x=>...). We could rename that to foreach(x as ...)? - */ - { "X=>Y", S2_T_OpArrow2, 0, -1, S2_PR_OpArrow2, 0, 0, 0, 0, 0 }, - { "X::Y", S2_T_OpColon2, 2, -1, S2_PR_OpColon2, 0, s2_op_f_oload_arrow, 0, 0, 0 }, - - { "X[]=Y", S2_T_ArrayAppend, 2, 1, S2_PR_ArrayAppend, 0, s2_op_f_array_append, 0, 0, 0 }, - - { "X?Y:Z", S2_T_Question, 3, 1, S2_PR_TernaryIf, 0, 0/*handled at parser level */, 0, 0, 0 }, - - /* RHSEval was initially an experiment in handling precedence during - recursive evaling, but it's no longer used. */ - { "...RHS", S2_T_RHSEval, 1, 1, S2_PR_RHSEval, 0, 0/*handled at parser level */ , 0, 0, 0 }, - - /* FOO is/was only used for internal by-hand tests of the stack - machine. */ - { "FOO...", S2_T_Foo, -1, 1, S2_PR_Keyword, 0, s2_op_f_foo, 0, 0, 0 }, - - /*_sentry_*/s2_op_empty_m -}; - - -s2_op const * s2_ttype_op( int ttype ){ - switch( ttype ){ -#define OP(E,M) case E: return &S2_OPS.M - OP(S2_T_OpPlusUnary,add1); - OP(S2_T_OpPlus,add2); - OP(S2_T_OpMinusUnary,sub1); - OP(S2_T_OpMinus,sub2); - - OP(S2_T_OpMultiply,multiply); - OP(S2_T_OpDivide,divide); - OP(S2_T_OpModulo,modulo); - - OP(S2_T_OpOrBitwise,bitOr); - OP(S2_T_OpXOr,bitXOr); - OP(S2_T_OpAndBitwise,bitAnd); - OP(S2_T_OpNegateBitwise,bitNegate); - OP(S2_T_OpShiftLeft,bitShiftLeft); - OP(S2_T_OpShiftRight,bitShiftRight); - - OP(S2_T_OpNot,logNot); - OP(S2_T_OpAnd,logAnd); - OP(S2_T_OpOr,logOr); - OP(S2_T_OpOr3,logOr3); - OP(S2_T_OpElvis,elvis); - - OP(S2_T_CmpLT,cmpLT); - OP(S2_T_CmpLE,cmpLE); - OP(S2_T_CmpEq,cmpEq); - OP(S2_T_CmpEqStrict,cmpEqStrict); - OP(S2_T_CmpNotEq,cmpNotEq); - OP(S2_T_CmpNotEqStrict,cmpNotEqStrict); - OP(S2_T_CmpGT,cmpGT); - OP(S2_T_CmpGE,cmpGE); - OP(S2_T_OpInherits,inherits); - OP(S2_T_OpNotInherits,notInherits); - OP(S2_T_OpContains,contains); - OP(S2_T_OpNotContains,notContains); - - OP(S2_T_OpAssign,assign); - OP(S2_T_OpColonEqual,assignConst); - OP(S2_T_OpAssign3,assignMember); - OP(S2_T_OpAssignConst3,assignMemberConst); - OP(S2_T_OpPlusAssign,assignPlus); - OP(S2_T_OpPlusAssign3,assignPlus3); - OP(S2_T_OpMinusAssign,assignMinus); - OP(S2_T_OpMinusAssign3,assignMinus3); - OP(S2_T_OpMultiplyAssign,assignMult); - OP(S2_T_OpMultiplyAssign3,assignMult3); - OP(S2_T_OpDivideAssign, assignDiv); - OP(S2_T_OpDivideAssign3, assignDiv3); - OP(S2_T_OpModuloAssign, assignModulo); - OP(S2_T_OpModuloAssign3, assignModulo3); - OP(S2_T_OpShiftLeftAssign, assignShiftLeft); - OP(S2_T_OpShiftLeftAssign3, assignShiftLeft3); - OP(S2_T_OpShiftRightAssign, assignShiftRight); - OP(S2_T_OpShiftRightAssign3, assignShiftRight3); - OP(S2_T_OpXOrAssign, assignXOr); - OP(S2_T_OpXOrAssign3, assignXOr3); - OP(S2_T_OpAndAssign, assignAnd); - OP(S2_T_OpAndAssign3, assignAnd3); - OP(S2_T_OpOrAssign, assignOr); - OP(S2_T_OpOrAssign3, assignOr3); - - OP(S2_T_OpIncrPre, incrPre); - OP(S2_T_OpIncrPost, incrPost); - OP(S2_T_OpDecrPre, decrPre); - OP(S2_T_OpDecrPost, decrPost); - - OP(S2_T_ParenOpen,parenOpen); - OP(S2_T_ParenClose,parenClose); - - OP(S2_T_Comma,comma); - OP(S2_T_OpDot,dot); -#if 0 - OP(S2_T_OpDotDot,dotDot); -#else - case S2_T_OpDotDot: return 0; -#endif - OP(S2_T_OpDotLength,dotLength); - OP(S2_T_OpHash,dotHash); - OP(S2_T_OpArrow,arrow); - OP(S2_T_OpArrow2,arrow2); - OP(S2_T_OpColon2,colon2); - OP(S2_T_ArrayAppend,arrayAppend); - OP(S2_T_Question,ternary); - OP(S2_T_RHSEval,rhsEval); - OP(S2_T_Foo,foo); -#undef OP - default: - return 0; - } -} - -s2_op const * s2_stoken_op( s2_stoken const * t ){ - return t ? s2_ttype_op( t->ttype ) : 0; -} - -int s2_op_is_unary_prefix( s2_op const * op ){ - return (1==op->arity && op->placement<0) ? op->id : 0; -} - - -int s2_op_is_math( s2_op const * op ){ - switch(op ? op->id : 0){ - case S2_T_OpPlus: - case S2_T_OpMinus: - case S2_T_OpMultiply: - case S2_T_OpDivide: - case S2_T_OpModulo: - case S2_T_OpOrBitwise: - case S2_T_OpXOr: - case S2_T_OpAndBitwise: - case S2_T_OpShiftRight: - case S2_T_OpShiftLeft: - return op->id; - default: - return 0; - } -} - -int s2_op_is_expr_border( s2_op const * op ){ - switch(op ? op->id : 0){ - case S2_T_ParenOpen: - case S2_T_ParenClose: - case S2_T_BraceOpen: - case S2_T_BraceClose: - case S2_T_Comma: - case S2_T_Semicolon: - case S2_T_Colon: - return op->id; - default: - return 0; - } -} - -int s2_op_short_circuits( s2_op const * op ){ -#if 1 - return op ? s2_ttype_short_circuits(op->id) : 0; -#else - switch( op ? op->id : 0 ){ - case S2_T_OpOr: - case S2_T_OpAnd: - case S2_T_Question: - return op->id; - default: return 0; - } -#endif -} - -/** - If ttype refers to an overloadable operator type and v is _capable_ - of overloading that operator, this function returns the string form - of the overloaded operator method and sets *len (if not NULL) to - the length of that name. It does not confirm that v actually does - overload it, only that the type/operator combination is potentially - legal. - - Returns 0 if v is of a type or type/operator combination for which - we prohibit overloading. -*/ -char const * s2_overload_name( int ttype, cwal_value const * v, cwal_size_t * len ){ - if(len) *len = 0; - - switch(ttype){ - /* overloadable for any types (these only exist for overloading)... */ -#define CASE(W,T) case T: if(len) *len=sizeof(W)-1/*NUL!*/; return W - CASE("operator->", S2_T_OpArrow); - CASE("operator=~", S2_T_OpContains); - CASE("operator!~", S2_T_OpNotContains); - /*CASE("operator..", S2_T_OpDotDot);*/ - CASE("operator::", S2_T_OpColon2); -#undef CASE - default: - break; - }; - - switch(cwal_value_type_id(v)){ - /* These type/operator combinations are prohibited */ - case CWAL_TYPE_UNDEF: - case CWAL_TYPE_NULL: - case CWAL_TYPE_BOOL: - case CWAL_TYPE_INTEGER: - case CWAL_TYPE_DOUBLE: - case CWAL_TYPE_UNIQUE: - return 0; - case CWAL_TYPE_STRING: - /* This subset is up for reconsideration. Is there a compelling - reason to disallow the others, aside from the convenience of - relying on default string-to-number coercion we get with - them? Maybe just prohibit overloading unary -/+? - */ - switch(ttype){ - case S2_T_OpPlus: break; - case S2_T_OpMultiply: break; - case S2_T_OpModulo: break; - case S2_T_OpDivide: break; - default: - if(s2_ttype_is_assignment_combo(ttype)) break; - else return 0; - } - default: - break; - } - - switch(ttype){ - /* These ops require overloads for all types not ruled out - above. */ -#define CASE(W,T) case T: if(len) *len=sizeof(W)-1/*NUL!*/; return W -#define COMBO(T, W,W2) CASE(W, T); CASE(W2, T##Assign); CASE(W2, T##Assign3) - COMBO(S2_T_OpPlus, "operator+", "operator+="); - COMBO(S2_T_OpMinus, "operator-", "operator-="); - COMBO(S2_T_OpMultiply, "operator*", "operator*="); - COMBO(S2_T_OpDivide, "operator/", "operator/="); - COMBO(S2_T_OpModulo, "operator%", "operator%="); - COMBO(S2_T_OpShiftLeft, "operator<<", "operator<<="); - COMBO(S2_T_OpShiftRight, "operator>>", "operator>>="); - COMBO(S2_T_OpXOr, "operator^", "operator^="); - CASE("operator&", S2_T_OpAndBitwise); - CASE("operator&=", S2_T_OpAndAssign); - CASE("operator&=", S2_T_OpAndAssign3); - CASE("operator|", S2_T_OpOrBitwise); - CASE("operator|=", S2_T_OpOrAssign); - CASE("operator|=", S2_T_OpOrAssign3); -#undef COMBO - - CASE("+operator", S2_T_OpPlusUnary); - CASE("-operator", S2_T_OpMinusUnary); - - CASE("++operator", S2_T_OpIncrPre); - CASE("operator++", S2_T_OpIncrPost); - - CASE("--operator", S2_T_OpDecrPre); - CASE("operator--", S2_T_OpDecrPost); - - CASE("operator<", S2_T_CmpLT); - CASE("operator<=", S2_T_CmpLE); - - CASE("operator>", S2_T_CmpGT); - CASE("operator>=", S2_T_CmpGE); - - CASE("operator==", S2_T_CmpEq); - CASE("operator!=", S2_T_CmpNotEq); - - /* CASE("operator#", S2_T_OpHash); - - Overloading of # was removed, as it (A) will complicate - extending # to support assignment (insert()) and (B) it - essentially negates the reason for having the # operator: an - access method for hashes which makes them competative with - Objects (using Hash.search() has function call overhead which - obj[prop] does not, making objects faster for most cases). - */ - /* CASE("operator.", S2_T_OpDot); */ -#undef CASE - default: - if(len) *len = 0; - return 0; - }; -} - -/** - Internal helper for overloadable operators. - - op is the operator being acted upon and it must have an arity of 1 - or 2. se is the interpreter. lhs and rhs are the LHS and RHS of the - operator (their requirements are described below). - - If no overload of op is legal for this type (see - s2_overload_name()) then 0 is returned and *theRc is set to 0. - i.e. a non-error. - - The POD types either do not participate, or participate in - castrated form (e.g. CWAL_TYPE_STRING only observes a limited set - of overloads). - - If non-0 is returned and *theRc is 0 after returning, it means an - overload was successfully called and its result value is stored in - *rv. If 1 is returned, an overload was truly found. If 2 is - returned, a synthesized overload was found, in which case the - caller should possibly force "return this" semantics on its result - before applying it. - - If *theRc is set to non-0 after returning, it means an error was - triggered, and the caller must not proceed with the operation. - - It may return >0 and set *theRc to non-0, meaning that the error - indicated by *theRc was triggered by/via an overload. - - If an overload is legal but (lhs ? lhs : rhs) does not contain it, - an error is triggered describing the missing operator (if requireIt - is true), or *theRc set it to 0 and 0 is returned if !requireIt. - - We require explicit overloading for all non-POD types, and treat - missing ops as an error unless requireIt is false. The thinking is: - rather that than using built-in ops in senseless contexts. i don't - want s2 to turn into a truly type-sloppy language, despite being - "loosely typed." If this proves to be a hindrance, we can always - make it more sloppy by calling back to the built-in operators - (i.e. coercion to numeric types). - - Conventions: - - For overload calls, the 'this' is always (lhs ? lhs : rhs), - and it may not be 0. - - if op->artity==3, neither lhs nor rhs may be NULL. The op is - assumed to be an object property assignment form of a binary - operation. e.g. X+=Z vs X.Y+=Z. The overload is called with lhs as - the 'this' and (rhs) argument. - - if op->arity==2: - - - lhs!=0, rhs!=0, assumed to be an infix binary operator. The - overload is called with lhs as the 'this' and (rhs) argument. - - If op->arity==1: - - - One of lhs or rhs must be 0. - - - If op->placement<0 (prefix op) or op->placement>0 (postfix op) - then the overload is called without parameters. -*/ -static char s2_op_check_overload( s2_op const * op, s2_engine * se, - char requireIt, - cwal_value * lhs, cwal_value * rhs, - cwal_value ** rv, int * theRc ){ - cwal_value * theThis = lhs ? lhs : rhs; - cwal_size_t opLen = 0; - char const * opName = s2_overload_name( op->id, theThis, &opLen ); - cwal_value * fv = 0; - cwal_function * fn; - int rc; - /* s2_dump_val(theThis,op->sym); */ - /* MARKER(("op->sym=%s\n", op->sym)); */ - if(!opName) { -#if 0 - switch(cwal_value_type_id(theThis)){ - case CWAL_TYPE_STRING: - if(s2_ttype_is_assignment_combo(op->id)){ - *theRc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED, - "Immutable strings may not " - "use the %s operator.", - op->sym); - break; - } - CWAL_SWITCH_FALL_THROUGH; - default: - *theRc = 0; - } -#else - *theRc = 0; -#endif - return 0; - } - /* MARKER(("opName=%s\n", opName)); */ - rc = s2_get( se, theThis, opName, opLen, &fv ); - if(rc){ - *theRc = rc; - return 0; - } - else if(!fv || !(fn = cwal_value_function_part(se->e, fv))){ - /* Not found or non-function property */ - - /* - TODO: check op->inferLeft and/or op->inferRight and see if we - can create a combo operator out of one or two others which - theThis does overload: - - != and !== can be inferred by applying ! to the ==/=== operators. - - Other ops can specify op->inferLeft/Right to tell us which - two ops to combine. - */ -#if 0 - /* - Enable this block to automatically derive combo assignment ops - from their non-assignment variants. - - 20160214: this actually seems to work, but has an interesting - problem/property: - - var a = [1,2,3]; - a.'operator+' = proc(self,arg){return [@self,arg]}; // return !this - a += 4; - // ^^^^ [1,2,3,4], but a different instance, so a.operator+ - // is now gone! - - Is seems the only sane behaviour in overloaded ops is to always - return "this". But maybe there are other valid uses? Can't think - of any. Should we force 'this' as the result for such overloads? - */ - if(op->derivedFromOp){ - char cc; - assert(s2_ttype_is_assignment_combo(op->id)); -# if 1 - MARKER(("Checking derived op %s\n", s2_ttype_op(op->derivedFromOp)->sym )); -# endif - assert(s2_ttype_op(op->derivedFromOp)); - cc = s2_op_check_overload( s2_ttype_op(op->derivedFromOp), - se, requireIt, lhs, rhs, - rv, theRc ); - /*if(cc && !*theRc){ - s2_dump_val(lhs,"Forcing 'this' result..."); - *rv = theThis; - }*/ - return cc ? cc+1 : cc; - } -#endif - if(!requireIt) *theRc = 0; - else *theRc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - "LHS (type %s) has no '%s' " - "(%s operator overload).", - cwal_value_type_name(theThis), - opName, op->sym); - return 0; - }else{ - cwal_value * argv[2] = {0,0}; - int argc = 0; - switch(op->arity){ - case 1: -#if 0 - if(op->placement<0){ - /*prefix op*/ - }else{/*postfix*/ -#if 0 - /* Historical behaviour. Removed 20190804 when the prefix - ++/-- ops were renamed so that they no longer collide - with (postfix ++/--). With that change, this behaviour - is no longer needed. */ - assert(op->placement!=0); - argc = 1; - argv[0] = theThis; -#endif - } -#endif - break; - case 2: - argc = 1; - /* argv[0] = lhs; */ - argv[0] = rhs; - break; - default: - /* X.Y OP Z: - - var obj = {a:1}; - obj.a += 1; - - At this point in the processing we only have (a, 1), but - that's okay, as the caller has the obj part and will apply - the result back to it after this operation. - */ - assert(3==op->arity); - assert(s2_ttype_is_assignment_combo(op->id)); - argc = 1; - /* argv[0] = lhs; */ - argv[0] = rhs; - break; - } - if(lhs) cwal_value_ref(lhs); - if(rhs) cwal_value_ref(rhs); - if(0){ - MARKER(("overload %s: argc=%d\n", op->sym, (int)argc)); - s2_dump_val(theThis,"theThis"); - s2_dump_val(argv[0],"argv[0]"); - s2_dump_val(argv[1],"argv[1]"); - } - *theRc = cwal_function_call( fn, theThis, rv, argc, argv ); - if(0){ - s2_dump_val(*rv, "overload call *rv"); - } - if(lhs) cwal_value_unhand(lhs); - if(rhs) cwal_value_unhand(rhs); - return 1; - } -} - - -int s2_op_f_addsub( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - cwal_value * rhs = 0; - cwal_value * lhs = 0; - char isAdd = 0; - switch(op->id){ - case S2_T_OpPlus: - isAdd = 1; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_OpMinus: - assert( 2 == argc ); - rhs = s2__pop_val( se ); - lhs = s2__pop_val( se ); - break; - case S2_T_OpPlusUnary: - isAdd = 1; - CWAL_SWITCH_FALL_THROUGH; - case S2_T_OpMinusUnary: - assert( 1 == argc ); - rhs = s2__pop_val( se ); - lhs = 0; - /* s2_dump_val(rhs, "unary addsub rhs"); */ - break; - default: - assert(!"WRONG!"); - if(argc){/*avoid unused param warning*/} - return CWAL_RC_ASSERT; - } - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - return 0; - }else{ - int rc = 0; - cwal_value_ref(lhs); - cwal_value_ref(rhs); - if(!s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc) - && !rc){ - rc = s2_values_addsub( se, isAdd, lhs, rhs, rv); - } - if(!rc) cwal_value_ref(*rv); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - if(!rc) cwal_value_unhand(*rv); - return rc; - } -} - -int s2_op_f_multdivmod( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - cwal_value * rhs; - cwal_value * lhs; - int mode; - assert( 2 == argc ); - rhs = s2__pop_val( se ); - lhs = s2__pop_val( se ); - switch( op->id ){ - case S2_T_OpMultiply: mode = 1; break; - case S2_T_OpDivide: mode = -1; break; - case S2_T_OpModulo: mode = 0; break; - default: - assert(!"Invalid op binding!"); - if(argc){/*avoid unused param warning*/} - return CWAL_RC_ASSERT; - } - cwal_value_ref(lhs); - cwal_value_ref(rhs); - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return 0; - }else{ - int rc = 0; - if(!s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc) - && !rc){ - rc = s2_values_multdivmod( se, mode, lhs, rhs, rv); - } - if(!rc) cwal_value_ref(*rv); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - if(!rc) cwal_value_unhand(*rv); - return rc; - } -} - -int s2_op_f_bitwiseshift( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - cwal_value * vlhs = 0, * vrhs = 0; - assert((S2_T_OpNegateBitwise==op->id) ? (1==argc) : (2==argc)); - vrhs = s2__pop_val(se); - assert(vrhs); - if(S2_T_OpNegateBitwise != op->id){ - vlhs = s2__pop_val(se); - assert(vlhs); - cwal_value_ref(vlhs); - if(argc){/*avoid unused param warning*/} - } - cwal_value_ref(vrhs); - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - assert(cwal_value_undefined()==vrhs); - assert(!vlhs || (cwal_value_undefined()==vlhs)); - /*cwal_refunref(vrhs); - if(vlhs && vlhs != rhs) cwal_refunref(vlhs);*/ - return 0; - }else{ - int rc = 0; - if(!s2_op_check_overload(op, se, 1, vlhs, vrhs, rv, &rc) - && !rc){ - rc = s2_values_bitwiseshift( se, op->id, vlhs, vrhs, rv ); - } - if(!rc) cwal_value_ref(*rv); - cwal_value_unref(vlhs); - cwal_value_unref(vrhs); - if(!rc) cwal_value_unhand(*rv); - return rc; - } -} - -int s2_op_f_not( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - cwal_value * rhs; - if(op || argc){/*avoid unused param warning*/} - assert(1==argc); - rhs = s2__pop_val(se); - cwal_value_ref(rhs); - *rv = (se->skipLevel>0) - ? cwal_value_undefined() - : cwal_new_bool( !cwal_value_get_bool(rhs) ); - assert(cwal_value_is_builtin(*rv)); - cwal_value_unref(rhs); - return 0; -} - -int s2_op_f_andor( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - cwal_value * rhs, * lhs; - assert(2==argc); - rhs = s2__pop_val(se); - lhs = s2__pop_val(se); - assert(rhs); - assert(lhs); - cwal_value_ref(rhs); - cwal_value_ref(lhs); - /** - In theory, if se->skipLevel>0 and if all operators comply and use - cwal_value_undefined() for all results in skip mode, then rhs is - guaranteed to be the Undefined value and lhs might (depending on - where skip-mode was invoked) be Undefined. We "might" want to - assert here that rhs===Undefined, to enforce the - must-be-undefined convention, but i've also half a mind to always - return (AND ? TRUE : FALSE) from this op on short-circuit mode, - the logic being that short-circuit mode "should" (i think) - consume as much as possible, and returning that bool from here - might help ensure that the downstream can do so. Something to - think about. - */ - switch((se->skipLevel>0) ? 0 : op->id){ - case 0: - /** - Reminder to self: if i'm not mistaken, the short-circuiting - ops (and/or/or3 and ternary if) are the only ones which can - have a non-undefined value as an operand in short-circuit - mode. i.e. we (theoretically) don't need to cwal_refunref() - in any skip-mode blocks in other operators. - */ - *rv = cwal_value_undefined(); - break; - case S2_T_OpAnd: - *rv = cwal_new_bool( cwal_value_get_bool(lhs) - && cwal_value_get_bool(rhs) ); - break; - case S2_T_OpOr: - *rv = cwal_new_bool( cwal_value_get_bool(lhs) - || cwal_value_get_bool(rhs) ); - break; - case S2_T_OpElvis: - *rv = cwal_value_undefined()==lhs ? rhs : lhs; - break; - case S2_T_OpOr3: - *rv = cwal_value_get_bool(lhs) ? lhs : rhs; - break; - default: - if(argc){/*avoid unused param warning*/} - assert(!"Invalid op mapping!"); - } - cwal_value_ref(*rv); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - cwal_value_unhand(*rv); - return 0; -} - -static int s2_op_f_cmp( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - cwal_value * rhs; - cwal_value * lhs; - int rc = 0; - assert(2==argc); - rhs = s2__pop_val(se); - lhs = s2__pop_val(se); - cwal_value_ref(rhs); - cwal_value_ref(lhs); - if(se->skipLevel>0){ - cwal_value_unref(rhs); - cwal_value_unref(lhs); - *rv = cwal_value_undefined(); - return 0; - } -#if 0 - MARKER(("COMPARE op %s\n", op->sym)); - s2_dump_val(lhs, "LHS"); - s2_dump_val(rhs, "RHS"); -#endif - switch(op->id){ - case S2_T_OpNotInherits: - rc = !cwal_value_derives_from( se->e, lhs, rhs ); - break; - case S2_T_OpInherits: - rc = cwal_value_derives_from( se->e, lhs, rhs ); - break; - case S2_T_CmpEqStrict: - case S2_T_CmpNotEqStrict:{ - if(rhs == lhs){ - rc = 0; - }else{ - int const tidL = cwal_value_type_id(lhs); - int const tidR = cwal_value_type_id(rhs); - if(tidL == tidR){ - rc = cwal_value_compare(lhs, rhs); - }else{ - rc = tidL - tidR; - } - } - rc = (S2_T_CmpNotEqStrict==op->id) - ? (0!=rc) - : (0==rc); - break; - } - default:{ -#if 1 - char const requiresOverload = 1; - /* strange: it wasn't until 20160121 that i realized that ({} == - {}) throws an exception. Maybe it shouldn't? If it doesn't, - though, we've also got to allow default impls of other - comparison ops. Is that wrong? */ -#else - char requiresOverload = 1; - switch(op->id){ - case S2_T_CmpEq: - case S2_T_CmpNotEq: - requiresOverload = 0; - break; - default: - requiresOverload = 1; - break; - } -#endif - if(s2_op_check_overload(op, se, requiresOverload, - lhs, rhs, rv, &rc) - || rc){ - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return rc; - } - rc = cwal_value_compare(lhs, rhs); - switch(op->id){ - case S2_T_CmpLT: rc = rc < 0; break; - case S2_T_CmpLE: rc = rc <= 0; break; - case S2_T_CmpGT: rc = rc > 0; break; - case S2_T_CmpGE: rc = rc >= 0; break; - case S2_T_CmpEq: rc = 0 == rc; break; - case S2_T_CmpNotEq: rc = 0 != rc; break; - default: - assert(!"CANNOT HAPPEN"); - if(argc){/*avoid unused param warning*/} - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return CWAL_RC_CANNOT_HAPPEN; - } - } - } - *rv = rc ? cwal_value_true() : cwal_value_false(); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - assert(cwal_value_is_builtin(*rv)); - return 0; -} - - -int s2_op_f_parens( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - - /* - TODO someday: re-add parens support into the stack machine, so - that they can be used independently of script code. Not likely to - happen until i want to use the stack machine that way, though. - */ - assert(!"')' is handled higher up!\n"); - if(S2_T_ParenOpen==op->id){ - MARKER(("We've hit the internal parens impl!\n")); - assert(!"'(' is handled higher up."); - assert(0==argc); - /* Tell s2_process_op() to keep the result - of the prior expression. - */ - if(se || argc){/*avoid unused param warning*/} - *rv = 0; - return 0; - } - return 0; -} - -int s2_op_f_comma( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - assert(2==argc); - assert(S2_T_Comma==op->id); - if(se->skipLevel){ - /* discard both args */ - cwal_value * v1, * v2; - v1 = s2__pop_val(se); - v2 = s2__pop_val(se); - *rv = cwal_value_undefined(); - assert(v1 && v2); - /* v1/v2 are likely, but not guaranteed, to - be cwal_value_undefined(), but in case - they're not... */ - if(v2!=v1) cwal_refunref(v2); - cwal_refunref(v1); - if(op || argc){/*avoid unused param warning*/} - }else{ - cwal_value * v; - *rv = s2__pop_val(se) /* RHS */; - v = s2__pop_val(se) /* discard LHS, but... */; - assert(v); - cwal_value_ref(*rv); - /* - Ref/unref cleans up any temps on the lhs (e.g. x++, y++). - This allows this for loop to avoid temporarily leaking - 998 integers into a higher scope: - - var x = 0; - for( var i = 0; x < 1000; x++, ++i ); - - Without this hack, the x++ result becomes a temporary in its - owning scope (x's) until the loop finishes and can clean up. - */ - cwal_refunref(v); - cwal_value_unhand(*rv); - } - return 0; -} - -int s2_op_f_dot( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * rhs = 0, * lhs, *lhsProto = 0; - char isUniqueDotValue = 0; - assert(S2_T_OpDot==op->id || S2_T_OpHash==op->id); - assert(2==argc); - rhs = s2__pop_val(se); - lhs = s2__pop_val(se); - cwal_value_ref(lhs); - cwal_value_ref(rhs); - /* s2_dump_val(rhs,"se->dotOp.key"); */ - /* s2_dump_val(lhs,"se->dotOp.lhs"); */ - if(!se->skipLevel && cwal_value_is_unique(lhs)){ - /* assume enum entry, allow only 'value' and 'prototype' - pseudo-properties */ - if(!s2_value_is_value_string(se, rhs) - && !s2_value_is_prototype_string(se, rhs)){ - rc = s2_engine_err_set(se, CWAL_RC_TYPE, - "Invalid key for '%s' operator on LHS of type '%s'.", - op->sym, - cwal_value_type_name(lhs) - /* ^^^ careful: lifetime! */); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - if(argc){/*avoid unused param warning*/} - return rc; - } - isUniqueDotValue = 1; - } - if(!se->skipLevel - && !isUniqueDotValue - && ((!(lhsProto=cwal_value_prototype_get(se->e, lhs)) - && !cwal_props_can(lhs)) -#if 1 - || (S2_T_OpHash==op->id && s2_value_is_enum(lhs)) - /* we disable the '#' on enums solely to force that - clients do not use it with them, as enums - might use Object storage instead of hashes. */ - /* As of 2020-02-21, enums are always hashes, so this - limitation is no longer necessary. */ - /* However... if we allow this op then it behaves, on enums, - exaclty as the -> op, except that the one requies an overload - lookup. Having 2 ops with identical results makes me queasy, - and i have client-side script code which makes use of ->, so - we'll keep that one.*/ -#endif - ) - ){ - rc = s2_engine_err_set(se, CWAL_RC_TYPE, - "Invalid LHS value (type %s) for " - "'%s' operator.", - cwal_value_type_name(lhs), - op->sym); - } - else if(se->skipLevel){ - if(S2_T_OpDot == op->id){ - s2_dotop_state( se, lhs, lhs, rhs ); - assert( se->dotOp.self == se->dotOp.lhs ); - assert( se->dotOp.self == lhs ); - assert( se->dotOp.key == rhs ); - }else{ - s2_dotop_state( se, 0, cwal_value_undefined(), - cwal_value_undefined() ); - assert(!se->dotOp.self); - assert(cwal_value_undefined() == se->dotOp.lhs); - assert(cwal_value_undefined() == se->dotOp.key); - } - *rv = cwal_value_undefined(); - rc = 0; - } - else{ /* X.Y and X#Y... */ - cwal_value * xrv = 0; - /* s2_dotop_state( se, 0, 0, 0 ); */ - /* s2_dump_val(lhs,"dot op lhs"); */ - if(S2_T_OpDot == op->id){ - const int noThis = - (cwal_value_is_integer(rhs) - && (cwal_value_is_string(lhs) - || cwal_value_is_tuple(lhs) - || cwal_value_array_part(se->e,lhs))) - /* Workaround to keep array/tuple/string[int]'s LHS from - becoming 'this' if it resolves to a function which gets - called. i.e. myArray[1](...) must not bind myArray as - 'this'. */ - ; - rc = s2_get_v(se, lhs, rhs, &xrv); - if(!rc){ - s2_dotop_state( se, noThis ? 0 : lhs, lhs, rhs ); - } - }else{ /* X#Y */ - cwal_hash * h; - assert(S2_T_OpHash==op->id); -#if 0 - if(!s2_op_check_overload(op, se, 0, lhs, rhs, &xrv, &rc) - && !rc){ -#endif - h = cwal_value_hash_part(se->e, lhs); - if(!h){ - rc = s2_engine_err_set(se, CWAL_RC_TYPE, - "Invalid (non-Hash) LHS for '%s' operator.", - op->sym); - }else{ - xrv = cwal_hash_search_v( h, rhs ); - } -#if 0 - } -#endif - if(!rc) s2_dotop_state(se, 0, lhs, rhs); - } - cwal_value_ref(xrv); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - if(rc){ - cwal_value_unref(xrv); - }else{ - *rv = xrv ? xrv : cwal_value_undefined(); - cwal_value_unhand(xrv); - } - } - return rc; -} - -int s2_op_f_dot_length( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * lhs; - assert(S2_T_OpDotLength==op->id); - assert(1==argc); - lhs = s2__pop_val(se); - cwal_value_ref(lhs); - if(se->skipLevel){ - /*s2_dotop_state( se, 0, cwal_value_undefined(), - cwal_value_undefined() );*/ - *rv = cwal_value_undefined(); - assert(cwal_value_undefined()==lhs); - cwal_value_unref(lhs); - rc = 0; - } - else{ - /* Translate X.# as X.length() if X if of type (array, tuple, - string, buffer), but without the cwal Function call() - overhead. */ - cwal_size_t n = 0; - cwal_value * xrv = 0; - switch(cwal_value_type_id(lhs)){ - case CWAL_TYPE_ARRAY: - n = cwal_array_length_get(cwal_value_get_array(lhs)); - break; - case CWAL_TYPE_TUPLE: - n = cwal_tuple_length(cwal_value_get_tuple(lhs)); - break; - case CWAL_TYPE_STRING: - n = cwal_string_length_utf8(cwal_value_get_string(lhs)); - break; - case CWAL_TYPE_BUFFER: - n = cwal_value_get_buffer(lhs)->used; - break; - case CWAL_TYPE_HASH: - n = cwal_hash_entry_count(cwal_value_get_hash(lhs)); - if(s2_value_is_enum(lhs)) n = n/2 /* do not count reverse mappings */; - break; - default: - if(cwal_props_can(lhs)){ - n = cwal_props_count(lhs); - }else{ - if(argc){/*avoid unused param warning*/} - rc = s2_engine_err_set(se, CWAL_RC_TYPE, - "Invalid LHS (type %s) for '%s' operator.", - cwal_value_type_name(lhs), - op->sym); - } - break; - } - if(!rc){ - xrv = cwal_new_integer(se->e, (cwal_int_t)n); - if(!xrv) rc = CWAL_RC_OOM; - } - cwal_value_ref(xrv); - cwal_value_unref(lhs); - if(rc){ - assert(!xrv); - cwal_value_unref(xrv)/*just for symmetry's sake*/; - }else{ - assert(xrv); - cwal_value_unhand(xrv); - *rv = xrv; - } - } - if(!rc){ - assert(*rv); - } - return rc; -} - -int s2_op_f_oload_arrow( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * rhs, * lhs, * xrv = 0; - assert(2==argc); - rhs = s2__pop_val(se); - lhs = s2__pop_val(se); - *rv = 0; - cwal_value_ref(lhs); - cwal_value_ref(rhs); - if(se->skipLevel){ - /*cwal_refunref(lhs); - cwal_refunref(rhs);*/ - cwal_value_unref(lhs); - cwal_value_unref(rhs); - *rv = cwal_value_undefined(); - }else{ - assert(s2_overload_name(op->id, lhs, 0)); - if(!s2_op_check_overload(op, se, 1, lhs, rhs, &xrv, &rc)){ - assert(rc); - }else{ - cwal_value_ref(xrv); - } -#if 0 - /*??? can of worms ??? */ - if(!rc && S2_T_OpColon2==op->id){ - /* 20160819: this is causing a cwal-level cleanup assertion downstream. */ - /* Has to be done afterwards b/c overload can overwrite these, - triggering an assertion in fcall() */ - s2_dotop_state( se, lhs, lhs, rhs ); - s2_dump_val(rhs,"se->dotOp.key"); - s2_dump_val(lhs,"se->dotOp.lhs"); - } -#endif - cwal_value_unref(lhs); - cwal_value_unref(rhs); - if(rc){ - assert(!xrv); - if(argc){/*avoid unused param warning*/} - }else{ - cwal_value_unhand(xrv); - *rv = xrv ? xrv : cwal_value_undefined(); - } - } - return rc; -} - -int s2_handle_set_result( s2_engine * se, s2_ptoker const * pr, int rc ){ - if(rc && (pr || se->currentScript)){ - switch(rc){ - case CWAL_RC_OOM: - case CWAL_RC_EXCEPTION: - break; - case CWAL_RC_NOT_FOUND: - rc = 0; - break; - default: - rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript); - assert(rc); - break; - } - } - return rc; -} - -int s2_handle_get_result( s2_engine * se, s2_ptoker const * pr, int rc ){ - if(rc && (pr || se->currentScript)){ - switch(rc){ - case CWAL_RC_OOM: - case CWAL_RC_EXCEPTION: - break; - case CWAL_RC_NOT_FOUND: - rc = 0; - break; - default: - rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript); - break; - } - } - return rc; -} - -/** - Maintenance reminder: s2_op_f_assign2() and s2_op_f_assign3() share - most of their logic, but having them separate improves their - readability considerably. It might be useful to separate parts of - the two implementations (e.g. the main operator dispatch switch) - into a shared function at some point. -*/ -int s2_op_f_assign3( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * rhs, * lhs, * self /* Assignment op: self[lhs]=rhs */; - s2_stoken * vTok; - int opReturnedLhs = -1 - /* optimization. If an operator returns the (X.Y) part of (X.Y OP - Z) then we can skip the assignment back to X.Y. A value of <0 - means we don't yet know. */; - cwal_flags16_t const fSetFlags = (S2_T_OpAssignConst3 == op->id) - ? CWAL_VAR_F_CONST : 0; - S2_UNUSED_ARG argc; - assert( 3==op->arity ); - rhs = s2__pop_val(se); - vTok = s2__pop_tok(se, 1); - lhs = vTok->value; - self = s2__pop_val(se); - assert(self); - cwal_value_ref(lhs); - cwal_value_ref(rhs); - cwal_value_ref(self); - s2_stoken_free(se, vTok, 1); - vTok = 0; - if(se->skipLevel>0){ - cwal_value_unref(lhs); - cwal_value_unref(rhs); - cwal_value_unref(self); - *rv = cwal_value_undefined(); - return 0; - } - if(op->id != S2_T_OpAssign3 && op->id != S2_T_OpAssignConst3 - /* combo assignment, i.e. not: X.Y=Z or X.Y:=Z*/){ - /* For combo ops (all but and X.Y=Z) we need to resolve the LHS - first because it's needed for calculating the result. For plain - assignments we don't need to do this, and we don't allow - overloading of those, so those can skip all this. - */ - /* - Reminder: this doesn't play well with the -> op. For that one, - we really want to use the overload to get the resolved value. - But at this point we have lost the info that there was a - -> op. The same applies to S2_T_OpHash. - */ - char gotOverload = 0; - cwal_value * lhsResolved = 0; - cwal_value * xrv = 0; - if( (rc = s2_get_v( se, self, lhs, &lhsResolved )) - /* Reminder to self: if we want full X->Y and X#Y support, we - need to adjust this to use those overloads. The problem is - that by the time this op is triggered, we no longer know - which dot-like op triggered the access, as se->dotOp.lhs and - friends _might_ (depending on stack conditions) have been - cleared by now. - */){ - assert(!lhsResolved); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - cwal_value_unref(self); - return rc; - }else if(!lhsResolved){ - /* unknown object property */ - lhsResolved = cwal_value_undefined(); - opReturnedLhs = 0 /* special case: we want to assign back over - this even if the op returns the undefined - value. */; - } - cwal_value_ref(lhsResolved); - if(!(gotOverload=s2_op_check_overload(op, se, 1, lhsResolved, rhs, &xrv, &rc)) - && !rc){ - assert(!xrv); - switch(op->id){ - case S2_T_OpPlusAssign3: - rc = s2_values_addsub( se, 1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpMinusAssign3: - rc = s2_values_addsub( se, 0, lhsResolved, rhs, &xrv); - break; - case S2_T_OpMultiplyAssign3: - rc = s2_values_multdivmod( se, 1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpDivideAssign3: - rc = s2_values_multdivmod( se, -1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpModuloAssign3: - rc = s2_values_multdivmod( se, 0, lhsResolved, rhs, &xrv); - break; - default: - rc = s2_values_bitwiseshift( se, op->id, lhsResolved, rhs, &xrv ); - break; - }/* the combo-assignment operators */ - if(!rc){ - assert(xrv); - if(opReturnedLhs<0){ - opReturnedLhs = xrv==lhsResolved ? 1 : 0; - } - cwal_value_ref(xrv); - cwal_value_unref(rhs); - rhs = xrv; - } - }else if(gotOverload){ - if(rc){ - assert(!xrv); - }else{ - assert(xrv); - if(opReturnedLhs<0){ - opReturnedLhs = xrv==lhsResolved ? 1 : 0; - } - cwal_value_ref(xrv); - cwal_value_unref(rhs); - rhs = xrv; - xrv = 0; - } - } - cwal_value_unref(lhsResolved); - lhsResolved = 0; - }/* combo assignments */ - if(!rc){ - assert(lhs); - assert(rhs); - /* Initial thought was that we do not want to assign back over - _overridden_ assignments. e.g. - - obj += 3 - - must not re-assign the result over obj, as doing so - results in non-intuitive results. - - Turns out that breaks: - - var a = "a"; - a += "b"; - - which we would expect to have the value 'ab' - - Reminder to self: - - holding/unhand'ing the lhs/self reference can lead to - values not being cleaned up when overwritten. - e.g.: - - for( var x = 0; x < 1000; ++x ){} - - If we ref/unhand lhs then each x which gets overwritten gets - unhand()ed here, and thus not cleaned up until sweep-up. - */ -#if 0 - /* an attempt at forcing "return this" semantics for - combo assignment ops. Together with op->derivedFromOp - handling, it seems to do what i want. - - Except that it breaks string+=string. :/ - - ACH - i think we need to force those semantics on the - _derived_ call, not this one. - */ - if(gotOverload>1/*synthesized assignment op overload*/ - && s2_ttype_is_assignment_combo(op->id)){ - cwal_value_ref(lhsResolved); - cwal_value_unref(rhs); - rhs = lhsResolved; - MARKER(("Forcing 'return this' semantics for overload...\n")); - s2_dump_val(lhsResolved,"this is this"); - } -#endif -#if 0 - if(opReturnedLhs>0){ - MARKER(("%s opReturnedLhs=%d, so skipping assignment.\n", op->sym, - opReturnedLhs)); - } -#endif - opReturnedLhs = 0 - /* - It turns out that this optimization, while it works, breaks - const expectations. Assume X.Y is an array property which has - been set const via C code and that array.operator+= behaves - like array.push() but returns, as required for operator+=, - 'this'). In that case, (X.Y += 3) would (with this - optimization) append to array X.Y while *appearing* to bypass - the constness. The constness is not "actually" violated, but - it would, to an observer who doesn't understand this - optimization, appear to. - - Whether or not "appearances" is a reason to disallow this - optimization is up for reconsideration. - */; - rc = opReturnedLhs>0 - ? 0 - : s2_handle_set_result(se, 0, s2_set_with_flags_v(se, self, lhs, - rhs, fSetFlags)) - /* Reminder: for full X#Y support this needs to be - different. */; - } - if(!rc){ - *rv = rhs; - cwal_value_unhand(rhs); - }else{ - cwal_value_unref(rhs); - } - cwal_value_unref(lhs); - cwal_value_unref(self); - return rc; -} - -/** - Maintenance reminder: - - s2_op_f_assign3() shares a good deal of logic with this routine, - but they were separated to improve readability. See that function - for other notes. -*/ -int s2_op_f_assign2( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * rhs, * lhs /* Asignment op: lhs=rhs */; - s2_stoken * vTok; - int ttype; - int opReturnedLhs = -1 - /* optimization. If an operator returns the X part of (X OP Y) - then we can skip the assignment back to X. A value of <0 means - we don't yet know. */; - char gotOverload = 0; - S2_UNUSED_ARG argc; - rhs = s2__pop_val(se); - vTok = s2__pop_tok(se, 1); - lhs = vTok->value; - assert( 2==op->arity ); - cwal_value_ref(rhs); - cwal_value_ref(lhs); - ttype = vTok->ttype; - s2_stoken_free(se, vTok, 1); - vTok = 0; - if(se->skipLevel>0){ - *rv = cwal_value_undefined(); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return 0; - } - if(S2_T_Identifier != ttype){ - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "Invalid LHS (%s) for '%s' op.", - s2_ttype_cstr(ttype), op->sym); - } - - if(op->id != S2_T_OpAssign /* a combo assignment, not plain X=Y */){ - /* For combo ops (all but X=Y) we need to resolve the LHS first - because it's needed for calculating the result. For plain - assignments we don't need to do this, and we don't allow - overloading of those, so those can skip all this. - */ - cwal_value * lhsResolved = 0; - cwal_value * xrv = 0; - if( (rc = s2_get_v( se, NULL, lhs, &lhsResolved )) ){ - /* Reminder to self: if we want full X->Y and X#Y support, we - need to adjust this to use those overloads. The problem is - that by the time this op is triggered, we no longer know - which dot-like op triggered the access, as se->dotOp.lhs and - friends _might_ (depending on stack conditions) have been - cleared by now. - */ - assert(!lhsResolved); - cwal_value_unref(lhs); - cwal_value_unref(rhs); - return rc; - }else if(!lhsResolved){ - cwal_size_t idLen = 0; - char const * sym = cwal_value_get_cstr(lhs, &idLen); - rc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - "'%s' operator cannot resolve " - "identifier '%.*s'", - op->sym, (int)idLen, sym ); - cwal_value_unref(rhs); - cwal_value_unref(lhs); - return rc; - } - cwal_value_ref(lhsResolved); - if(!(gotOverload=s2_op_check_overload(op, se, 1, lhsResolved, rhs, &xrv, &rc)) - && !rc){ - assert(!xrv); - switch(op->id){ - case S2_T_OpPlusAssign: - rc = s2_values_addsub( se, 1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpMinusAssign: - rc = s2_values_addsub( se, 0, lhsResolved, rhs, &xrv); - break; - case S2_T_OpMultiplyAssign: - rc = s2_values_multdivmod( se, 1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpDivideAssign: - rc = s2_values_multdivmod( se, -1, lhsResolved, rhs, &xrv); - break; - case S2_T_OpModuloAssign: - rc = s2_values_multdivmod( se, 0, lhsResolved, rhs, &xrv); - break; - default: - rc = s2_values_bitwiseshift( se, op->id, lhsResolved, rhs, &xrv ); - break; - }/* the combo-assignment operators */ - if(!rc){ - assert(xrv); - if(opReturnedLhs<0){ - opReturnedLhs = lhsResolved==xrv ? 1 : 0; - } - cwal_value_ref(xrv); - cwal_value_unref(rhs); - rhs = xrv; - } - }else if(gotOverload){ - if(rc){ - assert(!xrv); - }else{ - assert(xrv); - if(opReturnedLhs<0){ - opReturnedLhs = lhsResolved==xrv ? 1 : 0; - } - cwal_value_ref(xrv); - cwal_value_unref(rhs); - rhs = xrv; - xrv = 0; - } - } - cwal_value_unref(lhsResolved); - lhsResolved = 0; - }/* combo assignments */ - if(!rc){ - assert(lhs); - assert(rhs); - /* Initial thought was that we do not want to assign back over - _overridden_ assignments. e.g. - - obj += 3 - - must not re-assign the result over obj, as doing so - results in non-intuitive results. - - Turns out that breaks: - - var a = "a"; - a += "b"; - - which we would expect to have the value 'ab' - - Reminder to self: - - holding/unhand'ing the lhs reference can lead to - values not being cleaned up when overwritten. - e.g.: - - for( var x = 0; x < 1000; ++x ){} - - If we ref/unhand lhs then each x which gets overwritten gets - unhand()ed here, and thus not cleaned up until sweep-up. - */ -#if 0 - if(opReturnedLhs>0){ - MARKER(("%s opReturnedLhs==%d, so skipping assignment.\n", op->sym, - opReturnedLhs)); - } -#endif - opReturnedLhs = 0 - /* - Though this optimization works, we arguably can't use it - because it allows constructs which, on the surface, *appear* - to violate costness (for an observer who doesn't understand - this optimization): - - const a = 5; - // a += 1; // disallowed: const violation. - a += 0; // allowed by this optimization. - - That op returns 5, which is (or may be) a built-in constant, - thus triggering opReturnedLhs. That construct must, however, - for appearance's/consistency's sake, trigger a const violation - error. - - Similarly, assume that array.operator+= works like - array.push() but returns, as is required by operator+=, - 'this': - - const a = [1,2,3]; - a += 4; // allowed by this optimization - - That "should", at least for appearances sake, trigger a const - violation error. With this optimization, however, it would be - allowed. - - Whether or not "appearances" is a reason to disallow this - optimization is up for reconsideration. - */; - rc = opReturnedLhs>0 - ? 0 - : s2_handle_set_result(se, 0, s2_set_v(se, NULL, lhs, rhs)) - /* Reminder: for full X#Y support this needs to be - different. */ - ; - } - if(rc) cwal_value_unref(rhs); - else{ - cwal_value_unhand(rhs); - *rv = rhs; - } - cwal_value_unref(lhs); - return rc; -} - -int s2_op_f_incrdecr( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * self /* target of X.Y incr/decr*/, - * key /* the identifier or self[propertyKey] of X resp X.Y */; - int identType /* token type of the operator ID, for sanity - checking. */; - int direction = 0 /* -1 for prefix incr/decr, +1 for postfix */; - cwal_value * vResolved = 0 /* Pre-op resolved value of X.Y */ - , * vResult = 0 /* Post-op result */; - cwal_int_t addVal = 0 /* value to incr/decr by: -1 or +1 */; - char gotOverLoad = 0 - /* true if we found an overload. Only used for sanity checking. */; - char resolvedIsContainer = 0 - /* true if vResolved is a container type. - Used only for sanity checks. */; - s2_stoken * vTok = 0; - assert(1==op->arity); - vTok = s2__pop_tok(se, 1); - self = se->dotOp.lhs /* LHS of X.Y */; - key = self - ? se->dotOp.key /* RHS (property key) of X.Y */ - : vTok->value /* identifier X */; - identType = vTok->ttype; - cwal_value_ref(key); - cwal_value_ref(self); - if(key != vTok->value){ - /* vTok->value was (IIRC!) the result of an X.Y lookup. We don't - need that result, only the property key. */ - cwal_refunref(vTok->value); - vTok->value = 0; - } - s2_stoken_free(se, vTok, 1); - /* s2_dump_val(key, "key"); */ - /* s2_dump_val(self, "self"); */ - s2_dotop_state( se, 0, 0, 0 ); - if(se->skipLevel>0){ - cwal_value_unref(key); - cwal_value_unref(self); - *rv = cwal_value_undefined(); - return 0; - } - - switch(op->id){ - case S2_T_OpIncrPre: addVal = 1; direction = -1; break; - case S2_T_OpDecrPre: addVal = -1; direction = -1; break; - case S2_T_OpIncrPost: addVal = 1; direction = 1; break; - case S2_T_OpDecrPost: addVal = -1; direction = 1; break; - default: - if(argc){/*avoid unused param warning*/} - assert(!"invalid op mapping"); - s2_fatal(CWAL_RC_FATAL,"Invalid op mapping for incr/decr: %s", - op->sym) /* doesn't return */; - } - assert(direction==1 || direction==-1); - assert(addVal==1 || addVal==-1); - - if(!self && (S2_T_Identifier != identType)){ - return s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "Invalid %s (%s) for '%s' op.%s", - direction>0 ? "LHS" : "RHS", - s2_ttype_cstr(identType), - op->sym, - direction>0 ? "" : - " Expecting identifier or object " - "property access."); - } - else if(self && !cwal_props_can(self)){ - return s2_engine_err_set(se, CWAL_RC_TYPE, - "Invalid non-container type LHS (%s) " - "for '%s' op.", - cwal_value_type_name(self), - op->sym); - } - assert(self ? !!key : cwal_value_is_string(key) - /* Expecting the parser to pass the string value of the - identifier. */); - if( (rc = s2_get_v( se, self, key, &vResolved )) ) return rc; - else if(!vResolved){ - /* cannot_resolve: */ - if(!self){ - cwal_size_t idLen = 0; - char const * sym = cwal_value_get_cstr(key, &idLen); - assert(idLen && sym && "this _is_ an identifier, right?"); - return s2_engine_err_set(se, CWAL_RC_NOT_FOUND, - "'%s' operator cannot resolve " - "identifier '%.*s'", - op->sym, (int)idLen, sym ); - }else{ - /* Unknown _properties_ always resolve to undefined. */ - vResolved = cwal_value_undefined(); - } - } - cwal_value_ref(vResolved); - /* - Reminder: refs are most definitely needed in operator chaining, - e.g. (a = a++) to keep the values from being destroyed before the - pending LHS op(s) (yes, it happened via (a = ++a)). We do the key, - too, so that it's safe whether or not string interning is fooling - the reference count. Likewise, vResult could be an alias for any - other value which might otherwise be nuked by the assignment. - - Don't forget that operator overloading might change behaviours - here, causing the key/value/self references to do unpredictable - things. - */ - resolvedIsContainer = cwal_props_can(vResolved); - if(!resolvedIsContainer){ - /* Bypass overload checks for non-container types. */ - rc = s2_values_addsub( se, addVal>0, vResolved, - cwal_new_integer(se->e, 1) - /* does not allocate */, - &vResult); - }else{ - switch(op->id){ - case S2_T_OpIncrPre: - if((gotOverLoad = s2_op_check_overload(op, se, 1, 0, vResolved, - &vResult, &rc)) - || rc) break; - else rc = s2_values_addsub( se, 1, 0, vResolved, &vResult ); - break; - case S2_T_OpDecrPre: - if((gotOverLoad = s2_op_check_overload(op, se, 1, 0, vResolved, - &vResult, &rc)) - || rc) break; - else rc = s2_values_addsub( se, 0, 0, vResolved, &vResult ); - break; - case S2_T_OpIncrPost: - if((gotOverLoad = s2_op_check_overload(op, se, 1, vResolved, 0, - &vResult, &rc)) - || rc) break; - else rc = s2_values_addsub( se, 1, vResolved, 0, &vResult ); - break; - case S2_T_OpDecrPost: - if((gotOverLoad = s2_op_check_overload(op, se, 1, vResolved, 0, - &vResult, &rc)) - || rc) break; - else rc = s2_values_addsub( se, 0, vResolved, 0, &vResult ); - break; - } - } - while(!rc){ - assert(vResult); - cwal_value_ref(vResult); -#if 0 - s2_dump_val(self,"self"); - s2_dump_val(key,"key"); - s2_dump_val(vResolved,"vResolved"); - s2_dump_val(vResult,"vResult"); -#endif - if(!gotOverLoad && resolvedIsContainer){ - /* For sanity's sake. */ - assert(!"This should have been caught by s2_op_check_overload()."); - assert(!vResult); - rc = s2_engine_err_set(se, CWAL_RC_TYPE, - "Container type '%s' has no '%s' operator.", - cwal_value_type_name(vResolved), - op->sym); - break; - } - if(1){ - /* don't assign back over _overridden_ ++/-- because the results - are unintuitive and normally quite useless. - - Turns out that breaks other stuff, so we always assign back - over the original. - */ - rc = s2_handle_set_result(se, 0, s2_set_v(se, self, key, vResult)); - } - if(!rc){ - switch(op->id){ - case S2_T_OpIncrPre: - case S2_T_OpDecrPre: - /* Prefix result = the after-evaluation value. */ - *rv = vResult ? vResult : cwal_value_undefined(); - break; - case S2_T_OpIncrPost: - case S2_T_OpDecrPost: - /* Postfix result = the before-evaluation value. */ - *rv = vResolved ? vResolved : cwal_value_undefined(); - break; - } - } - break; - } - /* Let go of our references... */ - /* - This snippet helps free up the old value of ++X immediately, but - it's not yet clear if what we're doing here is really kosher - vis-a-vis lifetimes. Seems to be. - - The difference can be seen by starting s2sh with the -W flag - and running a simple loop: - - for(var i = 0; i < 100; ++i ); - - With this block enabled, that doesn't sweep/vacuum at all because - the unref() here cleans up quickly, whereas the unhand() delays - it until the next sweep (necessary for getting X++ results back - properly). - - We have a related sweeping problem, possibly not solvable - at this level: - - var a = []; - for(a.0 = 100; a.0 > 0; a.0-- ); - - that doesn't sweep any of the reset values until after the loop - because they're owned by the array's scope. Run that through - (s2sh -W). This problem does not show up with the prefix ops - because those unref() instead of unhand() in that case. We - resolve that in the for() loop handling using a simple ref/unref - trick which can nuke values living in older scopes. - - The for/while/do-while loops take care of that themselves (now - they do, anyway), but we'll leave this in here for the sake of - for-each callback functions which behave similarly, modifying - lower-scope values which we can potentially clean up. - - Nonetheless, we still have a problem in loop bodies: - - var a = [100]; - do a.0--; while(a.0) - - Does not free up a.0's previous values until a's owning scope is - swept because (a.0--) returns a newly-made-temporary value (the - old one) which is owned by a's scope. After the loop: - - MARKER: s2.c:264:s2_engine_sweep(): Swept up 99 value(s) in sweep mode - - Conversely: - - s2> a.0 = 100; do ; while(a.0--) - - Extra handling in for/do/while() can get rid of the temp created - in the condition, cleaning up those temps as it goes, but only if - they are on they are the result in the condition part. If they - are on the LHS of the condition part, they are still orphaned in - the parent scope until it returns. - - If we handled refcounts at the stack machine (or eval_impl() - loop) level this might not be so much of a problem. - */ -#if 0 - s2_dump_val(self,"self"); - s2_dump_val(key,"key"); - s2_dump_val(vResolved,"vResolved"); - s2_dump_val(vResult,"vResult"); -#endif - if(!rc){ - cwal_value_ref(*rv); - assert(*rv == vResolved || *rv == vResult); - } - cwal_value_unref(vResolved); - cwal_value_unref(vResult); - cwal_value_unref(key); - cwal_value_unref(self); - if(!rc) cwal_value_unhand(*rv); - /* - Reminder to self: our extra refs/unhand here cause for-loops - to require extra sweepups: - - for( var x = 0; x < 2000; ++x); - - each assignment back over x doesn't get cleaned up immediately - because of our unhand(). One can see this in s2sh s2.dumpMetrics() - and the above loop. Compare the metrics of multiple runs against - multiple runs of: - - for( var x = 0; x < 2000; x=x+1); - - which currently does not exhibit this behaviour because it doesn't - have the same ref requirements. - - The for() loop, comma operator, and a few other places account for - that by doing extra cleanup. - */ - return rc; -} - -int s2_op_f_array_append( s2_op const * op, s2_engine * se, - int argc, cwal_value **rv ){ - int rc = 0; - cwal_value * rhs, * lhs; - cwal_array * ar; - s2_stoken * vTok; - assert(2==argc); - rhs = s2__pop_val(se); - vTok = s2__pop_tok(se, 1); - lhs = vTok->value; - cwal_value_ref(rhs); - cwal_value_ref(lhs); - if(se->skipLevel>0){ - cwal_value_unref(rhs); - cwal_value_unref(lhs); - *rv = cwal_value_undefined(); - }else if(!(ar = cwal_value_array_part(se->e, lhs))){ - /* s2_dump_val(rhs,"rhs"); */ - /* s2_dump_val(lhs,"lhs"); */ - rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX, - "Invalid (non-array) LHS (token type '%s' " - "with value type '%s') for '%s' op.", - s2_ttype_cstr(vTok->ttype), - cwal_value_type_name(lhs), - op->sym); - if(argc){/*avoid unused param warning*/} - }else{ - assert(lhs); - assert(ar); - /* s2_dump_val(rhs,"rhs"); */ - /* s2_dump_val(lhs,"lhs"); */ - rc = cwal_array_append(ar, rhs); - if(!rc){ - *rv = rhs; - cwal_value_ref(*rv); - } - cwal_value_unref(rhs); - cwal_value_unref(lhs); - if(!rc) cwal_value_unhand(*rv); - } - s2_stoken_free(se, vTok, 1); - return rc; -} - - -int s2_op_f_foo( s2_op const * op, s2_engine * se, int argc, - cwal_value **rv ){ - MARKER(("%s operator: argc=%d%s\n", op->sym, - argc, argc ? " Args stack:" : "" )); - for( ; argc>0; --argc){ - cwal_value * v = s2__pop_val(se); - assert(v); - MARKER(("Arg type: %s\n", cwal_value_type_name(v))); - } - *rv = cwal_value_true(); - return 0; -} - -int s2_values_addsub( s2_engine * se, char doAdd, cwal_value * lhs, - cwal_value * rhs, cwal_value **rv ){ - if(!lhs){ - /* Unary prefix op */ - if(cwal_value_is_string(rhs)/*unfortunate special case*/){ - cwal_size_t slen = 0; - char const * cstr = cwal_value_get_cstr(rhs, &slen); - if(!slen) *rv = cwal_new_integer(se->e, 0); - else{ - cwal_int_t inty = 0; - if(s2_cstr_parse_int(cstr, (cwal_int_t)slen, &inty)){ - *rv = cwal_new_integer(se->e, doAdd ? inty : -inty); - }else{ - cwal_double_t dbl = 0.0; - if(s2_cstr_parse_double(cstr, (cwal_int_t)slen, &dbl)){ - *rv = cwal_new_double(se->e, doAdd ? dbl : -dbl); - }else{ - *rv = cwal_new_integer(se->e, 0); - } - } - } - }else if(!cwal_value_is_double(rhs)){ - /* Special case for integer math on integers >48 bits. - This is not a complete solution. - */ - cwal_int_t const iR = cwal_value_get_integer(rhs); - *rv = iR - ? (doAdd - ? (cwal_value_is_integer(rhs) - ? rhs /* already an int, make this a no-op. */ - : cwal_new_integer(se->e, iR) /* coerce to an int */ - ) - : cwal_new_integer(se->e, -iR)) - : cwal_new_integer(se->e, 0) - ; - }else{ - const cwal_double_t iR = cwal_value_get_double( rhs ); - *rv = doAdd - ? (cwal_value_is_double(rhs) - ? rhs - : cwal_new_double(se->e, iR)) - : cwal_new_double(se->e, -iR); - } - }else{ - /* Binary op */ - /* - Using double for all math here breaks for large integers (>48(?) - bits). We need to first determine whether the LHS is an int, and - use the int type for that, as that can potentially hold larger - numbers. - */ - /* Reminder: ("1"+n) is caught via string op overload. */ - if(!cwal_value_is_double(lhs)){ - cwal_uint_t const iL = (cwal_uint_t)cwal_value_get_integer(lhs); - cwal_uint_t const iR = (cwal_uint_t)cwal_value_get_integer(rhs); - *rv = cwal_new_integer(se->e, doAdd ? (cwal_int_t)(iL+iR) : (cwal_int_t)(iL-iR)); - }else{ - cwal_double_t const iL = cwal_value_get_double( lhs ); - /* FIXME: check if RHS is an integer, and do no to-double - conversion if it is. Needed for sane(?) behaviour of - integers larger than the integer part of a double - */ - cwal_double_t const iR = cwal_value_get_double( rhs ); - cwal_double_t const res = doAdd ? (iL+iR) : (iL-iR); - *rv = cwal_new_double(se->e, res); - } - } - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - proxy for s2_op_f_assign2/3() for *, /, % operators. - - mode: -1 = division, 0 = modulo, 1 = multiplication -*/ -int s2_values_multdivmod( s2_engine * se, int mode, - cwal_value * lhs, cwal_value * rhs, - cwal_value **rv ){ - switch(mode){ - case -1: { - if(!cwal_value_is_double(lhs)){ - /* Integer math */ - cwal_int_t const iL = cwal_value_get_integer(lhs); - /* We need to special-case the RHS in case it is an integer with more - than 48 bits. */ - if(!cwal_value_is_double(rhs)){ - cwal_int_t const iR = cwal_value_get_integer( rhs ); - if(0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO, - "Divide by 0."); - *rv = cwal_new_integer(se->e, iL / iR); - }else{ - cwal_double_t const iR = cwal_value_get_double( rhs ); - if(0.0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO, - "Divide by 0.0."); - *rv = cwal_new_integer(se->e, (cwal_int_t)(iL / iR)); - } - }else{ - cwal_double_t const iL = cwal_value_get_double( lhs ); - if(!cwal_value_is_double(rhs)){ - cwal_int_t const iR = cwal_value_get_integer( rhs ); - if(0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO, - "Divide by 0."); - *rv = cwal_new_double(se->e, iL / iR); - }else{ - cwal_double_t const iR = cwal_value_get_double( rhs ); - if(0.0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO, - "Divide by 0.0."); - *rv = cwal_new_double(se->e, iL / iR); - } - } - break; - } - case 0: { - /* Modulo */ - cwal_int_t const iR = cwal_value_get_integer( rhs ); - if(0==iR){ - return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO, - "Modulo by 0."); - }else{ - cwal_int_t const iL = cwal_value_get_integer( lhs ); - /* Optimization: if 1==lhs && 1!=rhs, return lhs. If the rhs is - 1 it is already optimized b/c cwal doesn't allocate for - values (-1,0,1). - */ - *rv = ((1==iL) && (iR!=1)) - ? (cwal_value_is_integer(lhs) - ? lhs - : cwal_new_integer(se->e, iL)) - : cwal_new_integer(se->e, (cwal_int_t)(iL % iR)); - } - break; - } - case 1: - /* Multiplication */ - if(!cwal_value_is_double(lhs)){ - /* Integer math */ - cwal_uint_t const iL = (cwal_uint_t)cwal_value_get_integer(lhs); - if(!cwal_value_is_double(rhs)){ - cwal_uint_t const iR = (cwal_uint_t)cwal_value_get_integer( rhs ); - *rv = cwal_new_integer(se->e, (cwal_int_t)(iL * iR)); - }else{ - cwal_double_t const iR = cwal_value_get_double( rhs ); - *rv = cwal_new_integer(se->e, (cwal_int_t)(iL * iR)); - } - }else{ - cwal_double_t const iL = cwal_value_get_double( lhs ); - if(!cwal_value_is_double(rhs)){ - cwal_int_t const iR = cwal_value_get_integer( rhs ); - *rv = cwal_new_double(se->e, iL * iR); - }else{ - cwal_double_t const iR = cwal_value_get_double( rhs ); - *rv = cwal_new_double(se->e, iL * iR); - } - } - break; - default:{ - const char * msg = - "Internal misuse: s2_values_multdivmod() expecting mode of -1, 0, or 1"; - assert(!msg); - s2_fatal(CWAL_RC_ASSERT, msg) /* does not return */; - } - } - return *rv ? 0 : CWAL_RC_OOM; -} - - -int s2_values_bitwiseshift( s2_engine * se, int op, cwal_value * vlhs, - cwal_value * vrhs, cwal_value ** rv ){ - cwal_int_t lhs = 0, rhs = 0, res = 0; - lhs = vlhs ? cwal_value_get_integer(vlhs) : 0; - rhs = cwal_value_get_integer(vrhs); - switch( op ){ - case S2_T_OpNegateBitwise: - assert(!vlhs); - res = ~rhs; - break; - case S2_T_OpShiftLeft: - case S2_T_OpShiftLeftAssign: - case S2_T_OpShiftLeftAssign3: - res = (cwal_int_t)((cwal_uint_t)lhs << (cwal_uint_t)rhs); break; - case S2_T_OpShiftRight: - case S2_T_OpShiftRightAssign: - case S2_T_OpShiftRightAssign3: - res = (cwal_int_t)((cwal_uint_t)lhs >> (cwal_uint_t)rhs); break; - case S2_T_OpOrBitwise: - case S2_T_OpOrAssign: - case S2_T_OpOrAssign3: - res = lhs | rhs; break; - case S2_T_OpAndBitwise: - case S2_T_OpAndAssign: - case S2_T_OpAndAssign3: - res = lhs & rhs; break; - case S2_T_OpXOr: - case S2_T_OpXOrAssign: - case S2_T_OpXOrAssign3: - res = lhs ^ rhs; break; - default: - return s2_engine_err_set(se, CWAL_RC_RANGE, - "Invalid operator for " - "s2_values_bitwiseshift(): %s", - s2_ttype_cstr(op)); - } - /* - Optimizations: re-use the LHS or RHS if the result equals one of - them... - */ - if(res==rhs && cwal_value_is_integer(vrhs)) *rv = vrhs; - else if(vlhs && (res==lhs) && cwal_value_is_integer(vlhs)) *rv = vlhs; - else *rv = cwal_new_integer(se->e, res); - return *rv ? 0 : CWAL_RC_OOM; -} - -#undef MARKER -#undef s2__pop_val -#undef s2__pop_tok -/* end of file ops.c */ -/* start of file pf.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#ifdef S2_OS_UNIX -/* for stat(2) */ -#include -#include -#include -#endif - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -struct s2_pf { - s2_engine * se; - cwal_value * self; - cwal_buffer buf; - /* char (*predicate)( char const * fn ); */ -}; -static const s2_pf s2_pf_empty = {0,0,cwal_buffer_empty_m}; - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \ - assert(se) - -/** - The "prefix" and "suffix" PathFinder properties are arrays of - strings holding the search paths (prefixes) and extensions - (suffixes). The names were, in hindsight, an unfortunate choice: - "path" and "extensions" (or "ext") would have been more intuitive. - Lots of my scripts use these names, though :/. -*/ -#define PF_PREFIX "prefix" -#define PF_PREFIX_N ((cwal_size_t)sizeof(PF_PREFIX)-1) -#define PF_SUFFIX "suffix" -#define PF_SUFFIX_N ((cwal_size_t)sizeof(PF_SUFFIX)-1) - -static void s2_pf_finalizer(cwal_engine * e, void * m){ - s2_pf * pf = (s2_pf*)m; - cwal_buffer_reserve(e, &pf->buf, 0); - cwal_free2( e, m, sizeof(s2_pf) ); -} - -s2_pf * s2_pf_new(s2_engine * se){ - s2_pf * pf; - cwal_value * vSelf; - if(!se || !se->e) return NULL; - pf = (s2_pf*)cwal_malloc(se->e, sizeof(s2_pf)); - if(!pf) return NULL; - *pf = s2_pf_empty; - vSelf = cwal_new_native_value(se->e, pf, s2_pf_finalizer, - &s2_pf_empty); - if(!vSelf){ - s2_pf_finalizer(se->e, pf); - pf = NULL; - }else{ - pf->self = vSelf; - pf->se = se; - assert(cwal_props_can(vSelf)); -#if 0 - s2_pf_dirs(pf); - s2_pf_exts(pf); - assert(cwal_prop_get(vSelf,PF_SUFFIX,PF_SUFFIX_N)); - assert(cwal_prop_get(vSelf,PF_PREFIX,PF_PREFIX_N)); -#endif - cwal_value_prototype_set( vSelf, s2_prototype_pf(se) ); - } - return pf; -} - -int s2_install_pf( s2_engine * se, cwal_value * ns ){ - return cwal_props_can(ns) - ? cwal_prop_set(ns, "PathFinder", 10, s2_prototype_pf(se)) - : CWAL_RC_MISUSE; -} - -static cwal_array * s2_pf_member_array(s2_pf *pf, char const * pKey, cwal_size_t pKeyLen ){ - cwal_value * ar = cwal_prop_get(pf->self,pKey, pKeyLen); - if(!ar || !cwal_value_is_array(ar)){ - ar = cwal_new_array_value(pf->se->e); - if(ar){ - int rc = 0; - cwal_value_ref(ar); - rc = cwal_prop_set(pf->self, pKey, pKeyLen, ar); - cwal_value_unref(ar); - if(rc!=0) ar = NULL; - } - } - return cwal_value_get_array(ar); -} - -cwal_array * s2_pf_exts(s2_pf *pf){ - return s2_pf_member_array(pf, PF_SUFFIX, PF_SUFFIX_N); -} - -cwal_array * s2_pf_dirs(s2_pf *pf){ - return s2_pf_member_array(pf, PF_PREFIX, PF_PREFIX_N); -} - -int s2_pf_dirs_set( s2_pf * pf, cwal_array * ar ){ - if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE; - else{ - return cwal_prop_set(pf->self, PF_PREFIX, PF_PREFIX_N, - cwal_array_value(ar)); - } -} - -int s2_pf_exts_set( s2_pf * pf, cwal_array * ar ){ - if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE; - else{ - return cwal_prop_set(pf->self, PF_SUFFIX, PF_SUFFIX_N, - cwal_array_value(ar)); - } -} - -int s2_pf_dir_add_v( s2_pf * pf, cwal_value * v ){ - if(!pf || !pf->self || !v) return CWAL_RC_MISUSE; - else{ - cwal_array * ar = s2_pf_dirs(pf); - return ar - ? cwal_array_append(ar, v) - : CWAL_RC_OOM; - } -} - -int s2_pf_dir_add( s2_pf * pf, char const * dir, cwal_size_t dirLen){ - if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE; - else{ - int rc; - cwal_value * v = cwal_new_string_value(pf->se->e, dir, dirLen); - if(!v) rc = CWAL_RC_OOM; - else { - cwal_value_ref(v); - rc = s2_pf_dir_add_v( pf, v); - cwal_value_unref(v); - } - return rc; - } -} - -int s2_pf_ext_add_v( s2_pf * pf, cwal_value * v ){ - if(!pf || !pf->self || !v) return CWAL_RC_MISUSE; - else{ - cwal_array * ar = s2_pf_exts(pf); - return ar - ? cwal_array_append(ar, v) - : CWAL_RC_OOM; - } -} - -int s2_pf_ext_add( s2_pf * pf, char const * dir, cwal_size_t dirLen){ - if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE; - else{ - int rc; - cwal_value * v = cwal_new_string_value(pf->se->e, dir, dirLen); - if(!v) rc = CWAL_RC_OOM; - else { - cwal_value_ref(v); - rc = s2_pf_ext_add_v( pf, v); - cwal_value_unref(v); - } - return rc; - } -} - -s2_pf * s2_value_pf_part(cwal_value const *v){ - cwal_native * n; - s2_pf * pf = NULL; - while(v){ - n = cwal_value_get_native(v); - if(n){ - pf = (s2_pf *)cwal_native_get(n, &s2_pf_empty); - if(pf) break; - } - v = cwal_value_prototype_get(NULL,v); - } - return pf; -} - -s2_pf * s2_value_pf(cwal_value const * v){ - cwal_native const * n = v ? cwal_value_get_native(v) : 0; - return n - ? (s2_pf *)cwal_native_get(n, &s2_pf_empty) - : 0; -} - -cwal_value * s2_pf_value(s2_pf const * pf){ - return pf->self; -} - -#define THIS_PF \ - s2_pf * pf = s2_value_pf_part(args->self); \ - if(!pf) return \ - cwal_exception_setf(args->engine, CWAL_RC_TYPE, \ - "'this' is not a PathFinder instance.") - -static int s2_cb_pf_add( cwal_callback_args const * args, cwal_value **rv, - cwal_array * dest ){ - THIS_PF; - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting string arguments."); - } - else if(!dest) return CWAL_RC_OOM; - else { - cwal_size_t i = 0; - int rc = 0; - for( ; !rc && (i < args->argc); ++i ){ - rc = cwal_array_append( dest, args->argv[i]); - } - if(!rc) *rv = args->self; - return rc; - } -} - - -static int s2_cb_pf_dir_add( cwal_callback_args const * args, cwal_value **rv ){ - THIS_PF; - return s2_cb_pf_add( args, rv, s2_pf_dirs(pf) ); -} - -static int s2_cb_pf_ext_add( cwal_callback_args const * args, cwal_value **rv ){ - THIS_PF; - return s2_cb_pf_add( args, rv, s2_pf_exts(pf) ); -} - - -static void s2_pf_separator( s2_pf * pf, char const ** sep, cwal_size_t * sepLen ){ - cwal_value const * vs = cwal_prop_get(pf->self,"separator",9); - char const * rc = vs ? cwal_value_get_cstr(vs, sepLen) : NULL; - assert(sep && sepLen); - if(!rc){ - if(vs){ /* Non-string separator value. FIXME: we _could_ use a - non-string with just a little more work but there's - currently no use case for it. If PF is abstracted - to do other types of searches (where the user - supplies a predicate function we call for each path - combination we try) then it will make sense to - allow non-string separators. For now we're only - dealing with file paths, which means strings. - */ - *sep = ""; - *sepLen = 0; - } - else{ - rc = S2_DIRECTORY_SEPARATOR; - *sepLen = sizeof(S2_DIRECTORY_SEPARATOR)- - sizeof(S2_DIRECTORY_SEPARATOR[0]); - } - } - *sep = rc; -} - - -char const * s2_pf_search( s2_pf * pf, char const * base, - cwal_size_t baseLen, cwal_size_t * rcLen, - int directoryPolicy){ - char const * pathSep = NULL; - cwal_size_t sepLen; - cwal_buffer * buf; - cwal_engine * e; - cwal_size_t d, x, nD, nX, resetLen = 0; - cwal_array * ad; - cwal_array * ax; - int rc = 0; - if(!pf || !base) return NULL; - else if(!*base || !baseLen) return NULL; - buf = &pf->buf; - buf->used = 0; - e = pf->se->e; - -#define CHECK_FILE(NAME) (s2_file_is_accessible(NAME, 0) && \ - ((directoryPolicy < 0 \ - ? s2_is_dir(NAME,0) \ - : (directoryPolicy > 0 \ - ? 1 \ - : !s2_is_dir(NAME,0))))) - if(CHECK_FILE(base)){ - rc = cwal_buffer_append(e, buf, base, baseLen); - if(rc) return NULL; - goto gotone; - } - - s2_pf_separator(pf, &pathSep, &sepLen); - assert(pathSep); - assert(sepLen); - ad = s2_pf_dirs(pf); - ax = s2_pf_exts(pf); - nD = cwal_array_length_get(ad); - nX = cwal_array_length_get(ax); - for( d = 0; !rc && (nD ? d < nD : 1); ){ - cwal_value * vD = nD - ? cwal_array_get(ad, d) - : 0; - buf->used = 0; - if(nD && vD){ - cwal_size_t const used = buf->used; - rc = s2_value_to_buffer(e, buf, vD); - if(rc) break; - else if(used != buf->used){ - /* Only append separator if vD is non-empty. */ - rc = cwal_buffer_append(e, buf, pathSep, sepLen); - if(rc) break; - } - } - rc = cwal_buffer_append(e, buf, base, baseLen); - if(rc) break; - if(CHECK_FILE( (char const *)buf->mem )){ - goto gotone; - } - resetLen = buf->used; - for( x = 0; !rc && (x < nX); ++x ){ - cwal_value * vX = cwal_array_get(ax, x); - if(vX){ - buf->used = resetLen; - rc = s2_value_to_buffer(e, buf, vX); - if(rc) break; - } - assert(buf->used < buf->capacity); - buf->mem[buf->used] = 0; - if(CHECK_FILE((char const *)buf->mem)){ - goto gotone; - } - } - if(++d >= nD) break; - } -#undef CHECK_FILE - return NULL; - gotone: - if(rcLen) *rcLen = buf->used; - return (char const *)buf->mem; -} - -/** - Script signature: - - string|undefined search(string baseName [, bool|int dirPolicy=0]) - - dirPolicy: bool true or integer>0 mean match files and dirs, false or - integer 0 mean only files, integer<0 means only match dirs. If this value - is of the Unique type, its wrapped value is used in its place. - - Returns the first matching file/dir entry, or the undefined value - if no entry is found. -*/ -static int s2_cb_pf_search( cwal_callback_args const * args, cwal_value **rv ){ - cwal_size_t baseLen = 0; - char const * base; - char const * rc; - cwal_size_t rcLen = 0; - int directoryPolicy = 0; - THIS_PF; - { - int const rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT); - if( rc ) return rc; - } - - if(!args->argc) goto misuse; - base = cwal_value_get_cstr(args->argv[0], &baseLen); - if(!base || !baseLen) goto misuse; - else if(args->argc>1){ - /* dirPolicy: bool|integer directoryPolicy */ - cwal_value const * a1 = s2_value_unwrap(args->argv[1]); - if(cwal_value_is_bool(a1)){ - directoryPolicy = cwal_value_get_bool(a1) - ? S2_PF_SEARCH_FILES_DIRS - : S2_PF_SEARCH_FILES; - }else{ - cwal_int_t const n = cwal_value_get_integer(a1); - directoryPolicy = n==0 - ? S2_PF_SEARCH_FILES - : (n<0 - ? S2_PF_SEARCH_DIRS - : S2_PF_SEARCH_FILES_DIRS); - } - } - rc = s2_pf_search( pf, base, baseLen, &rcLen, directoryPolicy ); - if(!rc) *rv = cwal_value_undefined(); - else { - *rv = cwal_new_string_value(args->engine, rc, rcLen); - } - return *rv ? 0 : CWAL_RC_OOM; - misuse: - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a non-empty string argument."); -} - -int s2_cb_pf_new( cwal_callback_args const * args, cwal_value **rv ){ - /* Constructor... */ - int rc = 0; - s2_pf * pf; - ARGS_SE; - pf = s2_pf_new(se); - if(!pf) return CWAL_RC_OOM; - else if(args->argc){ - /* Set or reserve the PF_PREFIX/PF_SUFFIX properties... */ - uint16_t i; - typedef int (*setter_f)(s2_pf *, cwal_array *); - typedef cwal_array * (*getter_f)(s2_pf *); - for(i = 0; iargc && i<2; ++i){ - cwal_value * const arg = args->argv[i]; - setter_f setter = i ? s2_pf_exts_set : s2_pf_dirs_set; - getter_f getter = i ? s2_pf_exts : s2_pf_dirs; - cwal_array * ar; - if(cwal_value_undefined()==arg || cwal_value_null()==arg){ - /* Special case: skip these without an error to simplify - certain usage patterns, e.g.: new - s2.PathFinder(s2.getenv("blah")). */ - continue; - } - ar = cwal_value_array_part(args->engine, arg); - if(ar){ - /* Array argument: use it as-is */ - rc = setter(pf, ar); - }else{ - if(cwal_value_is_string(arg)){ - /* Parse string as a PATH. */ - cwal_size_t plen; - char const * pstr = cwal_value_get_cstr(arg, &plen); - cwal_array * tgt = getter(pf); - if(!tgt){ - rc = CWAL_RC_OOM; - }else{ - assert(pstr); - rc = s2_tokenize_path_to_array(args->engine, &tgt, - pstr, (cwal_int_t)plen) - /* The only plausible error case here is an OOM, so we - don't translate the result to an exception. */; - } - }else{ - /* Lightly slap the user's fingers. */ - rc = s2_cb_throw(args, CWAL_RC_TYPE, - "Expecting 0, 1, or 2 string or array arguments."); - } - } - if(rc) break; - } - } - if(!rc) *rv = pf->self; - else cwal_value_unref(pf->self) /* takes pf with it */; - return rc; -} - -cwal_value * s2_prototype_pf( s2_engine * se ){ - int rc = 0; - cwal_value * v; - cwal_value * proto; - char const * pKey = "class.PathFinder"; - assert(se && se->e); - proto = s2_prototype_stashed(se, pKey); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash( se, pKey, proto ); - if(rc) goto end; - -#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - CHECKV; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - v = 0; \ - if(rc) goto end - - v = cwal_new_string_value(se->e, "PathFinder", 10); - CHECKV; - cwal_value_ref(v); - rc = cwal_prop_set_with_flags_v( proto, se->cache.keyTypename, - v, CWAL_VAR_F_HIDDEN ); - cwal_value_unref(v); - v = 0; - if(rc) goto end; - - v = cwal_new_xstring_value(se->e, -#ifdef _WIN32 - "\\", -#else - "/", -#endif - 1); - SET("separator"); - - { - s2_func_def const funcs[] = { - S2_FUNC2("addDir", s2_cb_pf_dir_add), - S2_FUNC2("addExt", s2_cb_pf_ext_add), - S2_FUNC2("search", s2_cb_pf_search), - S2_FUNC2("fileIsAccessible", s2_cb_file_accessible), - S2_FUNC2("new", s2_cb_pf_new), - S2_FUNC2("tokenizePath", s2_cb_tokenize_path), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(rc) goto end; - else { - cwal_value * fv = 0; - s2_get(se, proto, "new", 3, &fv); - assert(fv && "we JUST put this in there!"); - rc = s2_ctor_method_set( se, proto, - cwal_value_get_function(fv) ); - } - } - -#undef SET -#undef CHECKV - end: - return rc - ? NULL /* remember: proto is stashed at this point, so no leak. */ - : proto; -} - -#undef ARGS_SE -#undef THIS_PF -#undef MARKER - -#undef PF_PREFIX -#undef PF_PREFIX_N -#undef PF_SUFFIX -#undef PF_SUFFIX_N -/* end of file pf.c */ -/* start of file protos.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -cwal_value * s2_prototype_stashed( s2_engine * se, char const * typeName ){ - enum { BufLen = 128 }; - char buf[BufLen]; - S2_UNUSED_VAR cwal_size_t const slen = cwal_strlen(typeName); - assert(slen && (slen < BufLen - 11)); - sprintf(buf, "prototype.%s", typeName); - return s2_stash_get( se, buf ); -} - -int s2_prototype_stash( s2_engine * se, - char const * typeName, - cwal_value * proto ){ - enum { BufLen = 128 }; - char buf[BufLen]; - S2_UNUSED_VAR cwal_size_t const slen = cwal_strlen(typeName); - assert(slen && (slen < BufLen - 11)); - sprintf(buf, "prototype.%s", typeName); - return s2_stash_set( se, buf, proto ); -} - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \ - assert(se) - -static int s2_cb_object_ctor( cwal_callback_args const * args, cwal_value **rv ){ - if(args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting no arguments."); - }else{ - *rv = cwal_new_object_value( args->engine ); - return *rv ? 0 : CWAL_RC_OOM; - } -} - - -static int s2_cb_prop_has_own( cwal_callback_args const * args, - cwal_value **rv ){ - if(!args->argc) - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a property key."); - else { - *rv = cwal_new_bool(cwal_prop_has_v(args->self, - args->argv[0], - 0)); - return 0; - } -} - - -static int s2_cb_prop_clear( cwal_callback_args const * args, cwal_value **rv ){ - int rc = s2_immutable_container_check_cb(args, args->self); - if( !rc && (rc = cwal_props_clear( args->self )) ){ - rc = s2_cb_throw(args, rc, "Clearing properties failed " - "with code %s.", cwal_rc_cstr(rc)); - } - if(!rc) *rv = args->self; - return rc; -} - -int s2_value_visit_append_to_array( cwal_value * v, void * state ){ - return cwal_array_append((cwal_array *)state, v); -} - -/** - Script usage: var keys = obj.propertyKeys() - - Returns an array containing all keys for all key/value pairs for - obj. - -*/ -static int s2_cb_prop_keys( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_array * ar; - if(!cwal_props_can(args->self)){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "This value (of type %s) cannot hold properties.", - cwal_value_type_name(args->self)); - } - ar = cwal_new_array(args->engine); - if(!ar) return CWAL_RC_OOM; - rc = cwal_props_visit_keys( args->self, - s2_value_visit_append_to_array, ar ); - if(!rc) *rv = cwal_array_value(ar); - else { - cwal_array_unref(ar); - } - return rc; -} - -/** - Script usage: obj.unset(KEY [, ... KEY]) - - If obj is-a Array and KEY is-a Integer then the key is interpreted - as an array index. Sets *rv to true on success. -*/ -static int s2_cb_prop_unset( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - uint16_t i = 0; - int b = 0; - s2_engine * se; - if( (rc = s2_immutable_container_check_cb(args, args->self)) ) return rc; - se = s2_engine_from_args(args); - assert(se); - if(!args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "'unset' requires (INDEX|KEY,...) arguments."); - } - for( ; !rc && (i < args->argc); ++i ){ - rc = s2_set_v( se, args->self, args->argv[i], 0 ); - if(CWAL_RC_NOT_FOUND==rc){ - rc = 0; - } - else if(rc) rc = s2_handle_set_result(se, 0, rc); - else ++b; - } - if(!rc) *rv = cwal_new_bool( b>0 ); - return rc; -} - -/** - Script usage: obj.get(KEY) - - Returns the given key for the given obj, or undefined if not - found. If KEY is an integer and obj is-a Array then this function - treats the key as an array index. Sets *rv to the fetch value if found, - else sets it to the undefined value. - - Potential TODO: - - obj.get([list of keys]) - - returns [list of values] -*/ -static int s2_cb_prop_get( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - ARGS_SE; - if( (rc = s2_immutable_container_check_cb(args, args->self)) ) return rc; - else if(!args->argc){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "'get' requires (INDEX|KEY) argument."); - }else{ - /* Reminder: we use s2_get_v() instead of cwal_prop_get_v() - for the prototype pseudo-property and integer array - index support. OTOH, this makes it impossible to - get at Object-side props of a Hash which is running - in object-like mode. - */ - rc = - s2_handle_get_result(se, 0, s2_get_v(se, args->self, - args->argv[0], rv)); - if(!rc && !*rv) *rv = cwal_value_undefined(); - return rc; - } -} - - -/** - Script usage: obj.set(KEY,VAL) - - Sets *rv to the VAL argument. If KEY is an integer and obj is-a - Array then this function treats the key as an array index. - - Or: - - obj.set(OBJ2) - - to copy all keys from OBJ2 to obj. -*/ -static int s2_cb_prop_set( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - ARGS_SE; - if(1 == args->argc){ - /** - obj.set(OBJ2) copies all properties of OBJ2. - */ - if(!cwal_props_can(args->argv[0])){ - rc = CWAL_RC_TYPE; - goto misuse; - }else{ - rc = cwal_props_copy( args->argv[0], args->self ); - if(!rc) *rv = args->self; - return rc; - } - } - else if(2 == args->argc){ - assert(args->self); - rc = s2_set_v( se, args->self, - args->argv[0], args->argv[1] ); - if(!rc) *rv = args->argv[1]; - return rc; - } - /* Else fall through */ - misuse: - assert(rc); - return s2_engine_err_set(se, rc, - "set() requires (INDEX, VALUE) " - "or (OBJECT) arguments."); -} - -#if 0 -/** - Just an experiment. -*/ -static int s2_cb_container_experiment( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_value * arg = args->argc ? args->argv[0] : args->self; - s2_container_config( arg, 1, 1, 1 ); - *rv = arg; - return 0; -} -#endif - -static int s2_cb_props_copy_to( cwal_callback_args const * args, - cwal_value **rv ){ - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting one or more " - "container-type arguments."); - }else if(!cwal_props_can(args->self)){ - return cwal_exception_setf(args->engine, CWAL_RC_TYPE, - "'this' is not a container type."); - }else{ - int rc = 0; - uint16_t i = 0; - for( ; !rc && i < args->argc; ++i ){ - cwal_value * arg = args->argv[i]; - if(!arg/*can only happen via from-C calls*/ - || !cwal_props_can(arg)){ - rc = cwal_exception_setf(args->engine, CWAL_RC_TYPE, - "Argument #%d is of type (%s), " - "but we require a container type.", - (int)i+1, - arg ? cwal_value_type_name(arg) : ""); - }else{ - rc = cwal_props_copy(args->self, arg) - /* 2020-02-20: consider exposing - s2_eval.c:cwal_kvp_visitor_props_copy_s2() for re-use - here, primarily to improve the error reporting for const - violations. */; - if(!rc) *rv = arg; - /* - TODO: when i'm on a machine where i can change the docs to - match: change *rv to args->self, for consistency. OTOH, - being able to: - - var x = foo.copyPropertiesTo({}); - - is convenient. - */ - } - } - return rc; - } -} - - -static int s2_cb_value_may_iterate( cwal_callback_args const * args, - cwal_value **rv ){ - *rv = cwal_value_may_iterate(args->self) - ? cwal_value_true() - : cwal_value_false(); - return 0; -} - -static int s2_cb_container_is_empty( cwal_callback_args const * args, - cwal_value **rv ){ - *rv = cwal_props_has_any(args->self) - ? cwal_value_false() - : cwal_value_true(); - return 0; -} - -static int s2_cb_props_count( cwal_callback_args const * args, - cwal_value **rv ){ - return (*rv = cwal_new_integer(args->engine, - (cwal_int_t)cwal_props_count(args->self))) - ? 0 : CWAL_RC_OOM; -} - -/** - Internal cwal_kvp_visitor_f() implementation which requires state - to be a fully-populated (s2_kvp_each_state*). -*/ -int s2_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ ){ - cwal_value * av[2] = {NULL,NULL} /* (key, value) arguments for callback */; - s2_kvp_each_state * state = (s2_kvp_each_state*)state_; - cwal_value * rv = NULL; - int rc; - av[0] = state->valueArgFirst - ? cwal_kvp_value(kvp) - : cwal_kvp_key(kvp); - av[1] = state->valueArgFirst - ? cwal_kvp_key(kvp) - : cwal_kvp_value(kvp); - assert(av[0] && av[1]); - assert(state->e); - assert(state->callback); - assert(state->self); - assert(cwal_props_can(state->self)); -#if 0 - /* missing access to s2_engine object: s2_engine_sweep(state->s2); */ - cwal_engine_sweep(state->e); -#endif -#if 0 - s2_dump_val(av[0],"visiting key"); - s2_dump_val(av[1],"visiting value"); -#endif - assert(state->callback && state->self); - rc = cwal_function_call(state->callback, state->self, &rv, - 2, av ); - switch(rc){ - case 0: - if(rv==cwal_value_false()/*literal false, not another falsy*/){ - rc = S2_RC_END_EACH_ITERATION; - } - break; - case CWAL_RC_IS_VISITING: - case CWAL_RC_IS_VISITING_LIST /* for hashtable entries */: - rc = cwal_exception_setf(state->e, rc, - "Illegal iteration attempt detected."); - break; - } - cwal_refunref(rv); - return rc; -} - -static int s2_cb_prop_each( cwal_callback_args const * args, cwal_value **rv ){ - cwal_function * f; - f = args->argc - ? cwal_value_function_part(args->engine, args->argv[0]) - /* cwal_value_get_function(args->argv[0]) */ - : NULL; - if(!f){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "'eachProperty' expects a Function argument, " - "but got a %s.", - args->argc - ? cwal_value_type_name(args->argv[0]) - : ""); - }else{ - s2_kvp_each_state state = s2_kvp_each_state_empty; - int rc; - state.e = args->engine; - state.callback = f; - state.self = args->self /*better option???*/; - rc = cwal_props_visit_kvp( args->self, - s2_kvp_visitor_prop_each, - &state ); - if(S2_RC_END_EACH_ITERATION==rc) rc = 0; - if(!rc) *rv = args->self; - return rc; - } -} - -static int s2_cb_with_this( cwal_callback_args const * args, cwal_value **rv ){ - cwal_function * f; - f = args->argc - ? cwal_value_function_part(args->engine, args->argv[0]) - /* cwal_value_get_function(args->argv[0]) */ - : NULL; - if(!f){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "'withThis' expects a Function argument, " - "but got a %s.", - args->argc - ? cwal_value_type_name(args->argv[0]) - : ""); - }else{ -#if 0 - return cwal_function_call(f, args->self, rv, 0, 0); -#elif 1 - /* - In practice, the single most common script-side mistake when - calling this method is forgetting to return 'this'. Also (in - practice) callbacks must return 'this' for all current use cases - (all others are hypothetical). So... if the function does not - return anything (or returns undefined, since that's what script - function calls will do rather than returning NULL), we'll return - 'this' instead... - */ - int rc; - *rv = 0; - if(!(rc = cwal_function_call(f, args->self, rv, 0, 0))){ - if(!*rv || cwal_value_undefined()==*rv){ - *rv = args->self; - } - } - return rc; -#else - s2_func_state const * fst = s2_func_state_for_func(f); - int const rc = cwal_function_call(f, args->self, rv, 0, 0); - /* if(!rc) *rv = args->self; no, returning the callback - result is more flexible.*/ - if(fst && !fst->lastCallReturned){ - /* if a SCRIPT func does not explicitly return then - return 'this' instead of undefined. */ - *rv = args->self; - } - return rc; -#endif - } -} - -cwal_value * s2_prototype_object( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - cwal_value * v; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_OBJECT ); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - assert(!cwal_value_prototype_get(se->e, proto)); - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_OBJECT, proto ); - if(!rc) rc = s2_prototype_stash(se, "object", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_OBJECT)); - - /* MARKER(("Setting up OBJECT prototype.\n")); */ - /* - Chicken/egg: in order to ensure that, e.g. prototypes get added - to Object methods, we require the Function prototype to be in - place before those functions are installed. This applies to all - Container-level prototypes, but not to PODs because those are - looked up on demand, as opposed to being set when the value is - created, because non-Containers don't have a place to store their - prototype. - - So we'll init the other prototypes right after we set the base - Object prototype (which is used, indirectly, by all other - types). - */ -#define PROTO(F) if(!(v = F)) { \ - rc = se->e->err.code ? se->e->err.code : CWAL_RC_OOM; goto end; \ - } (void)0 - PROTO(s2_prototype_function(se)); - PROTO(s2_prototype_array(se)); - PROTO(s2_prototype_exception(se)); - PROTO(s2_prototype_hash(se)); - PROTO(s2_prototype_buffer(se)); - PROTO(s2_prototype_string(se)); - PROTO(s2_prototype_integer(se)); - PROTO(s2_prototype_double(se)); - /* PROTO(s2_prototype_unique(se)); */ - /* PROTO(s2_prototype_tuple(se)); */ -#undef PROTO - - -#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define RC if(rc) goto end; - -#if 0 - v = cwal_new_string_value(se->e, "Objekt", 6); - CHECKV; - rc = cwal_prop_set_v( proto, se->cache.keyTypename, v ); - RC; -#endif - - { - s2_func_def const funcs[] = { - /* S2_FUNC2("experiment", s2_cb_container_experiment), */ - S2_FUNC2("clearProperties", s2_cb_prop_clear), - S2_FUNC2("copyPropertiesTo", s2_cb_props_copy_to), - S2_FUNC2("compare", s2_cb_value_compare), - S2_FUNC2("eachProperty", s2_cb_prop_each), - S2_FUNC2("get", s2_cb_prop_get), - S2_FUNC2("hasOwnProperty", s2_cb_prop_has_own), - S2_FUNC2("mayIterate", s2_cb_value_may_iterate), - S2_FUNC2("isEmpty", s2_cb_container_is_empty), - S2_FUNC2("propertyKeys", s2_cb_prop_keys), - S2_FUNC2("propertyCount", s2_cb_props_count), - S2_FUNC2("set", s2_cb_prop_set), - S2_FUNC2("toJSONString", s2_cb_this_to_json_token), - S2_FUNC2("toString", s2_cb_value_to_string), - S2_FUNC2("unset", s2_cb_prop_unset), - S2_FUNC2("withThis", s2_cb_with_this), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(!rc) rc = s2_ctor_callback_set(se, proto, - s2_cb_object_ctor); - } - -#undef CHECKV -#undef RC - end: - return rc ? NULL : proto; -} - -static int s2_cb_exception_code_string( cwal_callback_args const * args, - cwal_value **rv ){ - int rc = 0; - cwal_value * v; - v = args->argc ? args->argv[0] : cwal_prop_get(args->self, "code", 4); - if(v){ - s2_engine * se = s2_engine_from_args(args); - cwal_value *hv = s2_stash_get(se, "RcHash") - /* Optimization: if the stashed RcHash (set up in s2.c) is - available, check it first. This avoids having to allocate - x-strings which we know are already in that hash. It also - incidentally supports a reverse mapping, such that passing in - the string 'CWAL_RC_OOM' will return its integer value. - */; - *rv = 0; - if(hv){ - cwal_hash * h = cwal_value_get_hash(hv); - assert(h); - *rv = cwal_hash_search_v(h, v); - } - if(!*rv){ - cwal_size_t vstrLen = 0; - char const * const vstr = cwal_value_get_cstr(v, &vstrLen); - if(vstr){ /* If passed a string, try (the hard way) to - find the integer code. */ - int code = 0; - if(s2_cstr_to_rc(vstr, (cwal_int_t)vstrLen, &code)){ - if(! (*rv = cwal_new_integer(args->engine, (cwal_int_t)code)) ){ - rc = CWAL_RC_OOM; - } - } - if(!rc && !*rv) *rv = cwal_value_undefined(); - }else{ - /* Assume the code is an integer and get its string - form. */ - cwal_int_t const code = cwal_value_get_integer(v); - char const * str = cwal_rc_cstr2((int)code); - *rv = str - ? cwal_new_xstring_value(args->engine, str, - cwal_strlen(str)) - /* Reminder to self: cwal recycles that xstring shell - to those static bytes, so this doesn't actually - allocate anything so long as we've got an x/z-string - shell in the recycler bin :-D. - */ - : cwal_value_null(); - if(str && !*rv) rc = CWAL_RC_OOM; - } - } - }else{ - /* No arg or "code" property. */ - *rv = cwal_value_undefined(); - } - return rc; -} - -cwal_value * s2_prototype_exception( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_EXCEPTION ); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_EXCEPTION, proto ); - if(!rc) rc = s2_prototype_stash(se, "Exception", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_EXCEPTION)); - /* MARKER(("Setting up EXCEPTION prototype.\n")); */ - -#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define RC if(rc) goto end; - - { - s2_func_def const funcs[] = { - S2_FUNC2("codeString", s2_cb_exception_code_string), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - - -#undef FUNC2 -#undef CHECKV -#undef RC - end: - return rc ? NULL : proto; -} - -#undef MARKER -#undef ARGS_SE -/* end of file protos.c */ -/* start of file str.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -#include -#include -#include - -#if !defined(S2_ENABLE_ZLIB) -# define S2_ENABLE_ZLIB 0 -#endif - -#if !defined(S2_INTERNAL_MINIZ) -# if S2_ENABLE_ZLIB -# define S2_INTERNAL_MINIZ 0 -# else -# define S2_INTERNAL_MINIZ 1 -# endif -#endif - -#if S2_INTERNAL_MINIZ -# if S2_ENABLE_ZLIB -# error Cannot enable both S2_INTERNAL_MINIZ and S2_ENABLE_ZLIB. -# endif -# define MINIZ_NO_STDIO /* necessary for linux, at least */ -# define MINIZ_NO_ARCHIVE_APIS /* we aren't using these */ -#elif S2_ENABLE_ZLIB -# include -#endif - -#define S2_ENABLE_ZLIKE (S2_ENABLE_ZLIB || S2_INTERNAL_MINIZ) - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -char s2_is_compressed(unsigned char const * mem, cwal_size_t len){ - if(!mem || (len<6)) return 0; - else{ - /** - Adapted from: - - http://blog.2of1.org/2011/03/03/decompressing-zlib-images/ - - Remember that s2-compressed data has a 4-byte big-endian header - holding the uncompressed size of the data, so we skip those - first 4 bytes. - - See also: - - http://tools.ietf.org/html/rfc6713 - - search for "magic number". - */ - int16_t const head = (((int16_t)mem[4]) << 8) | mem[5]; - /* MARKER(("isCompressed header=%04x\n", head)); */ - switch(head){ - case 0x083c: case 0x087a: case 0x08b8: case 0x08f6: - case 0x1838: case 0x1876: case 0x18b4: case 0x1872: - case 0x2834: case 0x2872: case 0x28b0: case 0x28ee: - case 0x3830: case 0x386e: case 0x38ac: case 0x38ea: - case 0x482c: case 0x486a: case 0x48a8: case 0x48e6: - case 0x5828: case 0x5866: case 0x58a4: case 0x58e2: - case 0x6824: case 0x6862: case 0x68bf: case 0x68fd: - case 0x7801: case 0x785e: case 0x789c: case 0x78da: - return 1; - default: - return 0; - } - } -} - -char s2_buffer_is_compressed(cwal_buffer const *buf){ - return buf - ? s2_is_compressed( buf->mem, buf->used ) - : 0; -} - -uint32_t s2_uncompressed_size(unsigned char const *mem, - cwal_size_t len){ - return s2_is_compressed(mem,len) - ? (uint32_t)((mem[0]<<24) + (mem[1]<<16) + (mem[2]<<8) + mem[3]) - : (uint32_t)-1; -} - -uint32_t s2_buffer_uncompressed_size(cwal_buffer const * b){ - return b - ? s2_uncompressed_size(b->mem, b->used) - : (uint32_t)-1; -} - -void s2_buffer_swap( cwal_buffer * left, cwal_buffer * right ){ - void * const self1 = left->self, * const self2 = right->self - /* cwal_value parts of script-side buffers */; - cwal_buffer const tmp = *left; - assert(left && right); - assert(left != right); - *left = *right; - *right = tmp; - left->self = self1; - right->self = self2; -} - -#if S2_ENABLE_ZLIKE -/** - calls s2_buffer_swap(left, right), then... - - clearWhich == 0 means clear neither, <0 means clear the left, >0 - means clear the right. -*/ -static void s2_buffer_swap_clear( cwal_engine * e, - cwal_buffer * left, - cwal_buffer * right, - int clearWhich ){ - s2_buffer_swap(left, right); - if(0 != clearWhich) cwal_buffer_clear(e, - (clearWhich<0) ? left : right); -} -#endif -/* ^^^ S2_ENABLE_ZLIKE */ - - -int s2_buffer_compress(cwal_engine * e, cwal_buffer const *pIn, - cwal_buffer *pOut){ -#if !S2_ENABLE_ZLIKE - if(e || pIn || pOut){/*avoid unused param warning*/} - return CWAL_RC_UNSUPPORTED; -#else -#define N_OUT (13 + nIn + (nIn+999)/1000) - unsigned int nIn = (unsigned int)pIn->used; - unsigned int nOut = N_OUT; - cwal_buffer temp = cwal_buffer_empty; - int rc; -#if CWAL_SIZE_T_BITS > 32 - if( (pIn->used != (cwal_size_t)nIn) - || ((cwal_size_t)nOut != (cwal_size_t)N_OUT)){ - /* Trying to defend against a cwal_size_t too big - for unsigned int. */ - return CWAL_RC_RANGE; - }else -#endif - if(s2_buffer_is_compressed(pIn)){ - if(pIn == pOut) return 0; - else{ - cwal_buffer_reset(pOut); - return cwal_buffer_append(e, pOut, pIn->mem, pIn->used); - } - } -#undef N_OUT - rc = cwal_buffer_resize(e, &temp, nOut+4); - if(rc) return rc; - else{ - unsigned long int nOut2; - unsigned char *outBuf; - unsigned long int outSize; - outBuf = temp.mem; - outBuf[0] = nIn>>24 & 0xff; - outBuf[1] = nIn>>16 & 0xff; - outBuf[2] = nIn>>8 & 0xff; - outBuf[3] = nIn & 0xff; - nOut2 = (long int)nOut; - rc = compress(&outBuf[4], &nOut2, - pIn->mem, pIn->used); - if(rc){ - cwal_buffer_clear(e, &temp); - return CWAL_RC_ERROR; - } - outSize = nOut2+4; - rc = cwal_buffer_resize(e, &temp, outSize); - if(rc){ - cwal_buffer_clear(e, &temp); - }else{ - s2_buffer_swap_clear(e, &temp, pOut, -1); - assert(0==temp.used); - assert((cwal_size_t)outSize==pOut->used); - } - return rc; - } -#endif -/* S2_ENABLE_ZLIKE */ -} - -int s2_buffer_uncompress(cwal_engine * e, cwal_buffer const *pIn, - cwal_buffer *pOut){ -#if !S2_ENABLE_ZLIKE - if(e || pIn || pOut){/*avoid unused param warning*/} - return CWAL_RC_UNSUPPORTED; -#else - unsigned int nOut; - unsigned char *inBuf; - unsigned int nIn = (unsigned int)pIn->used; - cwal_buffer temp = cwal_buffer_empty; - int rc; - unsigned long int nOut2; - if( nIn<=4 ){ - return CWAL_RC_RANGE; - }else if(pIn == pOut && !s2_buffer_is_compressed(pIn)){ - return 0; - } - inBuf = pIn->mem; - nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3]; - /* MARKER(("decompress size: %u\n", nOut)); */ - rc = cwal_buffer_reserve(e, &temp, nOut+1); - if(rc) return rc; - nOut2 = (long int)nOut; - rc = uncompress(temp.mem, &nOut2, - &inBuf[4], nIn - 4) - /* valgrind says there's an uninitialized memory access - somewhere under uncompress(), _presumably_ for one of - these arguments, but i can't find it. fsl_buffer_reserve() - always memsets() new bytes to 0. - - Turns out it's a known problem: - - http://www.zlib.net/zlib_faq.html#faq36 - */; - if( rc ){ - cwal_buffer_clear(e, &temp); - return CWAL_RC_ERROR; - } - rc = cwal_buffer_resize(e, &temp, nOut2); - if(!rc){ - void * const self = pOut->self - /* cwal_value part of script-side buffers */; - temp.used = (cwal_size_t)nOut2; - if( pOut==pIn ){ - cwal_buffer_clear(e, pOut); - } - assert(!pOut->mem); - *pOut = temp; - pOut->self = self; - }else{ - cwal_buffer_clear(e, &temp); - } - return rc; -#endif -/* S2_ENABLE_ZLIKE */ -} - - -/* - Various helper macros for callbacks... -*/ - -#define ARGS_SE s2_engine * se = s2_engine_from_args(args) - -#define THIS_STRING \ - cwal_string * self; ARGS_SE; \ - if(!se) return CWAL_RC_MISUSE; \ - if(!(self=cwal_value_string_part(args->engine, args->self)) && args->argc) \ - self = cwal_value_string_part(args->engine, args->argv[0]); \ - if(!self){ \ - return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \ - "Expecting a string 'this' or first argument." ); \ - } (void)0 -#define THIS_CSTRING(USE_EMPTY_FOR_BUFFER) \ - char const * self; cwal_size_t selfLen; ARGS_SE; \ - if(!se) return CWAL_RC_MISUSE; \ - else if(!(self=cwal_value_get_cstr(args->self, &selfLen))){ \ - if(USE_EMPTY_FOR_BUFFER && cwal_value_get_buffer(args->self)){ \ - self = ""; selfLen = 0; \ - }else { \ - return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \ - "Expecting a string or buffer 'this' argument." ); \ - }\ - } (void)0 - - -#define THIS_BUFFER \ - cwal_buffer * self; ARGS_SE; \ - if(!se) return CWAL_RC_MISUSE; \ - else if(!(self=cwal_value_buffer_part(args->engine, args->self))){ \ - return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \ - "'this' is-not-a Buffer." ); \ - } (void)0 - - -static int s2_cb_str_concat( cwal_callback_args const * args, cwal_value **rv ){ - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "'concat' requires at least one argument."); - }else{ - int rc = 0; - cwal_size_t i = 0, argc = args->argc; - cwal_buffer buf = cwal_buffer_empty; - s2_engine * se = s2_engine_from_args(args); - cwal_string * str = cwal_value_get_string(args->self); - assert(se); - if(1==args->argc && str - && 0==cwal_string_length_bytes(str) - && cwal_value_is_string(args->argv[0])){ - /* optimization: "".concat(anyString) simply returns argv[0] */ - *rv = args->argv[0]; - return 0; - } - if(cwal_value_is_string(args->self)){ - rc = s2_value_to_buffer( se->e, &buf, args->self ); - } - for( ; !rc && (i < argc); ++i ){ - rc = s2_value_to_buffer( se->e, &buf, args->argv[i] ); - } - if(!rc && !(*rv = cwal_string_value(cwal_buffer_to_zstring(args->engine, - &buf))) - ){ - rc = CWAL_RC_OOM; - } - cwal_buffer_reserve( args->engine, &buf, 0 ); - return rc; - } -} - - -/** - Impl of (STRING+VALUE) operator. - - Assumes operator+ or operator+= call form with the operand at args->argv[0]. -*/ -static int s2_cb_str_op_concat( cwal_callback_args const * args, cwal_value **rv ){ - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "'concat' requires at lest one argument."); - }else{ - int rc = 0; - cwal_size_t i = 0, argc = args->argc; - cwal_buffer buf = cwal_buffer_empty; - assert(1==args->argc); - rc = s2_value_to_buffer( args->engine, &buf, args->self ); - for( ; !rc && (i < argc); ++i ){ - /* s2_dump_val( args->argv[i], "arg"); */ - rc = s2_value_to_buffer( args->engine, &buf, args->argv[i] ); - } - if(!rc){ - *rv = cwal_string_value(cwal_buffer_to_zstring(args->engine, &buf)); - if(!*rv) rc = CWAL_RC_OOM; - else{ - assert(!buf.mem); - assert(!buf.capacity); - } - } - cwal_buffer_reserve( args->engine, &buf, 0 ); - return rc; - } -} - -#if 0 -/* TODO? Move s2 op-level support for unary +/-STRING - to here? -*/ - -/** - Impl of +STRING and -STRING operators. - - Assumes unary call form with the string on the RHS. -*/ -static int s2_cb_str_op_unarypm( int mode /*<0==minus, >0==plus*/, - cwal_callback_args const * args, - cwal_value **rv ){ -} -#endif - - -static int s2_cb_str_byte_at( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t pos; - THIS_STRING; - pos = (args->argc>0) - ? (cwal_value_is_number(args->argv[0]) - ? cwal_value_get_integer(args->argv[0]) - : -1) - : -1; - if(pos<0){ - return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, - "byteAt expects an integer argument " - "with a value of 0 or more."); - }else{ - unsigned char const * cstr = - (unsigned char const *)cwal_string_cstr(self); - cwal_size_t const slen = cwal_string_length_bytes(self); - if(pos >= (cwal_int_t)slen){ - *rv = cwal_value_undefined(); - }else{ - *rv = cwal_new_integer(args->engine, cstr[pos]); - } - return *rv ? 0 : CWAL_RC_OOM; - } -} - -static int s2_cb_str_isascii( cwal_callback_args const * args, cwal_value **rv ){ - THIS_STRING; - *rv = cwal_new_bool( cwal_string_is_ascii(self) ); - return 0; -} - -static int s2_cb_str_char_at( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t pos; - THIS_STRING; - pos = (args->argc>0) - ? (cwal_value_is_number(args->argv[0]) - ? cwal_value_get_integer(args->argv[0]) - : -1) - : -1; - if(pos<0){ - return s2_throw( se, CWAL_RC_MISUSE, - "charAt expects an integer argument with a " - "value of 0 or more."); - }else{ - unsigned char const * cstr = - (unsigned char const *)cwal_string_cstr(self); - cwal_size_t const slen = cwal_string_length_bytes(self); - int const asInt = (args->argc>1) && cwal_value_get_bool(args->argv[1]); - if(pos >= (cwal_int_t)slen){ - *rv = cwal_value_undefined(); - }else{ - unsigned int cpoint = (unsigned int)-1; - if( cwal_string_is_ascii(self) ){ - cpoint = (unsigned int)cstr[pos]; - }else{ - cwal_utf8_char_at( cstr, cstr + slen, pos, &cpoint); - } - if((unsigned int)-1 == cpoint){ - *rv = cwal_value_undefined(); - }else{ - if(asInt){ - *rv = cwal_new_integer(args->engine, (cwal_int_t)cpoint); - } - else { - unsigned char buf[6] = {0,0,0,0,0,0}; - int const clen = cwal_utf8_char_to_cstr(cpoint, buf, - sizeof(buf)); - assert(clen<(int)sizeof(buf)); - assert(0 != clen); - if(clen<1) *rv = cwal_value_undefined(); - else *rv = cwal_new_string_value(se->e, (char const *)buf, - (cwal_size_t)clen); - } - } - } - return *rv ? 0 : CWAL_RC_OOM; - } -} - -static int s2_cb_str_toupperlower( cwal_callback_args const * args, - cwal_value **rv, - char doUpper ){ - THIS_STRING; - return cwal_string_case_fold( args->engine, self, rv, doUpper ); -} - -static int s2_cb_str_tolower( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_str_toupperlower( args, rv, 0 ); -} - -static int s2_cb_str_toupper( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_str_toupperlower( args, rv, 1 ); -} - - -/** - Internal helper for s2_cb_str_split(), which splits the given - string into an array of all of its individual characters. -*/ -static int s2_cb_str_split_chars(cwal_callback_args const * args, - cwal_value **rv, - unsigned char const * str, - cwal_size_t slen, - cwal_int_t limit ){ - int rc = 0; - cwal_array * ar; - unsigned char const * pos = str; - unsigned char const * eof = str + slen; - cwal_value * v; - cwal_int_t count = 0; - assert(str); - ar = cwal_new_array(args->engine); - if(!ar) return CWAL_RC_OOM; -#if 1 - rc = cwal_array_reserve(ar, slen /*in bytes, but it's close enough */); -#endif - for( ; !rc && pos < eof; ){ - unsigned char const * cend = pos; - cwal_utf8_read_char(pos, eof, &cend); - if(!(cend-pos)) break /* ??? */; - v = cwal_new_string_value(args->engine, (char const *)pos, - (cwal_size_t)(cend-pos)); - if(!v){ - rc = CWAL_RC_OOM; - break; - } - cwal_value_ref(v); - rc = cwal_array_append(ar, v); - cwal_value_unref(v); - pos = cend; - if(limit > 0 && ++count==limit) break; - } - if(rc){ - cwal_array_unref(ar); - }else{ - *rv = cwal_array_value(ar); - } - return rc; - -} - -static int s2_cb_str_split( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_size_t sepLen = 0; - unsigned char const * sep; - THIS_STRING; - sep = args->argc - ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], &sepLen) - : NULL; - if(!sep){ - return s2_throw( se, CWAL_RC_MISUSE, - "Expecting a non-empty string argument."); - }else if(!sepLen){ - /* Split into individual characters */ - cwal_midsize_t slen = 0; - unsigned char const * pos = - (unsigned char const *)cwal_string_cstr2(self, &slen); - cwal_int_t limit; - assert(pos); - limit = (args->argc>1) - ? cwal_value_get_integer(args->argv[1]) - : 0; - return s2_cb_str_split_chars(args, rv, pos, slen, limit); - }else{ - int rc = 0; - cwal_int_t count = 0; - cwal_int_t limit; - cwal_array * ar = NULL; - cwal_midsize_t slen = 0; - unsigned char const * pos = - (unsigned char const *)cwal_string_cstr2(self, &slen); - unsigned char const * eof = pos + slen; - unsigned char const * start = pos; - cwal_value * v; - limit = (args->argc>1) - ? cwal_value_get_integer(args->argv[1]) - : 0; - ar = cwal_new_array(args->engine); - if(!ar) return CWAL_RC_OOM; - cwal_value_ref(cwal_array_value(ar)); - if(!slen || (sepLen>slen)){ - rc = cwal_array_append(ar, args->self); - } - else while( 1 ){ - if( (pos>=eof) - || (0==memcmp(sep, pos, sepLen)) - ){ - cwal_size_t sz; - char last = (pos>=eof); - if(pos>eof) pos=eof; - - sz = pos-start; - v = cwal_new_string_value(args->engine, - (char const *)start, - sz); - cwal_value_ref(v); - if(!v && sz){ - rc = CWAL_RC_OOM; - goto end; - } - else rc = cwal_array_append(ar, v); - cwal_value_unref(v); - v = 0; - if(rc) goto end; - if(limit>0 && ++count==limit){ - pos = eof; - last = 1; - } - - if(last) goto end; - /* ++count; */ - pos += sepLen; - start = pos; - }else{ - cwal_utf8_read_char( pos, eof, &pos ); - } - } - end: - if(rc && ar){ - cwal_value_unref(cwal_array_value(ar)); - }else if(!rc){ - *rv = cwal_array_value(ar); - cwal_value_unhand(*rv); - if(!*rv) rc = CWAL_RC_OOM; - } - return rc; - } -} - -/** - Script usage: - - var newString = aString.replace(needle, replacement [, limit = 0]) - - TODO: a variant for Buffers, except that we need different return - semantics (no returning self, unless we edit the buffer inline). -*/ -static int s2_cb_str_replace( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_size_t needleLen = 0; - unsigned char const * needle; - cwal_size_t replLen = 0; - unsigned char const * repl; - cwal_string * self; - ARGS_SE; - assert(se); - self = cwal_value_string_part(args->engine, args->self); - if(!self){ - return s2_cb_throw( args, CWAL_RC_TYPE, - "Expecting a string 'this'." ); - } - needle = args->argc - ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], &needleLen) - : NULL; - repl = args->argc>1 - ? (unsigned char const *)cwal_value_get_cstr(args->argv[1], &replLen) - : NULL; - if(!needle || !repl){ - return s2_cb_throw( args, CWAL_RC_MISUSE, - "Expecting a non-empty string argument."); - }else if(!needleLen){ - *rv = args->self; - return 0; - }else{ - int rc = 0; - cwal_int_t matchCount = 0; - cwal_midsize_t slen = 0; - unsigned char const * pos = - (unsigned char const *)cwal_string_cstr2(self, &slen); - unsigned char const * eof = pos + slen; - unsigned char const * start = pos; - cwal_buffer * buf = &se->buffer; - cwal_size_t const oldUsed = buf->used; - cwal_int_t const limit = args->argc>2 - ? cwal_value_get_integer(args->argv[2]) - : 0; - if(!slen || (needleLen>slen)){ - *rv = args->self; - return 0; - } - while( 1 ){ - if( (pos>=eof) - || (0==memcmp(needle, pos, needleLen)) - ){ - cwal_size_t sz; - char last = (pos>=eof) ? 1 : 0; - if(!last && matchCount++==limit && limit>0){ - pos = eof; - last = 1; - } - else if(pos>eof) pos=eof; - sz = pos-start; - if(sz){ - /* Append pending unmatched part... */ - rc = cwal_buffer_append(args->engine, buf, - start, sz); - } - if(!rc && posengine, buf, - repl, replLen); - } - if(rc || last) break; - pos += needleLen; - start = pos; - }else{ - cwal_utf8_read_char( pos, eof, &pos ); - } - } - if(!rc){ - if(!matchCount){ - *rv = args->self; - }else if(buf->used == oldUsed){ - /* replaced the whole string with nothing */ - *rv = cwal_new_string_value(args->engine, "", 0); - }else{ - assert(buf->used > oldUsed); - *rv = cwal_new_string_value(args->engine, (char const *)buf->mem, - buf->used - oldUsed); - if(!*rv) rc = CWAL_RC_OOM; - } - } - buf->used = oldUsed; - assert( buf->capacity > oldUsed ); - buf->mem[oldUsed] = 0; - return rc; - } -} - -/** - Script usage: - - var x = "haystack".indexOf("needle" [, startOffset=0]) - - if(x<0) { ...no match found... } - else { x === index of "needle" in "haystack" } - - If the startOffset is negative, it is counted as the number of - characters from the end of the haystack, but does not change the - search direction (because counting utf8 lengths backwards sounds - really hard ;). -*/ -static int s2_cb_str_indexof( cwal_callback_args const * args, cwal_value **rv ){ - if(!args->argc){ - misuse: - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting 1 string argument."); - }else{ - char const * arg; - cwal_size_t aLen = 0; - THIS_STRING; - arg = cwal_value_get_cstr(args->argv[0], &aLen); - if(!arg) goto misuse; - else if(!aLen){ - *rv = cwal_new_integer(args->engine, -1); - return 0; - } - else{ - cwal_midsize_t sLen = 0; - char const * myStr = cwal_string_cstr2(self, &sLen); - cwal_int_t i = 0; - cwal_int_t offset; - if(aLen>sLen){ - *rv = cwal_new_integer(args->engine, -1); - return 0; - } - offset = (args->argc>1) - ? cwal_value_get_integer(args->argv[1]) - : 0; - if(!offset && aLen==sLen){ - int const rc = memcmp(myStr, arg, (size_t)sLen); - *rv = rc - ? cwal_new_integer(args->engine, -1) - : cwal_new_integer(args->engine, 0); - return 0; - }else{ - i = cwal_utf8_indexof( myStr, sLen, offset, - arg, aLen, 0 ); - *rv = cwal_new_integer(args->engine, i>=0 ? i : -1); - return *rv ? 0 : CWAL_RC_OOM; - } - } - } -} - -static int s2_cb_str_length( cwal_callback_args const * args, cwal_value **rv ){ - THIS_STRING; - *rv = cwal_new_integer( args->engine, - (cwal_int_t)cwal_string_length_bytes(self) ); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_str_length_utf8( cwal_callback_args const * args, cwal_value **rv ){ - THIS_STRING; - *rv = cwal_new_integer(args->engine, - (cwal_int_t) cwal_string_length_utf8(self) ); - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - -*/ -static int s2_cb_str_substr( cwal_callback_args const * args, cwal_value **rv ){ - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting 1 or 2 integer arguments."); - }else{ - cwal_int_t offset, len; - THIS_CSTRING(1); - offset = cwal_value_get_integer(args->argv[0]); - len = (args->argc>1) - ? cwal_value_get_integer(args->argv[1]) - : -1; - if(len<0){ - len = (cwal_int_t)selfLen; - } - if(offset<0){ - offset = (cwal_int_t)selfLen + offset; - if(offset < 0) offset = 0; - } - if(offset>=(cwal_int_t)selfLen){ - *rv = cwal_new_string_value(args->engine, NULL, 0); - return 0; - } - else if((offset+len) > (cwal_int_t)selfLen){ - len = (cwal_int_t)selfLen - offset; - /* assert(len < sLen); */ - if(len > (cwal_int_t)selfLen) len = (cwal_int_t)selfLen; - } - { - /* Calculate the range/length based on the UTF8 - length. */ - unsigned char const * at = (unsigned char const *)self; - unsigned char const * eof = at + selfLen; - unsigned char const * start; - unsigned char const * end; - cwal_int_t i = 0; - for( ; i < offset; ++i ){ - cwal_utf8_read_char( at, eof, &at ); - } - start = at; - if(len>=0){ - end = start; - for( i = 0; (end= start); - len = end - start; - *rv = cwal_new_string_value(args->engine, - (char const *)start, len); - } - return *rv ? 0 : CWAL_RC_OOM; - } -} - - -/** - Impl for trim/trimLeft/trimRight(). mode determines which: - - <0 == left - 0 == both - >0 == right -*/ -static int s2_cb_str_trim_impl( cwal_callback_args const * args, - cwal_value **rv, - int mode ){ - THIS_STRING; - { - int rc = 0; - unsigned char const * cs = (unsigned char const *)cwal_string_cstr(self); - cwal_int_t const len = (cwal_int_t)cwal_string_length_bytes(self); - unsigned char const * end = cs + len; - if(!len){ - *rv = args->self; - return rc; - } - if(mode <= 0) for( ; *cs && s2_is_space((int)*cs); ++cs){} - if(mode>=0){ - for( --end; (end>cs) && s2_is_space((int)*end); --end){} - ++end; - } - *rv = ((end-cs) == len) - ? args->self - : cwal_new_string_value(args->engine, - (char const *)cs, end-cs) - ; - return *rv ? 0 : CWAL_RC_OOM; - } -} - -static int s2_cb_str_trim_left( cwal_callback_args const * args, - cwal_value **rv ){ - return s2_cb_str_trim_impl( args, rv, -1 ); -} -static int s2_cb_str_trim_right( cwal_callback_args const * args, - cwal_value **rv ){ - return s2_cb_str_trim_impl( args, rv, 1 ); -} - -static int s2_cb_str_trim_both( cwal_callback_args const * args, - cwal_value **rv ){ - return s2_cb_str_trim_impl( args, rv, 0 ); -} - -static int s2_cb_str_to_string( cwal_callback_args const * args, cwal_value **rv ){ - cwal_string * str; - ARGS_SE; - str = cwal_value_string_part( args->engine, args->self ); - if(!str){ - return s2_throw(se, CWAL_RC_TYPE, - "FIXME: check for a different toString() impl in this case."); - }else{ - *rv = cwal_string_value(str); - return 0; - } -} - -static int s2_cb_str_unescape_c( cwal_callback_args const * args, - cwal_value **rv ){ - if(!args->argc && !cwal_value_is_string(args->self)){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "'unescape' requires at lest one argument."); - }else{ - int rc = 0; - unsigned char const * begin = 0; - cwal_size_t sLen = 0; - cwal_string * sv; - cwal_value * theV; - s2_engine * se = s2_engine_from_args(args); - cwal_buffer * buf = &se->buffer; - cwal_size_t const oldUsed = buf->used; - if(cwal_value_is_string(args->self)){ - theV = args->self; - }else{ - theV = args->argv[0]; - } - sv = cwal_value_get_string(theV); - if(!sv){ - return s2_throw(se, CWAL_RC_TYPE, - "Expecting a STRING as 'this' or first argument."); - } - begin = (unsigned char const *)cwal_string_cstr(sv); - sLen = cwal_string_length_bytes(sv); - if(!sLen){ - *rv = cwal_string_value(sv); - return 0; - } - rc = s2_unescape_string(args->engine, (char const *)begin, (char const *)begin + sLen, buf ); - assert(buf->used >= oldUsed); - if(rc){ - return s2_throw(se, rc, "Unescaping failed with rc=%s. " - "Probably(?) due to invalid UTF8 or an unknown " - "\\Uxxxxxxxx sequence.", - cwal_rc_cstr(rc)); - } - if((sLen == (buf->used-oldUsed)) - && (0==cwal_compare_cstr((char const*)buf->mem, buf->used, - (char const*)begin, sLen))){ - /* Same byte sequence - re-use it. */ - *rv = theV; - }else{ - *rv = cwal_new_string_value( args->engine, - ((char const *)buf->mem+oldUsed), - buf->used-oldUsed ); - } - buf->used = oldUsed; - return *rv ? 0 : CWAL_RC_OOM; - } -} - - -/** - Internal helper for Buffer.appendf() and String.applyFormat() -*/ -static int s2_helper_appendf( s2_engine * se, cwal_buffer * buf, - char const * fmt, - cwal_size_t fmtLen, - uint16_t argc, - cwal_value * const * argv ){ - cwal_size_t oldUsed = buf->used; - int rc = cwal_buffer_format( se->e, buf, fmt, fmtLen, argc, argv); - if(rc && (CWAL_RC_OOM != rc)){ - if(buf->used > oldUsed){ - /* Error string is embedded in the buffer... */ - rc = s2_throw(se, rc, "%s", - (char const *)(buf->mem + oldUsed)); - buf->used = oldUsed; - }else{ - rc = s2_throw(se, rc, "String formatting failed with code: %s", - cwal_rc_cstr(rc)); - } - } - return rc; -} - -static int s2_cb_str_apply_format( cwal_callback_args const * args, cwal_value **rv ){ - char const * fmt; - cwal_size_t fmtLen; - cwal_size_t oldUsed; - cwal_buffer * buf; - int rc; - THIS_STRING; - fmt = cwal_string_cstr(self); - fmtLen = cwal_string_length_bytes(self); - buf = &se->buffer; - oldUsed = buf->used; - rc = s2_helper_appendf( se, buf, fmt, fmtLen, args->argc, args->argv); - if(!rc){ - cwal_size_t const n = buf->used - oldUsed; - *rv = cwal_new_string_value(args->engine, - ((char const *)buf->mem+oldUsed), n); - rc = *rv ? 0 : CWAL_RC_OOM; - } - buf->used = oldUsed; - return rc; -} - -static int s2_cb_strish_eval_contents( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - char const * fname = 0; - cwal_size_t nameLen = 0, slen = 0; - char const catchReturn = 1; - char const * src; - s2_engine * se = s2_engine_from_args(args); - cwal_buffer bufSwap = cwal_buffer_empty; - cwal_buffer * bufSelf = cwal_value_get_buffer(args->self); - cwal_value * vars = 0; - /* - BUG (and fix) REMINDER: - - When this callback is applied to a Buffer, if that buffer's - contents are modified while this call is active, results are - undefined. As a partial solution, we move the buffer's contents - out of the way before evaluation, swapping it back in before - returning. That disallows the (corner case) possibility of - recursion, but it gives us predictable/safe results. - */ - assert(se); - src = cwal_value_get_cstr(args->self, &slen) - /* Corner case reminder: that won't work if args->self - _derives_ from a Buffer */; - if(!src){ - if(cwal_value_buffer_part(args->engine, args->self)){ - *rv = cwal_value_undefined(); - return 0; - }else{ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "'this' type (%s) is not valid for this function.", - cwal_value_type_name(args->self)); - } - } - if(!slen){ - /* empty buffer/string */ - *rv = cwal_value_undefined(); - return 0; - } - - if(2==args->argc){ - /* - Accept (string,object) or (object,string) - */ - if(cwal_props_can(args->argv[0])){ - vars = args->argv[0]; - fname = cwal_value_get_cstr(args->argv[1], &nameLen); - }else{ - fname = cwal_value_get_cstr(args->argv[0], &nameLen); - if(cwal_props_can(args->argv[1])) vars = args->argv[1]; - } - }else if(args->argc){ - /* Accept (string) or (object) */ - fname = cwal_value_get_cstr(args->argv[0], &nameLen); - if(!fname && cwal_props_can(args->argv[0])){ - vars = args->argv[0]; - } - } - if(!fname){ - fname = cwal_value_type_name(args->self); - assert(fname); - nameLen = cwal_strlen(fname); - } -#if 0 - /* this is a nice idea, but the internal script-func-forwarding - callback will catch a propagated 'return', so we don't get the - effect i was going for. */ - if(args->argc>0){ - catchReturn = cwal_value_get_bool(args->argv[1]); - } -#endif - - if(vars){ - rc = cwal_scope_import_props( args->scope, vars ); - if(rc){ - rc = s2_cb_throw(args, rc, "Import of vars for evaluation failed."); - goto end; - } - } - - if(bufSelf){ - /* Move bufSelf->mem out of the way in case this eval modifies - bufSelf, because we'd otherwise have undefined behaviour. - If bufSelf is modified by the eval, any modifications to it - are discarded after the eval. That's probably the safest option - for how to handle that corner case. - */ - s2_buffer_swap(bufSelf, &bufSwap); - } - *rv = cwal_value_undefined() - /* make sure it's populated to avoid an assertion failure below */; - rc = s2_eval_cstr(se, 1, fname, src, (int)slen, rv) - /* Reminder: if we don't use a new scope or explicitly - ++se->sguard->vacuum for the eval, vacuuming gets us - somewhere (seems to be the argv array). That really should - be vacuum-safe, at least for the duration of the call(). - Toggling vacuum-safeness after the call requires a flag - in s2_engine. Doing that doesn't seem to solve it - someone else - is getting vacuumed, but the argv is suspect because the stack - trace generation for exceptions throw via here includes - a prior instance of the argv array, complete with the arguments - intact. i.e. effectively memory corruption. - */; - end: - if(bufSelf && bufSwap.mem){ - /* restore bufSelf's memory */ - s2_buffer_swap(bufSelf, &bufSwap); - cwal_buffer_clear(args->engine, &bufSwap); - } - if(catchReturn && CWAL_RC_RETURN==rc){ - *rv = s2_propagating_take(se); - assert(*rv); - rc = 0; - }else if(CWAL_RC_EXCEPTION==rc){ - assert(cwal_exception_get(se->e)); - }else if(!rc){ - assert((*rv - ? ((cwal_value_is_builtin(*rv) || cwal_value_scope(*rv))) - : 1) - || "lifetime check failed! cwal memory mismanagement!"); - if(!*rv) *rv = cwal_value_undefined(); - } - return rc; -} - - -cwal_value * s2_prototype_string( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_STRING ); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_prototype_set(proto, 0 /* so we don't inherit Object!*/); - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_STRING, proto ); - if(!rc) rc = s2_prototype_stash(se, "String", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_STRING)); - /* MARKER(("Setting up STRING prototype.\n")); */ - - { - cwal_value * v; - const s2_func_def funcs[] = { - S2_FUNC2("applyFormat", s2_cb_str_apply_format), - S2_FUNC2("byteAt", s2_cb_str_byte_at), - S2_FUNC2("charAt", s2_cb_str_char_at), - S2_FUNC2("compare", s2_cb_value_compare), - S2_FUNC2("concat", s2_cb_str_concat), - S2_FUNC2("evalContents", s2_cb_strish_eval_contents), - S2_FUNC2("indexOf", s2_cb_str_indexof), - S2_FUNC2("isAscii", s2_cb_str_isascii), - S2_FUNC2("length", s2_cb_str_length_utf8), - S2_FUNC2("lengthBytes", s2_cb_str_length), - S2_FUNC2("lengthUtf8", s2_cb_str_length_utf8), - S2_FUNC2("replace", s2_cb_str_replace), - S2_FUNC2("split", s2_cb_str_split), - S2_FUNC2("substr", s2_cb_str_substr), - S2_FUNC2("toLower", s2_cb_str_tolower), - S2_FUNC2("toJSONString", s2_cb_this_to_json_token), - S2_FUNC2("toString", s2_cb_str_to_string), - S2_FUNC2("toUpper", s2_cb_str_toupper), - S2_FUNC2("trim", s2_cb_str_trim_both), - S2_FUNC2("trimLeft", s2_cb_str_trim_left), - S2_FUNC2("trimRight", s2_cb_str_trim_right), - S2_FUNC2("unescape", s2_cb_str_unescape_c), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0 ); - if(rc) goto end; - /* Create a Function instance for s2_cb_str_op_concat and - install it manually, to avoid duplicating 2 cwal_function - wrappers for it via s2_install_functions(). */ - v = cwal_new_function_value(se->e, s2_cb_str_op_concat, 0, 0, 0); - if(!v){ rc = CWAL_RC_OOM; goto end; } - cwal_value_ref(v); - rc = cwal_prop_set_with_flags(proto, "operator+", 9, v, - CWAL_VAR_F_CONST); - if(!rc){ - rc = cwal_prop_set_with_flags(proto, "operator+=", 10, v, - CWAL_VAR_F_CONST); - } - cwal_value_unref(v); - if(rc) goto end; - } - - end: - return rc ? NULL : proto; -} - -/** - Buffer slice([offset=0 [,count=0]]) - - where count==0 means "to the end". -*/ -static int s2_cb_buffer_slice( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t offset = 0, count = -1; - cwal_buffer * bcp = NULL; - cwal_value * bv = NULL; - int rc; - THIS_BUFFER; - offset = args->argc - ? cwal_value_get_integer(args->argv[0]) - : 0; - count = args->argc>1 - ? cwal_value_get_integer(args->argv[1]) - : -1; - if(offset<0){ - return s2_throw(se, CWAL_RC_RANGE, - "Slice offset must be 0 or higher."); - } - if(offset > (cwal_int_t)self->used){ - offset = (cwal_int_t)self->used; - } - if(count < 0) count = self->used; - if((cwal_size_t)(offset + count) >= self->used){ - count = self->used - offset; - } - bcp = cwal_new_buffer(args->engine, count ? count+1 : 0 ); - if(!bcp) return CWAL_RC_OOM; - bv = cwal_buffer_value(bcp); - cwal_value_ref(bv); - rc = cwal_buffer_append(args->engine, bcp, - self->mem + offset, (cwal_size_t)count); - if(rc) cwal_value_unref(bv); - else { - cwal_value_unhand(bv); - *rv = bv; - } - return rc; -} - -/** - Script usage: - - assert aBuffer === aBuffer.replace(needle, replacement [, limit = 0]) - - (needle, replacement) must be (string, string) or (byte, byte), - where byte means integer in the range 0..255. Potential TODO: - change 'byte' to mean "UTF codepoint". That's currently easier to do - in script code: b.replace("...", 0x00a9.toChar()) - - Potential TODO: replace(buffer needle,buffer replacement), which - replaces arbitrary byte-range matches (as opposed to UTF8 chars). -*/ -static int s2_cb_buffer_replace( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_size_t needleLen = 0; - unsigned char const * needle; - cwal_size_t replLen = 0; - unsigned char const * repl; - cwal_buffer * self; - static const char * usageError = - "Expecting (string,string) or (int, int) arguments."; - ARGS_SE; - assert(se); - self = cwal_value_buffer_part(args->engine, args->self); - if(!self){ - if(se){/*avoid unused param warning*/} - return s2_cb_throw( args, CWAL_RC_TYPE, - "Expecting a Buffer 'this'." ); - } - needle = args->argc - ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], - &needleLen) - : NULL; - repl = args->argc>1 - ? (unsigned char const *)cwal_value_get_cstr(args->argv[1], - &replLen) - : NULL; - if(!needle && args->argc && cwal_value_is_integer(args->argv[0])){ - /* replace(byte, byte) */ - cwal_int_t const needleByte = - cwal_value_get_integer(args->argv[0]); - cwal_int_t const replByte = - (args->argc>1 && cwal_value_is_integer(args->argv[1])) - ? (cwal_value_get_integer(args->argv[1])) - : -1; - if(replByte<0){ - return s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError); - }else if((needleByte & ~0xFF) || (replByte & ~0xFF)) { - return s2_cb_throw( args, CWAL_RC_RANGE, - "Byte values must be in the range [0,255]." ); - }else{ - cwal_int_t const limit = args->argc>2 - ? cwal_value_get_integer(args->argv[2]) - : 0; - unsigned char const bN = (unsigned char)(needleByte & 0xFF); - unsigned char const bR = (unsigned char)(replByte & 0xFF); - int const rc = - cwal_buffer_replace_byte(args->engine, self, bN, bR, - (cwal_size_t)(limit>=0 ? (cwal_size_t)limit : 0U), - NULL); - assert(!rc && "There are no error cases here."); - if(rc){/*avoid unused param warning*/} - *rv = args->self; - return 0; - } - }else if(!needle || !repl){ - return s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError); - }else if(!needleLen){ - return s2_cb_throw( args, CWAL_RC_RANGE, - "Needle length must be >0."); - }else if(!self->used || (needleLen>self->used)){ - /* Nothing to do. */ - *rv = args->self; - return 0; - }else{ - int rc = 0; - /* replace(needle, replacement) ... */ - cwal_int_t const limit = args->argc>2 - ? cwal_value_get_integer(args->argv[2]) - : 0; - if(!needle || !repl){ - rc = s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError); - }else{ - rc = cwal_buffer_replace_str(args->engine, self, - needle, needleLen, - repl, replLen, - (limit>=0 ? (cwal_size_t)limit : 0U), - NULL); - if(rc){ - rc = s2_cb_throw(args, rc, "cwal_buffer_replace_str() failed " - "with code %d (%s).", rc, cwal_rc_cstr(rc)); - }else{ - *rv = args->self; - } - } - return rc; - } -} - - - -/** - Callback handler for buffer.length() (if isCapacity is false) and - buffer.capacity (if isCapacity is true). -*/ -static int s2_cb_buffer_length_uc( cwal_callback_args const * args, - cwal_value **rv, - char isCapacity){ - THIS_BUFFER; - if(args->argc>0){ - /* SET length */ - cwal_value * const index = args->argv[0]; - cwal_int_t len = cwal_value_get_integer(index); - int rc; - if((len<0) || !cwal_value_is_number(index)){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "%s argument must be " - "non-negative integer.", - isCapacity ? "capacity" : "length"); - } - if(isCapacity || ((cwal_size_t)len > self->capacity)){ - rc = cwal_buffer_reserve(args->engine, self, (cwal_size_t)len ); - if(rc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Setting buffer length to %"CWAL_INT_T_PFMT - " failed with code %d (%s).", - len, rc, cwal_rc_cstr(rc)); - } - } - if(!isCapacity) self->used = (cwal_size_t)len; - assert(self->used <= self->capacity); - *rv = args->self; - }else{ - *rv = cwal_new_integer( args->engine, - (cwal_int_t) - (isCapacity ? self->capacity : self->used) - ); - } - return *rv ? 0 : CWAL_RC_OOM; -} - - -static int s2_cb_buffer_length_u( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_buffer_length_uc( args, rv, 0 ); -} - -static int s2_cb_buffer_length_c( cwal_callback_args const * args, cwal_value **rv ){ - return s2_cb_buffer_length_uc( args, rv, 1 ); -} - -static int s2_cb_buffer_is_empty( cwal_callback_args const * args, cwal_value **rv ){ - THIS_BUFFER; - *rv = cwal_new_bool(self->used>0 ? 0 : 1); - return 0; -} - -static int s2_cb_buffer_length_utf8( cwal_callback_args const * args, cwal_value **rv ){ - cwal_size_t ulen; - THIS_BUFFER; - ulen = cwal_strlen_utf8((char const *)self->mem, self->used); - *rv = cwal_new_integer(args->engine, (cwal_int_t) ulen); - return *rv ? 0 : CWAL_RC_OOM; -} - - -static int s2_cb_buffer_append( cwal_callback_args const * args, cwal_value **rv ){ - uint16_t i; - int rc = 0; - THIS_BUFFER; - for( i = 0; !rc && (i < args->argc); ++i ){ - rc = s2_value_to_buffer(args->engine, self, args->argv[i]); - } - if(!rc) *rv = args->self; - return rc; -} - -static int s2_cb_buffer_appendf( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - cwal_size_t fmtlen; - char const * fmt; - THIS_BUFFER; - fmt = (args->argc>0) - ? cwal_value_get_cstr(args->argv[0], &fmtlen) - : NULL; - if((!args->argc) || !fmt){ - return s2_throw(se, CWAL_RC_MISUSE, - "Expecting (String,...) arguments."); - } - rc = s2_helper_appendf(se, self, fmt, fmtlen, args->argc-1, args->argv+1); - if(!rc){ -#if 1 - *rv = args->self; -#else - cwal_size_t const newLen = self->used - oldUsed; - *rv = cwal_new_integer(args->engine, (int)(newLen)); - rc = *rv ? 0 : CWAL_RC_OOM; -#endif - } - return rc; -} - -/** - Assumes operator call form. -*/ -static int s2_cb_buffer_op_append( cwal_callback_args const * args, cwal_value **rv ){ - THIS_BUFFER; - if(!args->argc){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "operator+ requires at least one argument."); - }else{ - int rc = 0; - cwal_size_t i = 0, argc = args->argc; - assert(1==argc); - for( ; !rc && (i < argc); ++i ){ - /* s2_dump_val( args->argv[i], "arg"); */ - rc = s2_value_to_buffer( args->engine, self, args->argv[i] ); - } - if(!rc){ - *rv = args->self; - } - return rc; - } -} - - -static int s2_cb_construct_buffer( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t len; - ARGS_SE; - assert(se); - if(cwal_value_buffer_part(args->engine, args->self)){ - /* assert(!"This doesn't seem to be possible any more."); */ - return s2_throw(se, CWAL_RC_MISUSE, - "Do not call a buffer as a function."); - } - len = args->argc - ? cwal_value_get_integer(args->argv[0]) - : 0; - if(len<0) len = 0; - *rv = cwal_new_buffer_value(args->engine, (cwal_size_t) len); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_buffer_take_string( cwal_callback_args const * args, cwal_value **rv ){ - THIS_BUFFER; - *rv = self->mem - ? cwal_string_value( cwal_buffer_to_zstring(args->engine, self) ) - : cwal_value_null(); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int s2_cb_buffer_to_string( cwal_callback_args const * args, cwal_value **rv ){ - char const * begin = 0; - char const * end = 0; - THIS_BUFFER; - if(!self->mem){ - *rv = cwal_value_null(); - return 0; - } - if(args->argc){ - cwal_int_t off, len; - if(!cwal_value_is_number(args->argv[0])) goto misuse; - off = cwal_value_get_integer(args->argv[0]); - if(args->argc>1){ - if(!cwal_value_is_number(args->argv[1])) goto misuse; - len = cwal_value_get_integer(args->argv[1]); - }else{ - len = off; - off = 0; - } - if(off<0 || len<0) goto misuse; - begin = (char const *)self->mem + off; - end = begin + len; - if(end>((char const *)self->mem+self->used)){ - end = (char const *)self->mem + self->used; - } - } - else { - begin = (char const *)self->mem; - end = begin + self->used; - } - *rv = cwal_new_string_value(args->engine, begin, - (cwal_size_t)(end - begin) ); - return *rv ? 0 : CWAL_RC_OOM; - misuse: - return s2_throw(se, CWAL_RC_MISUSE, - "Buffer.toString() arguments must 0 or " - "positive integers."); -} - -static int s2_cb_buffer_reset( cwal_callback_args const * args, cwal_value **rv ){ - THIS_BUFFER; - self->used = 0; - memset(self->mem, 0, self->capacity); - *rv = args->self; - return 0; -} - -static int s2_cb_buffer_resize( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t n; - THIS_BUFFER; - n = args->argc>0 - ? cwal_value_get_integer(args->argv[0]) - : -1; - if(n<0){ - return cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Expecting an integer value 0 or larger."); - }else{ - int const rc = cwal_buffer_resize(args->engine, self, (cwal_size_t)n ); - if(!rc) *rv = args->self; - return rc; - } -} - - -static cwal_int_t s2_get_byte(cwal_value const *v){ - char const * cstr = cwal_value_get_cstr(v, NULL); - return cstr ? *cstr : cwal_value_get_integer(v); -} - -int s2_cb_buffer_byte_at( cwal_callback_args const * args, cwal_value **rv ){ - THIS_BUFFER; - if(!args->argc || !cwal_value_is_number(args->argv[0])){ - return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, - "Expecting one integer argument."); - } - else { - cwal_value * const index = args->argv[0]; - cwal_int_t pos = cwal_value_get_integer(index); - if(pos<0){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Byte position argument must be " - "non-negative integer."); - }else if(args->argc<2){ - *rv = (pos>=(cwal_int_t)self->used) - ? cwal_value_undefined() - : cwal_new_integer(args->engine, self->mem[pos]) - ; - return *rv ? 0 : CWAL_RC_OOM; - }else{ - int const rc = cwal_buffer_reserve( args->engine, - self, - (cwal_size_t)pos+1); - if(rc) return rc; - self->mem[pos] = 0xFF & s2_get_byte(args->argv[1]); - *rv = cwal_value_true(); - if(self->used <= (cwal_size_t)pos) self->used = (cwal_size_t)pos+1; - return 0; - } - } -} - -int s2_cb_buffer_file_read( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - char const * fname; - cwal_buffer * newBuf = NULL; - cwal_buffer * self = cwal_value_buffer_part(args->engine, args->self); - ARGS_SE; - assert(se); - if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_READ)) ) return rc; - fname = args->argc - ? cwal_string_cstr(cwal_value_get_string(args->argv[0])) - : NULL; - if(!fname){ - return s2_throw(se, CWAL_RC_MISUSE, - "Expecting a filename argument."); - } - if(!self/* || (cwal_buffer_value(self) == s2_prototype_buffer(se))*/){ - newBuf = cwal_new_buffer(args->engine, 0); - if(!newBuf) return CWAL_RC_OOM; - } - rc = cwal_buffer_fill_from_filename( args->engine, newBuf ? newBuf : self, fname ); - if(rc){ - if(newBuf) cwal_value_unref(cwal_buffer_value(newBuf)); - assert(CWAL_RC_EXCEPTION!=rc); - rc = s2_throw(se, rc, - "Reading file [%s] failed.", - fname); - }else { - *rv = newBuf ? cwal_buffer_value(newBuf) : args->self; - } - return rc; -} - - -int s2_cb_buffer_file_write( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0; - char const * fname; - cwal_size_t nameLen = 0; - THIS_BUFFER; - rc = s2_cb_disable_check(args, S2_DISABLE_FS_WRITE); - if(rc) return rc; - fname = args->argc - ? cwal_value_get_cstr(args->argv[0], &nameLen) - : NULL; - if(!fname){ - return s2_throw(se, CWAL_RC_MISUSE, - "Expecting a filename argument."); - } - else{ - FILE * f; - char append = (args->argc>1) - ? cwal_value_get_bool(args->argv[1]) - : 0; - f = fopen( fname, append ? "a" : "w" ); - if(!f){ - if(errno){ - rc = s2_throw(se, s2_errno_to_cwal_rc(errno, CWAL_RC_IO), - "Could not open file [%.*s] " - "in %s mode. " - "errno=%d: %s", - (int)nameLen, fname, - append ? "append" : "truncate/write", - errno, strerror(errno)); - }else{ - rc = s2_throw(se, s2_errno_to_cwal_rc(errno, CWAL_RC_IO), - "Could not open file [%.*s] " - "in %s mode.", - (int)nameLen, fname, - append ? "append" : "truncate/write"); - } - }else{ - if(self->used && - 1 != fwrite(self->mem, self->used, 1, f)){ - rc = s2_throw(se, errno - ? s2_errno_to_cwal_rc(errno, CWAL_RC_IO) - : CWAL_RC_IO, - "Error writing %"CWAL_SIZE_T_PFMT" byte(s) " - "to file [%.*s].", - self->used, (int)nameLen, fname); - } - fclose(f); - } - if(!rc) *rv = args->self; - return rc; - } -} - - -/** - Script usage: - - buffer.fill( Byte [offset=0 [,length=0]]) - - Byte may be an integer or string (in which case the first byte - is used, even if it is a NUL byte). By default it fills the whole - buffer with the byte, but the offset and length may be specified. - It _will_not_ expand the buffer. If the offset/length are out of range - this is a no-op. -*/ -static int s2_cb_buffer_fill( cwal_callback_args const * args, cwal_value **rv ){ - cwal_int_t byte; - THIS_BUFFER; - if(!args->argc){ - return s2_throw(se, CWAL_RC_MISUSE, - "Expecting a byte value argument."); - } - byte = 0xFF & s2_get_byte(args->argv[0]); - if(1 == args->argc){ - cwal_buffer_fill(self, (unsigned char)byte); - *rv = args->self; - return 0; - }else{ - cwal_int_t offset; - cwal_int_t len; - cwal_size_t const limit = 1 ? self->used : self->capacity; - offset = cwal_value_get_integer(args->argv[1]); - len = (args->argc>2) - ? cwal_value_get_integer(args->argv[2]) - : 0; - if((offset<0) || (len<0)){ - return s2_throw(se, CWAL_RC_MISUSE, - "Neither offset nor length may be negative."); - } - if(0==len){ - len = ((cwal_int_t)limit <= offset) - ? 0 - : ((cwal_int_t)limit - offset); - } - if( (len>0) && ((cwal_size_t)offset < limit) ) { - if((cwal_size_t)(offset+len) > limit){ - len = (cwal_int_t)(limit - (cwal_size_t)offset); - } - /*MARKER(("Filling %d byte(s) at offset %d to 0x%02x\n", (int)len, (int)offset, (int)byte));*/ - if(len>0){ - memset( self->mem+offset, byte, (size_t)len ); - } - } -#if 0 - *rv = cwal_new_integer( args->engine, len ); - return *rv ? 0 : CWAL_RC_OOM; -#else - *rv = args->self; - return 0; -#endif - } -} - -/* -** Combined impl for Buffer.compress() and Buffer.uncompress(). If -** doCompress is true, it is the former, else the latter. -*/ -static int s2_cb_buffer_press( cwal_callback_args const * args, - cwal_value **rv, char doCompress ){ -#if !S2_ENABLE_ZLIKE - if(rv || doCompress){/*avoid unused param warning*/} - return s2_cb_throw(args, CWAL_RC_UNSUPPORTED, - "Compression support is not enabled in this build."); -#else - int rc; - cwal_value * arg = args->argc ? args->argv[0] : args->self; - cwal_buffer * buf = cwal_value_buffer_part(args->engine, arg); - char const isPressed = buf ? s2_buffer_is_compressed(buf) : 0; - if(!buf){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "%s is-not-a Buffer.", - args->argc ? "Argument" : "'this'"); - } - if(doCompress && isPressed){ - rc = 0; - }else if(!doCompress && !isPressed){ - rc = 0; - }else{ - rc = doCompress - ? s2_buffer_compress( args->engine, buf, buf ) - : s2_buffer_uncompress( args->engine, buf, buf ); - } - if(rc && CWAL_RC_OOM!=rc){ - rc = s2_cb_throw(args, rc, - "Buffer %s failed with code %d (%s).", - doCompress ? "compression" : "decompression", - rc, cwal_rc_cstr(rc)); - } - if(!rc) *rv = arg; - return rc; -#endif -} - -/** - Script usage: - - bufferInstance.compress() - - returns 'this'. - - compress(buffer) - - returns the given buffer. - - Throws if the arg/this is not/does not derive a cwal_buffer. -*/ -static int s2_cb_buffer_compress( cwal_callback_args const * args, - cwal_value **rv ){ - return s2_cb_buffer_press(args, rv, 1); -} - -/** - Script usage: - - bufferInstance.uncompress() - - returns 'this'. - - compress(buffer) - - returns the given buffer. - - Throws if the arg/this is not/does not derive a cwal_buffer. -*/ -static int s2_cb_buffer_uncompress( cwal_callback_args const * args, - cwal_value **rv ){ - return s2_cb_buffer_press(args, rv, 0); -} - -/** - Script signature: - - boolean bufferInstance.isCompressed([buffer]) - - If passed an argument, it evaluates that argument, - else it evaluates args->self. -*/ -static int s2_cb_buffer_is_compressed( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_buffer * buf = cwal_value_buffer_part(args->engine, - args->argc - ? args->argv[0] - : args->self); - if(buf){ - *rv = cwal_new_bool( s2_buffer_is_compressed(buf) ); - return 0; - }else{ - return s2_cb_throw(args, CWAL_RC_TYPE, - "%s is-not-a Buffer.", - args->argc ? "Argument" : "'this'"); - } -} - -static int s2_cb_buffer_uncompressed_size( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_buffer * buf = cwal_value_buffer_part(args->engine, - args->argc - ? args->argv[0] - : args->self); - if(!buf){ - return s2_cb_throw(args, CWAL_RC_TYPE, - "%s is-not-a Buffer.", - args->argc ? "Argument" : "'this'"); - }else{ - uint32_t const sz = s2_buffer_uncompressed_size(buf); - if((uint32_t)-1 == sz){ - *rv = cwal_value_undefined(); - return 0; - }else{ - *rv = cwal_new_integer(args->engine, (cwal_int_t)sz); - return *rv ? 0 : CWAL_RC_OOM; - } - } -} - - - -cwal_value * s2_prototype_buffer( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - proto = cwal_prototype_base_get( se->e, CWAL_TYPE_BUFFER ); - if(proto - || !s2_prototype_object(se) /* timing hack */ - ) return proto; -#define DERIVE_FROM_HASH 0 -#if DERIVE_FROM_HASH - /* the unit tests actually pass with this (after one - bug was fixed in the s2_get() family of funcs), - but the implications on the prototype chain currently - disturb me. */ - proto = cwal_new_hash_value(se->e, cwal_first_1000_primes()[13] - /* a bit higher than the property - count we will install */ ); -#else - proto = cwal_new_object_value(se->e); -#endif - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } -#if DERIVE_FROM_HASH - s2_hash_dot_like_object( proto, 1 ); -#endif - rc = cwal_prototype_base_set(se->e, CWAL_TYPE_BUFFER, proto ); - if(!rc) rc = s2_prototype_stash(se, "Buffer", proto); - if(rc) goto end; - assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_BUFFER)); - /* MARKER(("Setting up BUFFER prototype.\n")); */ - - - { - cwal_value * v = 0; -#if S2_ENABLE_ZLIKE - if(S2_INTERNAL_MINIZ){ - v = cwal_new_string_value(se->e, "miniz", 5); - }else if(S2_ENABLE_ZLIB){ - v = cwal_new_string_value(se->e, "zlib", 4); - }else{ - assert(!"Not possible unless the internal #ifs are wrong."); - v = cwal_value_undefined(); - } - if(!v) return NULL /* proto is already stashed - at the cwal level, not leaked. */; -#endif - cwal_value_ref(v); - rc = s2_set_with_flags(se, proto, "compression", 11, - v ? v : cwal_value_false(), - CWAL_VAR_F_CONST); - cwal_value_unref(v); - v = 0; - if(rc) goto end; -#if DERIVE_FROM_HASH - /* __typename doesn't resolve via hash entries b/c the callback - for it doesn't currently provide enough state for us to do - that. So we have to make sure it's in an Object-level - property... - */ - v = cwal_new_string_value(se->e, "buffer", 6); - if(!v){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_ref(v); - rc = cwal_prop_set_v(proto, se->cache.keyTypename, v ); - cwal_value_unref(v); - if(rc) goto end; -#endif - } -#undef DERIVE_FROM_HASH - - { - const s2_func_def funcs[] = { - S2_FUNC2("append", s2_cb_buffer_append), - S2_FUNC2("appendJSON", s2_cb_this_to_json_token), - S2_FUNC2("appendf", s2_cb_buffer_appendf), - S2_FUNC2("byteAt", s2_cb_buffer_byte_at), - S2_FUNC2("capacity", s2_cb_buffer_length_c), - S2_FUNC2("evalContents", s2_cb_strish_eval_contents), - S2_FUNC2("fill", s2_cb_buffer_fill), - S2_FUNC2("isEmpty", s2_cb_buffer_is_empty), - S2_FUNC2("length", s2_cb_buffer_length_u), - S2_FUNC2("lengthUtf8", s2_cb_buffer_length_utf8), - S2_FUNC2("operator<<", s2_cb_buffer_op_append), - S2_FUNC2("readFile", s2_cb_buffer_file_read), - S2_FUNC2("replace", s2_cb_buffer_replace), - S2_FUNC2("reserve", s2_cb_buffer_length_c) /* deprecated name for capacity(N) */, - S2_FUNC2("reset", s2_cb_buffer_reset), - S2_FUNC2("resize", s2_cb_buffer_resize), - S2_FUNC2("slice", s2_cb_buffer_slice), - S2_FUNC2("substr", s2_cb_str_substr), - S2_FUNC2("takeString", s2_cb_buffer_take_string), - S2_FUNC2("toString", s2_cb_buffer_to_string), - S2_FUNC2("writeFile", s2_cb_buffer_file_write), - S2_FUNC2("new", s2_cb_construct_buffer), - S2_FUNC2("isCompressed", s2_cb_buffer_is_compressed), - S2_FUNC2("compress", s2_cb_buffer_compress), - S2_FUNC2("uncompress", s2_cb_buffer_uncompress), - S2_FUNC2("uncompressedSize", s2_cb_buffer_uncompressed_size), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(rc) goto end; - else { - cwal_value * fv = 0; - s2_get(se, proto, "new", 3, &fv); - assert(fv && "we JUST put this in there!"); - rc = s2_ctor_method_set( se, proto, - cwal_value_get_function(fv) ); - } - } - - end: - return rc - ? NULL /* remember: proto is stashed at this point, so no leak. */ - : proto; -} - - -#undef MARKER -#undef ARGS_SE -#undef THIS_STRING -#undef THIS_BUFFER -#undef THIS_CSTRING -#undef S2_ENABLE_ZLIKE -/* end of file str.c */ -/* start of file t10n.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include -#include -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:\t",__FILE__,__LINE__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -/* const s2_byte_range s2_byte_range_empty = s2_byte_range_empty_m; */ -const s2_ptoker s2_ptoker_empty = s2_ptoker_empty_m; -const s2_ptoken s2_ptoken_empty = s2_ptoken_empty_m; -const s2_path_toker s2_path_toker_empty = s2_path_toker_empty_m; - -/** - S2_T10N_LCCACHE: set to 0 to disable the line-counting cache. It - appears to work, saving more than half of the CPU cycles in the s2 - unit tests, but is not yet battle-hardened (or even optimized). - - Sidebar: those tests are essentially a pathological case, as they - intentionally throw many exceptions in their testing duties, - exceptions being a major culprit in the line-counting rabbit hole, - along with function definitions. -*/ -#define S2_T10N_LCCACHE 1 -#if S2_T10N_LCCACHE -# define S2_T10N_LCCACHE_SLOT 0 -/* - S2_T10N_LCCACHE_SLOT == whether or not to use a predictable cache - slot placement algorithm which always places the same pos in the - same slot and orders slots by position. - - Some test metrics (20191229)... - - "Ir" values as reported by callgrind (in debug builds): - - cache size 20: 1=328M w/ 457 hits, 0=325M w/ 458 hits - cache size 13: 1=330M w/ 457 hits, 0=324.9M w/ 458 hits - cache size 10: 1=331M w/ 457 hits, 0=324.8M w/ 458 hits - cache size 9: 1=331M w/ 457 hits, 0=324.7M w/ 458 hits - - (Sidebar: those Ir values drop by about a 3rd in non-debug builds: - ~330M --> ~195M.) - - Summary: this approach doesn't perform quite as well, presumably - because closely-located entries take the same slot, and thus can't - benefit from potentially closer matches, especially on a large - tokenizer (where each slot spans a larger space). - - Maybe if we spread out the entries, instead of ordering them - strictly by position (use modulo instead of division to place - them)... - - cache size 20: 1=345M w/ 447 hits - cache size 13: 1=345M w/ 451 hits - cache size 10: 1=340M w/ 446 hits - - (Noting that the fallback handler when a hit is not found on the - first try is/was not explicitly optimized for this case: it would - essentially need to degrade to the same loop the circular queue - does.) - - Summary: simply using a small circular cache, discarding older - entries as we loop around, performs better in every regard. -*/ -static const int s2_ptoker_lccache_size = sizeof(s2_ptoker_empty._lcCache.lines) - /sizeof(s2_ptoker_empty._lcCache.lines[0]); -#else -# define S2_T10N_LCCACHE_SLOT 0 -#endif - -#if 0 && S2_T10N_LCCACHE -# define LCMARKER MARKER -#else -# define LCMARKER(PFEXP) (void)0 -#endif - -#if S2_T10N_LCCACHE && S2_T10N_LCCACHE_SLOT -# if 1 -/* Only a miniscule performance improvement. */ -# define s2_ptoker_lc_slot(PT,POS) (PT)->_lcCache.slotSize ? (int)(POS - (PT)->begin) / (PT)->_lcCache.slotSize : 0 -# else -/*inline (not C89)*/ static int s2_ptoker_lc_slot(s2_ptoker const * pt, char const * pos){ - assert(pos >= pt->begin); - assert(pos < pt->end); -# if 1 - return pt->_lcCache.slotSize ? (int)(pos - pt->begin) / pt->_lcCache.slotSize : 0; -# else - /* Performs worse, but the fallback lookup is not optimized for this case. */ - return pt->_lcCache.slotSize ? (int)(pos - pt->begin) % s2_ptoker_lccache_size : 0; -# endif -} -# endif -#endif - -#if S2_T10N_LCCACHE -static s2_ptoker_lccache_entry const * -s2_ptoker_lc_search( s2_ptoker const * pt, char const * pos ){ - if(pos>=pt->begin && posend){ - s2_ptoker_lccache const * const lcc = &pt->_lcCache; -#if S2_T10N_LCCACHE_SLOT - int slot = s2_ptoker_lc_slot(pt, pos); - assert(slot < s2_ptoker_lccache_size); - while( slot >= 0 ){ - s2_ptoker_lccache_entry const * const rc - = (s2_ptoker_lccache_entry const *)&lcc->lines[slot]; - if(rc->pos && rc->pos <= pos){ - LCMARKER(("%p Search slot %d, possible hit for %d, %d\n", - (void const *)pt, slot, rc->line, rc->col)); - return rc; - } - --slot; - } - return 0; -#else - s2_ptoker_lccache_entry const * rc = 0; - int i; - for( i = 0; i < s2_ptoker_lccache_size; ++i ){ - s2_ptoker_lccache_entry const * const m = (s2_ptoker_lccache_entry const *)&lcc->lines[i]; - if(!m->pos) break; - assert(m->pos>=pt->begin && m->posend); - if(m->pos<=pos){ - if(!rc || rc->pospos){ - rc = m; - LCMARKER(("%p Search slot %d, possible hit for %d, %d\n", - (void const *)pt, i, rc->line, rc->col)); - if(m->pos==pos) break; - } - } - } - return rc; -#endif - }else{ - return 0; - } -} -#else -# define s2_ptoker_lc_search(X,Y) 0 -#endif - -#if S2_T10N_LCCACHE -static void s2_ptoker_lc_cache( s2_ptoker const * pt, char const * pos, int line, int col ){ - if(pos>=pt->begin && posend){ - s2_ptoker_lccache * lcc = (s2_ptoker_lccache *)&pt->_lcCache; - static int once = 0; - s2_ptoker_lccache_entry * m; -#if S2_T10N_LCCACHE_SLOT - int const slot = s2_ptoker_lc_slot(pt, pos); - assert(slot < s2_ptoker_lccache_size); - m = (s2_ptoker_lccache_entry *)&lcc->lines[slot]; - /*if(m->pos && pos>=m->pos) return; wow, this adds 37M instructions - in these tests!*/ -#else - if(lcc->cursor==s2_ptoker_lccache_size){ - lcc->cursor = 0; - /* - For now, rather than do something clever, we'll just wrap - around, under the thinking that we're far more likely to hit - the counter for more recently-visited positions than older - ones. - - With a large enough cache size (60 entries), when tested against - the current (20191228) s2 amalgamated unit tests, this literally,... - - 1) Cuts the wall-clock time execution of the tests by *HALF*. - - 2) Reduces line-counting from the single most CPU-intensive - operation (~56% of CPU instructions) to something like 1.8% of - the instructions. - - 3) Cuts the overall number of CPU instructions by more than - half: from ~730-ish million to ~330M (values reported by - callgrind). - - What i'd *like* to do is divvy the cache up into slots and - pack cache entries into slots based on their position within - the tokenizer, so that we can quickly (O(1)), based on the - search pos, figure out which slot to use, but my brain - apparently isn't yet big enough to get that math right. - Update: tried that but it provides far fewer cache hits and - (curiously) more total CPU instructions (382M vs 324M in the - same test run. The problem seems to be that the grouping of - cache slots can easily lead to nearby neighbors not being - considered as near hits. - */ - } - m = (s2_ptoker_lccache_entry *)&lcc->lines[lcc->cursor]; -#endif - if(!once++){ - LCMARKER(("sizeof(s2_ptoker_lccache)=%d\n",(int)sizeof(s2_ptoker_lccache))); - } - m->line = line; - m->col = col; - m->pos = pos; -#if S2_T10N_LCCACHE_SLOT - LCMARKER(("%p Cached slot %d @ %d, %d\n", (void const *)pt, slot, m->line, m->col)); -#else - LCMARKER(("%p Cached slot %d @ %d, %d\n", (void const *)pt, lcc->cursor, m->line, m->col)); - ++lcc->cursor; -#endif - } -} -#endif - -int s2_ptoker_init_v2( cwal_engine * e, s2_ptoker * t, char const * src, cwal_int_t len, - uint32_t flags ){ - if(!t||!src) return CWAL_RC_MISUSE; - else{ - *t = s2_ptoker_empty; - t->e = e; - t->flags = flags; - /*memset(&t->_lcCache, 0, sizeof(t->_lcCache));*/ - if(len<0) len = cwal_strlen(src); - t->begin = src; - t->end = src+len; - memset(&t->_lcCache, 0, sizeof(t->_lcCache)); - s2_ptoker_reset(t); -#if S2_T10N_LCCACHE_SLOT - { - int ptl = (int)len; - int const cs = s2_ptoker_lccache_size; - assert(0==t->_lcCache.lines[cs-1].pos); - t->_lcCache.slotSize = (ptl + (ptl / cs)) / cs + 1; - LCMARKER(("%p slot size = %d\n", (void const *)t, t->_lcCache.slotSize)); - } -#endif - return 0; - } -} - -int s2_ptoker_init( s2_ptoker * t, char const * src, cwal_int_t len ){ - return s2_ptoker_init_v2( NULL, t, src, len, 0 ); -} - -void s2_ptoker_reset( s2_ptoker * t ){ - t->_pbToken = t->_errToken = t->token = s2_ptoken_empty; - s2_ptoken_begin_set(&t->token, s2_ptoker_begin(t)) - /* begin w/o an end set is our internal signal that we're - just starting off tokenization */; - t->currentLine = 1; - t->currentCol = 0; -} - -void s2_ptoker_finalize( s2_ptoker * pt ){ - *pt = s2_ptoker_empty; -} - -int s2_ptoker_sub_from_token( s2_ptoker const * parent, - s2_ptoken const * tok, - s2_ptoker * dest ){ - char const * begin; - char const * end; - int rc; -#if 0 - /* 20200107: this breaks stuff */ - if(!s2_ttype_is_group(tok->ttype)){ - return CWAL_RC_TYPE; - } -#endif - if(s2_ptoken_adjbegin(tok)){ - begin = s2_ptoken_adjbegin(tok); - end = s2_ptoken_adjend(tok); - }else{ - begin = s2_ptoken_begin(tok); - end = s2_ptoken_end(tok); - } - if(!begin || (begin>end)) return CWAL_RC_RANGE; - rc = s2_ptoker_init_v2( parent->e, dest, begin, - (cwal_int_t)(end - begin), 0 ); - if(!rc){ - dest->parent = parent; - assert(parent->e == dest->e); - } - return rc; -} - -int s2_ptoker_sub_from_toker( s2_ptoker const * parent, s2_ptoker * sub ){ - int const rc = s2_ptoker_sub_from_token( parent, &parent->token, sub ); - if(!rc){ - assert(sub->parent == parent); - assert(sub->e == parent->e); - /* There are multiple TODOs here if/when we add compiled token - chains. */ - } - return rc; -} - -s2_ptoker const * s2_ptoker_top_parent( s2_ptoker const * t ){ - while( t && t->parent ){ - t = t->parent; - } - return t; -} -char const * s2_ptoker_name_first( s2_ptoker const * t, cwal_size_t * len ){ - while(t && !t->name){ - t = t->parent; - } - if(t && len && t->name) *len = cwal_strlen(t->name); - return t ? t->name : 0; -} - -char const * s2_ptoker_err_pos_first( s2_ptoker const * t, - s2_ptoker const ** foundIn){ - while(t && !s2_ptoken_begin(&t->_errToken)){ - t = t->parent; - } - if(t && foundIn) *foundIn = t; - return t ? s2_ptoken_begin(&t->_errToken) : 0; -} - -char const * s2_ptoker_name_top( s2_ptoker const * t ){ - char const * n = t ? t->name : 0; - while(t && t->parent){ - t = t->parent; - if(t && t->name) n = t->name; - } - return n; -} - -int s2_ttype_is_junk( int ttype ){ - switch(ttype){ - case ' ': - case '\t': - case '\r': - case S2_T_Blank: - case S2_T_CommentC: - case S2_T_CommentCpp: - case S2_T_Whitespace: - case S2_T_Shebang: - case S2_T_UTFBOM: -#if 0 - case S2_T_EOL: - case S2_T_NL: -#endif - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_assignment( int ttype ){ - switch(ttype){ - case S2_T_OpAssign: - case S2_T_OpAssign3: - case S2_T_ArrayAppend: - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_assignment_combo( int ttype ){ - switch(ttype){ - case S2_T_OpPlusAssign: - case S2_T_OpPlusAssign3: - case S2_T_OpMinusAssign: - case S2_T_OpMinusAssign3: - case S2_T_OpModuloAssign: - case S2_T_OpModuloAssign3: - case S2_T_OpDivideAssign: - case S2_T_OpDivideAssign3: - case S2_T_OpMultiplyAssign: - case S2_T_OpMultiplyAssign3: - case S2_T_OpShiftLeftAssign: - case S2_T_OpShiftLeftAssign3: - case S2_T_OpShiftRightAssign: - case S2_T_OpShiftRightAssign3: - case S2_T_OpXOrAssign: - case S2_T_OpXOrAssign3: - case S2_T_OpOrAssign: - case S2_T_OpOrAssign3: - case S2_T_OpAndAssign: - case S2_T_OpAndAssign3: - return ttype; - default: - return 0; - } -} - - -int s2_ttype_is_group( int ttype ){ - switch(ttype){ - case S2_T_ParenGroup: - case S2_T_BraceGroup: - case S2_T_SquigglyBlock: - return ttype; - default: - return 0; - } -} - - -int s2_ttype_is_eof( int ttype ){ - switch(ttype){ - case S2_T_EOF: - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_eol( int ttype ){ - switch(ttype){ - case S2_T_EOF: - case S2_T_EOL: - case S2_T_CR: - case S2_T_NL: - /* case S2_T_CommentCpp: A //-style comment implies a newline, so - we might consider treating it as one (and consuming a trailing - newline, if any, as part of the token). - */ - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_eox( int ttype ){ - switch(ttype){ - case S2_T_EOF: - case S2_T_EOX: - case S2_T_Semicolon: - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_space( int ttype ){ - switch(ttype){ - case S2_T_NL: - case S2_T_CR: - case S2_T_EOL: - case S2_T_Whitespace: - /* case S2_T_CommentCpp: */ - /* case S2_T_CommentC: */ - case ' ': - case '\t': - case '\v': - case '\f': - return 1; - default: - return 0; - } -} - -int s2_ttype_is_object_keyable( int ttype ){ - switch(ttype){ - case S2_T_LiteralIntBin: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntOct: - case S2_T_LiteralIntHex: - case S2_T_LiteralDouble: - case S2_T_LiteralStringSQ: - case S2_T_LiteralStringDQ: - case S2_T_LiteralString: - case S2_T_Identifier: - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_int( int ttype ){ - switch(ttype){ - case S2_T_LiteralIntBin: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntOct: - case S2_T_LiteralIntHex: - return ttype; - default: - return 0; - } -} - -int s2_ttype_is_number( int ttype ){ - switch(ttype){ - case S2_T_LiteralIntBin: - case S2_T_LiteralIntDec: - case S2_T_LiteralIntOct: - case S2_T_LiteralIntHex: - case S2_T_LiteralDouble: - return ttype; - default: - return 0; - } -} - -int s2_ptoker_is_eof( s2_ptoker const * st ){ - return s2_ttype_is_eof(st->token.ttype); -} - -int s2_ptoker_is_eox( s2_ptoker const * st ){ - return s2_ttype_is_eox(st->token.ttype); -} - -int s2_ttype_short_circuits( int ttype ){ - switch( ttype ){ - case S2_T_OpOr: - case S2_T_OpAnd: - case S2_T_Question: - return ttype; - default: return 0; - } -} - - -int s2_ttype_is_identifier_prefix( int ttype ){ - switch( ttype ){ - case S2_T_OpIncr: - case S2_T_OpIncrPre: - case S2_T_OpIncrPost: - case S2_T_OpDecr: - case S2_T_OpDecrPre: - case S2_T_OpDecrPost: - return ttype; - default: return 0; - } -} - -int s2_ttype_may_precede_unary( int ttype ){ - switch(ttype){ -#if 1 - /* (...) and [...] are handled at the eval level, - and are simply treated as values. */ - case S2_T_ParenOpen: - case S2_T_ParenGroup: - /* No! case S2_T_ParenClose: */ - case S2_T_BraceOpen: - case S2_T_BraceGroup: - /* No! case S2_T_BraceClose: */ -#endif - case S2_T_Comma: - case S2_T_Semicolon: - case S2_T_Colon: - case S2_T_OpArrow: /* So x-> -1 can work. */ - case S2_T_OpNot: - case S2_T_OpAnd: - case S2_T_OpAssign: - case S2_T_OpAssign3: - /* case S2_T_OpArrow2: */ - case S2_T_OpOr: - case S2_T_OpOr3: - case S2_T_OpElvis: - case S2_T_OpPlus: - case S2_T_OpPlusUnary: - case S2_T_OpMinus: - case S2_T_OpMinusUnary: - case S2_T_OpMultiply: - case S2_T_OpDivide: - case S2_T_OpModulo: - case S2_T_OpOrBitwise: - case S2_T_OpNegateBitwise: - case S2_T_OpXOr: - case S2_T_CmpLT: - case S2_T_CmpGT: - case S2_T_CmpLE: - case S2_T_CmpGE: - case S2_T_CmpEq: - case S2_T_CmpNotEq: - case S2_T_CmpEqStrict: - case S2_T_CmpNotEqStrict: - case S2_T_OpContains: - case S2_T_OpNotContains: - case S2_T_OpAndBitwise: - case S2_T_KeywordThrow: - case S2_T_KeywordAssert: - case S2_T_KeywordReturn: - case S2_T_ArrayAppend /* b/c this op slides over its '=' part */: - return ttype; - default: - return s2_ttype_is_assignment_combo(ttype); - } -} - -/** - Returns true if ch is a legal char for "identifier" strings - (e.g. var/function names). Pass 1 as isStart for the first call and - 0 for subsequent calls for the same string. If isStart is true it - only allows alpha and underscore characters, otherwise is also - allows numbers. - - To support unicode identifers, this function accepts any character - with a value >0x7f (127d) as an identifier character. Any character - value <=0 is not an identifier character. -*/ -static char s2_is_id_char( int ch, char isStart ) { - if(ch<=0) return 0; - else if(ch>0x7f) return 1 /* TODO: filter to specific ranges? Or - at least remove any unicode - whitespace/blanks (are there such - things?). */; - else switch(ch){ - case '_': - case '$': - /* case '@': */ - return 1; - default: - return isStart - ? s2_is_alpha(ch) - : s2_is_alnum(ch); - } -} - -void s2_ptoker_putback_set( s2_ptoker * const st, - s2_ptoken const * const tok ){ - st->_pbToken = *tok; -} - -s2_ptoken const * s2_ptoker_putback_get( s2_ptoker const * const st ){ - return &st->_pbToken; -} - -char s2_ptoker_putback( s2_ptoker * st ){ - char const rc = s2_ptoken_begin(&st->_pbToken) ? 1 : 0; - if(rc){ - st->token = st->_pbToken; - st->_pbToken = s2_ptoken_empty; - /*s2_ptoker_token_set(st, &st->_pbToken);*/ - } - return rc; -} - - -int s2_ptoker_lookahead( s2_ptoker * st, s2_ptoken * tgt ){ - s2_ptoken const oldT = st->token; - s2_ptoken const oldP = st->_pbToken; - int const rc = s2_ptoker_next_token( st ); - *tgt = st->token; - st->token = oldT; - st->_nextToken = *tgt; - st->_pbToken = oldP; - return rc; -} - -/** - Internal impl for s2_ptoker_lookahead_xxx_pred(). - - matchMode: true means skip as long as the predicate matches. False - means skip until the predicate matches. i.e. true implements a 'while' - loop and false implements a 'do-until' loop. - - Returns 0 on success. -*/ -static int s2_ptoker_lookahead_pred( s2_ptoker * st, s2_ptoken * tgt, - s2_ttype_predicate_f pred, char matchMode ){ - s2_ptoken const oldT = st->token; - s2_ptoken const oldP = st->_pbToken; - int rc = 0; - assert(tgt); - while( !(rc = s2_ptoker_next_token( st )) - && (matchMode ? pred(st->token.ttype) : !pred(st->token.ttype)) - && !s2_ptoker_is_eof(st)){ - } - st->_nextToken = s2_ptoken_empty; - *tgt = st->token; - s2_ptoker_token_set(st, &oldT); - /*if(!rc) st->_nextToken = *tgt;*/ - st->_pbToken = oldP; - return rc; -} - -int s2_ptoker_lookahead_skip( s2_ptoker * st, s2_ptoken * tgt, - s2_ttype_predicate_f pred ){ - return s2_ptoker_lookahead_pred( st, tgt, pred, 1 ); -} - -int s2_ptoker_lookahead_until( s2_ptoker * st, s2_ptoken * tgt, - s2_ttype_predicate_f pred ){ - return s2_ptoker_lookahead_pred( st, tgt, pred, 0 ); -} - -void s2_ptoker_token_set( s2_ptoker * const st, s2_ptoken const * const t ){ - /* Need a temp in case t== one of st's tokens */ - s2_ptoken const tmp = *t; - st->_pbToken = st->token; - st->token = tmp; - st->_nextToken = s2_ptoken_empty; -} - -#if 0 -s2_ptoken const * s2_ptoker_token_get( s2_ptoker const * st ){ - return &st->token; -} -int s2_ptoker_ttype( s2_ptoker const * st ){ - return st->token.ttype; -} -#endif - -void s2_ptoker_next_token_set( s2_ptoker * pt, s2_ptoken const * tk ){ - pt->_nextToken = *tk; -} - -int s2_ptoker_next_token( s2_ptoker * t ){ - int rc = 0; - s2_ptoken * pt = &t->token; - char const * curpos = s2_ptoken_end(&t->token) - ? s2_ptoken_end(&t->token) /* for the 2nd and subsequent calls */ - : s2_ptoken_begin(&t->token) /* for the first run through this function */; - /** - TODO, but it'd be a lot of work: rewrite this to use a uint32_t - current character, rather than (char const *), to handle - UTF better from here. - */ - assert(curpos); - assert(curpos >= s2_ptoker_begin(t)); - assert(curpos <= s2_ptoker_end(t)); - t->_pbToken = t->token; - s2_ptoken_adjbegin_set(&t->token, 0); - s2_ptoken_adjend_set(&t->token, 0); - t->errMsg = 0; - t->_errToken = s2_ptoken_empty; - if(s2_ptoken_begin(&t->_nextToken)){ - /* Reminder to self: certain s2 logic now depends on _nextToken in - order to function, so we cannot simply rip it out. */ -#if 1 - /* - Optimization: use _nextToken if it is set. Some lookahead - ops set this when they end up not consuming the looked-ahead - (lookaheaded?) token. - */ -#if 0 - { - /* This actually saves right at 11.6k tokens in the ~3.3k code - lines of the s2 unit test suite (as of 202001). */ - static int counter = 0; - MARKER(("Using t->_nextToken for token type %s (%d)\n", - s2_ttype_cstr(t->_nextToken.ttype), ++counter)); - } -#endif - t->token = t->_nextToken; - t->_nextToken = s2_ptoken_empty; - return 0; -#else - /* Just to compare results in cachegrind... */ - curpos = s2_ptoken_begin(&t->_nextToken); -#endif - } - - if(!t->currentLine) t->currentLine = 1; - pt->line = t->currentLine; - pt->column = t->currentCol; - -#if 0 - /* i want something _like_ this, but it needs to behave properly - with multi-byte tokens. e.g. hitting EOF after a '+', while - checking for '++'. - */ -#define CHECKEND if(curpos>=t->end) { t->ttype = TT_EOF; return 0; }(void)0 -#else -#define CHECKEND (void)0 -#endif -#define BUMP(X) curpos+=(X); CHECKEND; t->currentCol+=(X)/*only correct when skipping ASCII!*/ -#define RETURN_ERR(RC,MSG) pt->ttype = S2_T_TokErr; t->errMsg = MSG; rc=RC; goto end -#define NEXT_LINE ++t->currentLine; t->currentCol = 0 - - if( curpos >= s2_ptoker_end(t) ) { - pt->ttype = S2_T_EOF; - s2_ptoken_begin_set(&t->token, s2_ptoker_end(t)); - s2_ptoken_end_set(&t->token, s2_ptoker_end(t)); - return 0; - } - - if(!t->parent && curpos == s2_ptoker_begin(t)){ - /* Check for some things which can only appear at the start of a - script. The if() condition above isn't quite 100% accurate, - considering how s2 uses sub-tokenizers at times, but it's - pretty close. - */ - if('#'==*curpos && '!'==*(curpos+1)){ - /* Workaround: strip shebang line from start of scripts. */ - for( ; ++curpos < s2_ptoker_end(t) - && *curpos - && ('\n' != *curpos); ++t->currentCol){} - ++curpos /* skip NL */; - if( curpos >= s2_ptoker_end(t) ) { - pt->ttype = S2_T_EOF; - s2_ptoken_begin_set(&t->token, s2_ptoker_end(t)); - s2_ptoken_end_set(&t->token, s2_ptoker_end(t)); - return 0; - } - s2_ptoken_end_set(&t->token, curpos); - t->token.ttype = S2_T_Shebang; - ++t->currentLine; - t->currentCol = 0; - return 0; - }else{ - /* Check for a UTF-8 BOM. */ - unsigned char const * ccp = (unsigned char const*)curpos; - if(0xEF==*ccp && 0xBB==ccp[1] && 0xBF==ccp[2]){ - curpos += 3; - s2_ptoken_end_set(&t->token, curpos); - t->token.ttype = S2_T_UTFBOM; - t->currentCol += 3; - return 0; - } - } - } - - pt->ttype = S2_T_INVALID; - s2_ptoken_begin_set(&t->token, curpos); - - switch(*curpos){ - case 0: - pt->ttype = S2_T_EOF; - /*This is a necessary exception to the bump-on-consume - rule.*/ - break; - case '\\': /* Treat \ as a continuation of a line */ - if(('\n'==*(curpos+1)) || (('\r'==*(curpos+1)) && ('\n'==*(curpos+2)))){ - pt->ttype = S2_T_Whitespace; - BUMP(('\r'==*(curpos+1)) ? 3 : 2); - NEXT_LINE; - }else{ - RETURN_ERR(CWAL_SCR_SYNTAX,"Unexpected backslash."); - } - break; - case '\r': - if('\n'==*(curpos+1)){ - pt->ttype = S2_T_EOL; - BUMP(2); - NEXT_LINE; - } - else{ - pt->ttype = S2_T_CR; - BUMP(1); - } - break; - case '\n': - pt->ttype = S2_T_NL; - BUMP(1); - NEXT_LINE; - break; - case ' ': - case '\t': - case '\v': - case '\f': - pt->ttype = *curpos /*S2_T_Blank*/; - BUMP(1); -#if 1 - while( curposttype = S2_T_Blank; - BUMP(1); - } -#endif - break; - case ':': /* colon or namespace */ - if( ':' == *(curpos+1) ){ - pt->ttype = S2_T_Colon2; - BUMP(2); - }else if( '=' == *(curpos+1) ){ - pt->ttype = S2_T_OpColonEqual; - BUMP(2); - }else{ - pt->ttype = S2_T_Colon; - BUMP(1); - } - break; - case '~': /* ~ or ~= */ - pt->ttype = *curpos; - BUMP(1); -#if 0 - if('=' == *curpos){ - pt->ttype = S2_T_NotYet; - BUMP(1); - } -#endif - break; - case '/': /* numeric division or C-style comment block */ - pt->ttype = S2_T_OpDivide /* *curpos */; - BUMP(1); - switch(*curpos){ - case (int)'=': - pt->ttype = S2_T_OpDivideAssign; - BUMP(1); - break; - case (int)'*':/* C-style comment block */ - pt->ttype = S2_T_CommentC; - BUMP(1); - do{ - while( curposttype = S2_T_CommentCpp; - while( curposend && *curpos && ('\n' != *curpos) ){ - BUMP(1); - } -#if 0 - if(curposend && *curpos){ - /* - Consume the EOL with the comment, since a CPP - comment always implies an EOL or EOF. - - Re-try this when other changes aren't pending - committing. - - This would probably (untested) break those few - constructs which optionally treat EOL as an EOX unless - we reported this comment as an EOL token. - */ - BUMP(1); - } -#endif - break; - } /* end post-/ checks */ - break; - case '"': - case '\'': /* read string literal */{ - char const quote = *curpos; - pt->ttype = ('"' == *curpos) - ? S2_T_LiteralStringDQ - : S2_T_LiteralStringSQ; - BUMP(1)/*leading quote*/; - while(curposcurrentCol (via BUMP()) - is off for non-ASCII characters. Need to loop over - this as UTF. - */ - if( (*curpos == '\\') ){ - /* consider next char to be escaped, but keep escape char */ - BUMP(1); - if(*curpos == 0){ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Unexpected EOF while tokenizing " - "backslash-escaped char in string literal."); - } - BUMP(1); - continue; - } - else if('\n'==*curpos){ - BUMP(1); - NEXT_LINE; - } - else { - BUMP(1); - } - } - if(s2_ptoker_end(t)<=curpos || *curpos != quote){ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Unexpected end of string literal."); - }else{ - BUMP(1)/*trailing quote*/; - } - break; - } /* end literal string */ - case '&': /* & or && or &= */ - pt->ttype = S2_T_OpAndBitwise; - BUMP(1); - switch(*curpos){ - case '&': - pt->ttype = S2_T_OpAnd; - BUMP(1); - break; - case '=': - pt->ttype = S2_T_OpAndAssign; - BUMP(1); - break; - } - break; - case '|': /* | or || or |= or ||| */ - pt->ttype = S2_T_OpOrBitwise; - BUMP(1); - switch(*curpos){ - case '|': - pt->ttype = S2_T_OpOr; - BUMP(1); - if( '|' == *curpos ){ - pt->ttype = S2_T_OpOr3; - BUMP(1); - } - break; - case '=': - pt->ttype = S2_T_OpOrAssign; - BUMP(1); - break; - } - break; - case '?': /* ? or ?: */ - pt->ttype = S2_T_Question; - BUMP(1); - if( ':' == *curpos ){ - pt->ttype = S2_T_OpElvis; - BUMP(1); - } - break; - case '^': /* ^ or ^= */ - pt->ttype = S2_T_OpXOr; - BUMP(1); - if( '=' == *curpos ){ - pt->ttype = S2_T_OpXOrAssign; - BUMP(1); - } - break; - case '+': /* + or ++ or += */{ - pt->ttype = S2_T_OpPlus; - BUMP(1); - switch(*curpos){ - case '+': /* ++ */ - pt->ttype = S2_T_OpIncr; - BUMP(1); - break; - case '=': /* += */ - pt->ttype = S2_T_OpPlusAssign; - BUMP(1); - break; - default: break; - } - break; - } - case '*': /* * or *= */ - pt->ttype = S2_T_OpMultiply; - BUMP(1); - switch(*curpos){ - case '=': - pt->ttype = S2_T_OpMultiplyAssign; - BUMP(1); - break; - case '/': - pt->ttype = S2_T_INVALID; - RETURN_ERR(CWAL_SCR_SYNTAX, - "Comment closer (*/) not inside a comment."); - break; - } - break; - case '-': /* - or -- or -= */{ - pt->ttype = S2_T_OpMinus; - BUMP(1); - switch( *curpos ){ - case '-': /* -- */ - pt->ttype = S2_T_OpDecr; - BUMP(1); - break; - case '=': - pt->ttype = S2_T_OpMinusAssign; - BUMP(1); - break; - case '>': - pt->ttype = S2_T_OpArrow; - BUMP(1); - break; - default: break; - } - break; - } - case '<': /* LT or << or <= or <<= or <<< */{ - pt->ttype = S2_T_CmpLT; - BUMP(1); - switch( *curpos ){ - case '<': /* tok= << */ { - pt->ttype = S2_T_OpShiftLeft; - BUMP(1); - switch(*curpos){ - case '=': /* <<= */ - pt->ttype = S2_T_OpShiftLeftAssign; - BUMP(1); - break; - case '<': /* <<< */ - pt->ttype = S2_T_HeredocStart; - BUMP(1); - break; - default: break; - } - break; - } - case '=': /* tok= <= */ - pt->ttype = S2_T_CmpLE; - BUMP(1); - break; - default: break; - } - break; - } - case '>': /* GT or >> or >= or >>= */{ - pt->ttype = S2_T_CmpGT; - BUMP(1); - switch(*curpos){ - case '>': /* >> */ - pt->ttype = S2_T_OpShiftRight; - BUMP(1); - if( '=' == *curpos ){/* >>= */ - pt->ttype = S2_T_OpShiftRightAssign; - BUMP(1); - } - break; - case '=': /* >= */ - pt->ttype = S2_T_CmpGE; - BUMP(1); - break; - default: break; - } - break; - } - case '=': /* = or == or === or =~ or => */ - pt->ttype = S2_T_OpAssign; - BUMP(1); - switch( *curpos ){ - case '~': /* -- */ - pt->ttype = S2_T_OpContains; - BUMP(1); - break; - case '=': - pt->ttype = S2_T_CmpEq; - BUMP(1); - if( '=' == *curpos ){ - pt->ttype = S2_T_CmpEqStrict; - BUMP(1); - } - break; - case '>': - pt->ttype = S2_T_OpArrow2; - BUMP(1); - break; - default: break; - } - break; - case '%': /* % or %= */ - pt->ttype = S2_T_OpModulo /* *curpos */; - BUMP(1); - if( '=' == *curpos ){ - pt->ttype = S2_T_OpModuloAssign; - BUMP(1); - } - break; - case '!': /* ! or != or !== or !~ */ - pt->ttype = S2_T_OpNot; - BUMP(1); - if( '~' == *curpos ){ - pt->ttype = S2_T_OpNotContains; - BUMP(1); - }else if( '=' == *curpos ){ - pt->ttype = S2_T_CmpNotEq; - BUMP(1); - if( '=' == *curpos ){ - pt->ttype = S2_T_CmpNotEqStrict; - BUMP(1); - } - } - break; - case '.': /* . or .. */ - pt->ttype = S2_T_OpDot; - BUMP(1); - if( '.' == *curpos ){ - pt->ttype = S2_T_OpDotDot; - BUMP(1); - } - break; - case '{': /* { */ - pt->ttype = S2_T_SquigglyOpen; - BUMP(1); - break; - case '}': /* } */ - pt->ttype = S2_T_SquigglyClose; - BUMP(1); - break; - case '0': /* 0 or hex or octal or binary literals */ - BUMP(1); - if(s2_ptoker_end(t) <= curpos){ - /* special case: 0 at the end of input. */ - pt->ttype = S2_T_LiteralIntDec; - break; - } - switch (*curpos)/* try hex or octal or binary */{ - case 'x': - case 'X':{/** hex digit. */ - int digitCount = 0; - BUMP(1); - while(curposttype = S2_T_LiteralIntHex; - if('_' == curpos[-1] /* last char was a separator */ - || s2_is_alnum(*curpos) - ){ - BUMP(1); /* make sure it shows up in the error string. */ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Malformed hexidecimal int literal."); - } - } - break; - } - case 'o':{/* try octal... */ - int digitCount = 0; - BUMP(1); - while(curposttype = S2_T_LiteralIntOct; - if('_' == curpos[-1] /* last char was a separator */ - || s2_is_alnum(*curpos) - ){ - BUMP(1); /* make sure it shows up in the error string. */ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Malformed octal int literal."); - } - } - break; - } - case 'b':{ - /* try binary... */ - int digitCount = 0; - BUMP(1); - while(*curpos){ - if(*curpos=='0' || *curpos=='1'){ - ++digitCount; - BUMP(1); - continue; - }else if('_'==*curpos){ - BUMP(1); - continue /* separator */; - } - break; - } - if(!digitCount){ - RETURN_ERR(CWAL_SCR_SYNTAX, - "No digits in binary int literal."); - } - /*else if(digitCount > CWAL_INT_T_BITS){ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Binary value is too large for this build."); - }*/ - else{ - pt->ttype = S2_T_LiteralIntBin; - if('_' == curpos[-1] /* last char was a separator */ - || s2_is_alnum(*curpos) - ){ - BUMP(1); /* make sure it shows up in the error string. */ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Malformed binary int literal."); - } - } - break; - }/*binary*/ - default: - if( *curpos && ( - s2_is_alnum(*curpos) - || (*curpos == '_')) - ) - { /* reject 12334x where x is alphanum or _ */ - BUMP(1); /* make sure it shows up in the error string. */ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Malformed numeric literal starts " - "with '0' but is neither octal nor " - "hex nor binary."); - } - if('.'==*curpos){ - if(!s2_is_digit(curpos[1])){ - /* curpos -= 1 */ /* Assume this might be NUMBER.FUNC(), - back up and leave it for next time */; - pt->ttype = S2_T_LiteralIntDec; - break; - } - BUMP(1); - while( s2_is_digit(*curpos) ){ - BUMP(1); - } - if('.'==*(curpos-1)){ - RETURN_ERR(CWAL_SCR_SYNTAX, - "Mis-terminated floating point value."); - } - pt->ttype = S2_T_LiteralDouble; - } - else { - pt->ttype = S2_T_LiteralIntDec; - /* A literal 0. This is okay. */ - } - break; - } - break; - case '1': case '2': case '3': - case '4': case '5': case '6': - case '7': case '8': case '9': /* integer or double literal. */{ - int gotSep = 0; - /* - Reminder to self: The 0x, 0o, and 0b formats all support '_' - characters as "visual separators" in their numeric literals. - Decimals values "should" do the same but... our downstream - code for parsing doubles does not handle those characters, - thus we prohibit the '_' separators in floating-point values - here. At _this_ point in the tokenization we don't yet know if - we're reading an integer or double, so we have to remember - whether we hit a '_' and error out if it turns out we're - parsing a double. Not elegant, but that's okay. - */ - BUMP(1); - while(curposttype = S2_T_LiteralDouble; - BUMP(1); - while(curposttype = S2_T_LiteralIntDec; - } - if( (curpos[-1] == '_' - /* disallow trailing separator for symmetry - with 0x/0o/0b literals. */) - || (curposttype = S2_T_At; - BUMP(1); - break; - }/* end of switch(*curpos) */ - - if(0==rc - && (curpos == s2_ptoken_begin(&t->token)) - && (S2_T_EOF != pt->ttype) ){ - /* keep trying... */ - int const idChars = s2_read_identifier2( curpos, s2_ptoker_end(t), - &curpos, t->flags ); - if( idChars ) /* identifier string */{ - t->currentCol += idChars; - pt->ttype = S2_T_Identifier; - } -#if 1 - /* i don't like this, but removing it causes errors i'm - not willing to chase down right now: a falsely-reported - CWAL_RC_OOM triggered via the eval loop. OTOH, if - we don't do this then we need to explicitly catch and tag all - 1-char operators. */ - else if( (*((unsigned char const *)curpos) > 0) - && (*((unsigned char const *)curpos) <= 127) ) { - pt->ttype = *curpos; - BUMP(1); - } -#endif - else { - assert(curpos == s2_ptoken_begin(&t->token)); - assert(S2_T_INVALID==pt->ttype); - } - } - - if(S2_T_INVALID == pt->ttype){ - unsigned char const cCh = (unsigned char)*curpos; - if( cCh > 0x7F ) - /* _hope_ for a UTF8 identifier string! */{ - int const chars = s2_read_identifier2( curpos, s2_ptoker_end(t), - &curpos, t->flags ); - if(chars){ - t->currentCol += chars; - pt->ttype = S2_T_Identifier; - }else{ - t->errMsg = "Don't know how to tokenize this."; - rc = CWAL_SCR_SYNTAX; - } - }else{ - pt->ttype = S2_T_TokErr; - /* MARKER(("byte=%02x\n", (unsigned char)*curpos)); */ - t->errMsg = "Don't know how to tokenize this."; - rc = CWAL_SCR_SYNTAX; - } - } - s2_ptoken_end_set(&t->token, (curpos > s2_ptoker_end(t)) - ? s2_ptoker_end(t) : curpos); -#undef CHECKEND -#undef BUMP -#undef RETURN_ERR -#undef NEXT_LINE - end: - if(rc){ - assert(t->errMsg); - t->_errToken = t->token; - } - return rc; -} - -int s2_ptoker_next_token_skip_junk( s2_ptoker * st ){ - int rc = 0; - do{ - rc = s2_ptoker_next_token(st); - }while(!rc && s2_ttype_is_junk(st->token.ttype)); - return rc; -} - -char s2_is_space( int ch ){ - switch(ch){ - case ' ': - case '\n': - case '\r': - case '\t': - case '\v': - case '\f': - return 1; - default: - return 0; - } -} - -char s2_is_blank( int ch ){ - switch(ch){ - case ' ': - case '\t': - return 1; - default: - return 0; - } -} - -char s2_is_digit( int ch ){ - return '0'<=ch && '9'>=ch; -} - -char s2_is_xdigit( int ch ){ - return ('a'<=ch && 'f'>=ch) - ? 1 - : (('A'<=ch && 'F'>=ch) - ? 1 - :s2_is_digit(ch)); -} - -char s2_is_octaldigit( int ch ){ - return ('0'<=ch && '7'>=ch); -} - -char s2_is_alpha( int ch ){ - return ('a'<=ch && 'z'>=ch) - ? 1 - : ('A'<=ch && 'Z'>=ch); -} - -char s2_is_alnum( int ch ){ - return s2_is_alpha(ch) ? 1 : s2_is_digit(ch); -} - -int s2_read_identifier2( char const * zPos, - char const * zEnd, - char const ** zIdEnd, - uint32_t flags ){ - unsigned char const * start = (unsigned char const *) zPos; - unsigned char const * pos = start; - unsigned char const * end = (unsigned char const *) zEnd; - unsigned char const * endChar = pos; - int ch; - int rc = 0; - int const allowDash = (S2_T10N_F_IDENTIFIER_DASHES & flags); - assert(zEnd>zPos); - for( ; pos < end; ){ - ch = cwal_utf8_read_char( pos, end, &endChar ); - if(endChar == pos) break; - else if(!s2_is_id_char(ch, (pos==start) ? 1 : 0)){ - if(!('-'==ch && allowDash)){ - break; - } - } - ++rc; - pos = endChar; - } - *zIdEnd = zPos + (pos - start); - return rc; -} - -int s2_read_identifier( char const * zPos, - char const * zEnd, - char const ** zIdEnd ){ - return s2_read_identifier2( zPos, zEnd, zIdEnd, 0 ); -} - -#if 0 -/* For later study. It's not clear whether this will help us - count lines more quickly but may have use in the core cwal - UTF8 APIs. -*/ -/** - Copyright (c) 2008-2009 Bjoern Hoehrmann - See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -*/ -#define UTF8_ACCEPT 0 -#define UTF8_REJECT 1 -static const uint8_t utf8d[] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 00..1f */ - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 20..3f */ - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40..5f */ - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 60..7f */ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, /* 80..9f */ - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* a0..bf */ - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* c0..df */ - 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, /* e0..ef */ - 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, /* f0..ff */ - 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, /* s0..s0 */ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, /* s1..s2 */ - 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, /* s3..s4 */ - 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, /* s5..s6 */ - 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 /* s7..s8 */ -}; -static uint32_t inline -bh_decode(uint32_t* state, uint32_t* codep, uint32_t byte) { - uint32_t type = utf8d[byte]; - - *codep = (*state != UTF8_ACCEPT) ? - (byte & 0x3fu) | (*codep << 6) : - (0xff >> type) & (byte); - - *state = utf8d[256 + *state*16 + type]; - return *state; -} -#endif - -int s2_count_lines( char const * src, char const * end_, - char const * pos_, - s2_linecol_t *line, s2_linecol_t *col ){ - s2_linecol_t ln = 1, c = 0; - unsigned char const * x = (unsigned char const *)src; - unsigned char const * const pos = (unsigned char const *)pos_; - unsigned char const * const end = (unsigned char const *)end_; - if((pos=end)) { - return CWAL_RC_RANGE; - } - /* profiling shows that cwal_utf8_read_char() is, by leaps and - bounds, the most oft-called func in this whole constellation, - largely due to this routine. We need a faster, file-local - multi-byte char skipping routine. - - And yet this function still takes more time than many other - functions doing much more work and called more often?!?!? - e.g. cwal_engine_vacuum(). Heh? - - Need to see if we can build line/column counting into s2_ptoker - and s2_ptoker_next_token(). (That failed because of how we - continually swap tokens in and out.) - - Misc. interesting UTF-decoding links with free code: - - https://gist.github.com/gorb314/7888804 - https://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - - */ -#if 1 - /** - Vaguely derived from: https://nullprogram.com/blog/2017/10/06/ - - A notably faster than the daemonology.net impl in the s2 - amalgamated unit tests. :-D i'm not sure which values in - callgrind are the significant ones, but in the test data i'm - working from, where this routine is the framework's hotspot, this - algo cuts total instructions(?) by about 25% compared to that - one. - */ - else{ - static const unsigned char lengths[32] = { - /* ^^^ changing this to int takes more CPU instructions. */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 - }; - while( x < pos ){ - if(*x > 127U) x += lengths[*x>>3]; - /* ^^^^ oddly, this loop is slower if we invert - this if/else into if(*x<128U)... */ - else{ - if('\n'==*x){ - ++ln; - c = -1; - } - ++x; - /* Reminder to future self: we spent more than an hour - trying out various combinations to reduce the instruction - count and tiny changes have huge effects. e.g., combining - the increment of x into: - - if('\n'==*x++){...} - - Increased the overall instruction count on the unit tests - by a whopping 33M. - */ - } - ++c; - } - } -#elif 1 - /** - Vaguely derived from: https://nullprogram.com/blog/2017/10/06/ - - We unfortunately can't use that algo as-is because it requires - that the input be padded to an increment of 4 bytes. - - After notable tinkering, a tiny tick faster than the - daemonology.net impl. - */ - else{ - static const char lengths[32] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 - }; - char i; - while(x>3])){ - if('\n'==x[0]){ - ++ln; - c = -1; - } - x+=i; - ++c; - } - } -#elif 0 - /* Derived from: - http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html - */ - for( ; (x < pos) && *x; ++x ){ - switch(*x){ - case '\n': - ++ln; - c = 0; - break; - /*case '\r': - if('\n'==x[1]){ - ++ln; - c = 0; - ++x; - } - else ++c; - break;*/ - default: - ++c; - if(*x>127U){ - switch(0xF0 & *x) { - case 0xF0: /* length 4 */ - x += 3; - break; - case 0xE0: /* length 3 */ - x+= 2; - break; - default: /* length 2 */ - x += 1; - break; - } - break; - } - } - } -#else - /* EXTREMELY slow impl... */ - else{ - unsigned char const * tail = end; - int ch; - for( ; (x < pos) && *x; ++x ){ - if('\n'==*x){ - ++ln; - c = 0; - }else if('\r'==*x && '\n'==x[1]){ - ++ln; - c = 0; - } - else { - ch = cwal_utf8_read_char( x, end, &tail ); - if(ch>0){ - assert(tail>x); - ++c; - x = tail - 1/* account for loop incr */; - } - } - } - } -#endif - if(line) *line = ln; - if(col) *col = c; - /* MARKER(("Updated line/col: %u, %u\n", ln, c )); */ - return 0; -} - -int s2_ptoker_count_lines( s2_ptoker const * pt, char const * pos, - s2_linecol_t * line, s2_linecol_t * col ){ - s2_ptoker const * top; - s2_ptoker const * prev; - int rc = 0; - s2_linecol_t li = 0, c = 0; - s2_linecol_t offLine = 0, offCol = 0; - s2_ptoker_lccache_entry const * cachedPos; - /* look for an already-calculated starting point... */ - for( prev = top = pt; top; prev = top, top = top->parent){ - if(top->lineOffset){ - offLine = top->lineOffset; - offCol = top->colOffset; - break; - } - } - if(!top) top = prev; - assert(top); - assert(s2_ptoker_begin(pt) >= s2_ptoker_begin(top) - && s2_ptoker_end(pt) <= s2_ptoker_end(top)); - assert(pos= s2_ptoker_begin(top)); - if(S2_T10N_F_LINEAR_TOKER & pt->flags){ - s2_ptoken const * et = s2_ptoken_begin(&pt->_errToken) - ? &pt->_errToken - : &pt->token; - li = et->line; - c = et->column; - /*MARKER(("Using S2_T10N_F_LINEAR_TOKER line %d, col %d\n", - li, c));*/ - } - if(!li && (cachedPos = s2_ptoker_lc_search(top, pos))){ - LCMARKER(("%p Got a hit: (%d, %d) distance from target pos=%d, from start=%d\n", - (void const *)top, cachedPos->line, cachedPos->col, - (int)(pos - cachedPos->pos), (int)(pos - s2_ptoker_begin(top)) - )); - if(cachedPos->pos==pos){ - li = cachedPos->line; - c = cachedPos->col; - }else{ - offLine = cachedPos->line; - offCol = cachedPos->col; - rc = s2_count_lines( cachedPos->pos, s2_ptoker_end(pt), pos, - &li, &c ); - assert(!rc); - } - }else if(!li){ - rc = s2_count_lines( s2_ptoker_begin(top), s2_ptoker_end(pt), - pos, &li, &c ); - assert(!rc); - } - if(!rc && (line || col)){ - if(offLine){ - /*MARKER(("top->lineOffset=%d, top->colOffset=%d\n", - top->lineOffset, top->colOffset));*/ - c += (1==li) ? offCol : 0; - li += offLine - 1; - } - if(line) *line = li; - if(col) *col = c; -#if S2_T10N_LCCACHE - /*LCMARKER(("%p Attempting to cache entry (%d, %d)\n", (void const *)top, li, c));*/ - s2_ptoker_lc_cache(top, pos, li, c); -#endif - } - return rc; -} - -int s2_ptoker_count_lines2( s2_ptoker const * pt, - s2_ptoken const * tok, - s2_linecol_t * line, s2_linecol_t * col ){ - s2_linecol_t li = 0, c = 0; - s2_ptoker const * top; - int rc = 0; - if(!tok) tok = &pt->token; - for( top = pt; top; top = top->parent){ - if(top->lineOffset) break; - } - if(!top) top = s2_ptoker_top_parent(pt); - if(tok->line){ - MARKER(("Using token-embedded line %d, col %d [%.*s] parent?=%d\n", - tok->line, tok->column, - (int)s2_ptoken_len(tok), s2_ptoken_begin(tok), - !!pt->parent)); - li = tok->line; - c = tok->column; - }else{ - rc = s2_count_lines( s2_ptoker_begin(top), s2_ptoker_end(pt), - s2_ptoken_begin(tok), &li, &c ); - } - if(!rc && (line || col)){ - /* Adjust the line/column for the line/colOffset - members of the parent tokenizer... */ - int oL = 0, oC = 0; - assert(top->lineOffset || !top->parent); - if(top->lineOffset){ - oL += top->lineOffset /* - 1 */; - if(1==li) oC += top->colOffset; - else oC = 0; - MARKER(("top->lineOffset=%d, top->colOffset=%d\n", - top->lineOffset, top->colOffset)); - MARKER(("adjusted line=%d=>%d, col=%d=>%d\n", - li, li+oL, c, c+oC)); - li += oL; - c += oC; - } - if(line) *line = li; - if(col) *col = c; - } - return rc; -} - -static int s2_hexbyte( int ch ){ - if(ch>='0' && ch<='9') return ch - '0'; - else if(ch>='a' && ch<='f') return ch - 'a' + 10; - else if(ch>='A' && ch<='F') return ch - 'A' + 10; - else return -1; -} - -static int s2_read_hex_bytes(unsigned char const *zPos, - unsigned int howMany, - unsigned int * rv ){ - unsigned int rc = 0; - int ch1, ch2; - unsigned int n = 0; - assert(!(howMany%2) && "howMany must be an even number!"); - assert(zPos && rv); - while(n=0) ? s2_hexbyte(*zPos++) : -1; - if(ch2<0) return -1; - rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2); - n += 2; - } - *rv = rc; - return (int)n; -} - -/** - TODO?: return a positive value (the length of the unescaped string) - on success and the negated byte offset of an error? -*/ -int s2_unescape_string( cwal_engine * e, - char const * _begin, - char const * _end, - cwal_buffer * dest ){ - cwal_size_t sz; - unsigned char const * begin = (unsigned char const*)_begin; - unsigned char const * end = (unsigned char const*)_end; - unsigned char const * p = begin; - unsigned char * out; - int check; - cwal_size_t oldUsed; - if(!e || !begin || !end || !dest) return CWAL_RC_MISUSE; - else if(endused; - sz = end - begin + 1 /*NUL*/; - check = cwal_buffer_reserve( e, dest, sz + oldUsed ); - if(check) return check; - out = dest->mem + oldUsed; - for( ; p < end; ++p ){ - switch(*p){ - case '\\': - if(end==p+1){ - /* stray slash at the end of the string. */ - *(out++) = '\\'; - break; - } - switch(*++p){ -#if 0 - case 0: - *(out++) = '\0'; - break; -#endif - case '0': *(out++) = 0; break; - case 'b': *(out++) = '\b'; break; - case 't': *(out++) = '\t'; break; - case 'n': *(out++) = '\n'; break; - case 'r': *(out++) = '\r'; break; - case 'f': *(out++) = '\f'; break; - case 'v': *(out++) = '\v'; break; - case 'u': - case 'U':{ - /* \uXXXX and \UXXXXXXXX */ - unsigned char uCount = 0; - unsigned int uChar = 0; - const unsigned ulen = 'u'==*p ? 4U : 8U; - uCount = s2_read_hex_bytes(p+1, ulen, &uChar); - if(uCount != ulen) return CWAL_RC_RANGE; - assert(0 == (uCount % 2)); - check = cwal_utf8_char_to_cstr(uChar, out, uCount); - if(check<0){ - /*MARKER(("Invalid UTF: \\%c seq: uCount=%d, uChar=0x%08x\n", - *p, (int)uCount, uChar));*/ - return CWAL_RC_RANGE - /* Invalid UTF */; - } - out += check /* length of the char, in bytes */; - p += uCount; - break; - } -#if 0 - default: - /* strip the backslash */ - *(out++) = *p; - break; -#else - case '\\': - case '"': - case '\'': - *(out++) = *p; - break; - default: - /* retain the backslash. The main benefit of this is that - libraries which take escaped strings don't have to be - double-escaped in many cases. This "problem" was - discovered in the POSIX regex extension, where we had to - double-escape parens and '+' before this change. */ - *(out++) = '\\'; - *(out++) = *p; - break; -#endif - } - break; - default: - *(out++) = *p; - break; - } - } - *out = 0; - dest->used = out - dest->mem; - assert(dest->used <= dest->capacity); - return 0; -} - - -char const * s2_ttype_cstr( int ttype ){ - switch((enum s2_token_types)ttype){ - /* ^^^^ reminder: that cast is so that gcc will warn - when i forget to update this function after adding - new entries to that enum. */ -#define CASE(TT) case TT: return &(#TT[5])/*==prefix strlen("S2_T_")*/ - CASE(S2_T_TokErr); - CASE(S2_T_INVALID); - CASE(S2_T_EOF); - CASE(S2_T_EOX); - CASE(S2_T_Tab); - CASE(S2_T_NL); - CASE(S2_T_VTab); - CASE(S2_T_FF); - CASE(S2_T_CR); - CASE(S2_T_EOL); - CASE(S2_T_Space); - CASE(S2_T_Blank); - CASE(S2_T_Whitespace); - CASE(S2_T_At); - CASE(S2_T_UTFBOM); - CASE(S2_T_OpNot); - CASE(S2_T_OpHash); - CASE(S2_T_Shebang); - CASE(S2_T_OpModulo); - CASE(S2_T_OpModuloAssign); - CASE(S2_T_OpModuloAssign3); - CASE(S2_T_OpAndBitwise); - CASE(S2_T_OpAnd); - CASE(S2_T_OpAndAssign); - CASE(S2_T_OpAndAssign3); - CASE(S2_T_ParenOpen); - CASE(S2_T_ParenGroup); - CASE(S2_T_ParenClose); - CASE(S2_T_OpMultiply); - CASE(S2_T_OpMultiplyAssign); - CASE(S2_T_OpMultiplyAssign3); - CASE(S2_T_OpPlus); - CASE(S2_T_OpPlusUnary); - CASE(S2_T_OpPlusAssign); - CASE(S2_T_OpPlusAssign3); - CASE(S2_T_OpIncr); - CASE(S2_T_OpIncrPre); - CASE(S2_T_OpIncrPost); - CASE(S2_T_Comma); - CASE(S2_T_RHSEval); - CASE(S2_T_OpMinus); - CASE(S2_T_OpMinusUnary); - CASE(S2_T_OpMinusAssign); - CASE(S2_T_OpMinusAssign3); - CASE(S2_T_OpDecr); - CASE(S2_T_OpDecrPre); - CASE(S2_T_OpDecrPost); - CASE(S2_T_OpDot); - CASE(S2_T_OpDotDot); - CASE(S2_T_OpDotLength); - CASE(S2_T_OpInherits); - CASE(S2_T_OpNotInherits); - CASE(S2_T_OpContains); - CASE(S2_T_OpNotContains); - CASE(S2_T_OpArrow); - CASE(S2_T_OpArrow2); - CASE(S2_T_OpDivide); - CASE(S2_T_OpDivideAssign); - CASE(S2_T_OpDivideAssign3); - CASE(S2_T_Colon); - CASE(S2_T_Colon2); - CASE(S2_T_OpColonEqual); - CASE(S2_T_Semicolon); - CASE(S2_T_CmpLT); - CASE(S2_T_CmpLE); - CASE(S2_T_OpShiftLeft); - CASE(S2_T_OpShiftLeftAssign); - CASE(S2_T_OpShiftLeftAssign3); - CASE(S2_T_HeredocStart); - CASE(S2_T_Heredoc); - CASE(S2_T_OpAssign); - CASE(S2_T_OpAssign3); - CASE(S2_T_OpAssignConst3); - CASE(S2_T_CmpEq); - CASE(S2_T_CmpNotEq); - CASE(S2_T_CmpEqStrict); - CASE(S2_T_CmpNotEqStrict); - CASE(S2_T_CmpGT); - CASE(S2_T_CmpGE); - CASE(S2_T_OpShiftRight); - CASE(S2_T_OpShiftRightAssign); - CASE(S2_T_OpShiftRightAssign3); - CASE(S2_T_Question); - CASE(S2_T_QDot); - CASE(S2_T_BraceOpen); - CASE(S2_T_Backslash); - CASE(S2_T_BraceClose); - CASE(S2_T_BraceGroup); - CASE(S2_T_ArrayAppend); - CASE(S2_T_OpXOr); - CASE(S2_T_OpXOrAssign); - CASE(S2_T_OpXOrAssign3); - CASE(S2_T_SquigglyOpen); - CASE(S2_T_SquigglyBlock); - CASE(S2_T_OpOrBitwise); - CASE(S2_T_OpOr); - CASE(S2_T_OpOr3); - CASE(S2_T_OpElvis); - CASE(S2_T_OpOrAssign); - CASE(S2_T_OpOrAssign3); - CASE(S2_T_SquigglyClose); - CASE(S2_T_OpNegateBitwise); - - CASE(S2_T_Literal__); - CASE(S2_T_LiteralInt); - CASE(S2_T_LiteralIntDec); - CASE(S2_T_LiteralIntHex); - CASE(S2_T_LiteralIntOct); - CASE(S2_T_LiteralIntBin); - CASE(S2_T_LiteralDouble); - CASE(S2_T_LiteralStringDQ); - CASE(S2_T_LiteralStringSQ); - CASE(S2_T_LiteralString); - CASE(S2_T_PropertyKey); - CASE(S2_T_Identifier); - CASE(S2_T_ValueTypes__); - CASE(S2_T_Value); - CASE(S2_T_Undefined); - CASE(S2_T_Null); - CASE(S2_T_False); - CASE(S2_T_True); - CASE(S2_T_Object); - CASE(S2_T_Array); - CASE(S2_T_Function); - - CASE(S2_T_Keyword__); - CASE(S2_T_KeywordAffirm); - CASE(S2_T_KeywordAssert); - CASE(S2_T_KeywordBREAKPOINT); - CASE(S2_T_KeywordBreak); - CASE(S2_T_KeywordCOLUMN); - CASE(S2_T_KeywordCatch); - CASE(S2_T_KeywordClass); - CASE(S2_T_KeywordConst); - CASE(S2_T_KeywordContinue); - CASE(S2_T_KeywordDefine); - CASE(S2_T_KeywordDefined); - CASE(S2_T_KeywordDelete); - CASE(S2_T_KeywordDo); - CASE(S2_T_KeywordEcho); - CASE(S2_T_KeywordEnum); - CASE(S2_T_KeywordEval); - CASE(S2_T_KeywordException); - CASE(S2_T_KeywordExit); - CASE(S2_T_KeywordFILE); - CASE(S2_T_KeywordFILEDIR); - CASE(S2_T_KeywordFalse); - CASE(S2_T_KeywordFatal); - CASE(S2_T_KeywordFor); - CASE(S2_T_KeywordForEach); - CASE(S2_T_KeywordFunction); - CASE(S2_T_KeywordIf); - CASE(S2_T_KeywordImport); - CASE(S2_T_KeywordInclude); - CASE(S2_T_KeywordInterface); - CASE(S2_T_KeywordIs); - CASE(S2_T_KeywordIsA); - CASE(S2_T_KeywordLINE); - CASE(S2_T_KeywordNameof); - CASE(S2_T_KeywordNew); - CASE(S2_T_KeywordNull); - CASE(S2_T_KeywordPragma); - CASE(S2_T_KeywordPrivate); - CASE(S2_T_KeywordProc); - CASE(S2_T_KeywordProtected); - CASE(S2_T_KeywordPublic); - CASE(S2_T_KeywordReturn); - CASE(S2_T_KeywordRefcount); - CASE(S2_T_KeywordS2Out); - CASE(S2_T_KeywordSRCPOS); - CASE(S2_T_KeywordScope); - CASE(S2_T_KeywordStatic); - CASE(S2_T_KeywordThrow); - CASE(S2_T_KeywordTrue); - CASE(S2_T_KeywordTry); - CASE(S2_T_KeywordTypeinfo); - CASE(S2_T_KeywordTypename); - CASE(S2_T_KeywordUndefined); - CASE(S2_T_KeywordUnset); - CASE(S2_T_KeywordUKWD); - CASE(S2_T_KeywordUsing); - CASE(S2_T_KeywordVar); - CASE(S2_T_KeywordWhile); - - CASE(S2_T_Comment__); - CASE(S2_T_CommentC); - CASE(S2_T_CommentCpp); - CASE(S2_T_Mark__); - CASE(S2_T_MarkVariadicStart); - CASE(S2_T_Misc__); - CASE(S2_T_Foo); - CASE(S2_T_comma_kludge_); -#undef CASE - } -#if 0 - /* Make ttypes matching characters in the ASCII range (even - unprintable ones) their own strings. Exception: the value 0 is - reserved for S2_T_INVALID, so we don't have a string for the - token \0. We don't really need these in scripts, though, and NOT - having these allows code to call this function to determine - whether or not it's a known-to-s2 token type. - */ - if(ttype>0 && ttype<=127){ - static char singleChars[127*2] = - {0} /* all characters in (1..127), each followed by a NUL */; - int ntype = ttype-1; - if(0 == singleChars[0]){ - int i = 0, n = 1; - for(; n <=127; ++n, ++i ){ - singleChars[i*2] = (char)n; - singleChars[i*2+1] = 0; - } - } - return &singleChars[ntype*2]; - } -#endif - return 0; -} - -int s2_ptoken_has_content( s2_ptoken const * tok ){ - char const * begin; - char const * end; - if(s2_ptoken_adjbegin(tok)){ - begin = s2_ptoken_adjbegin(tok); - end = s2_ptoken_adjend(tok); - }else{ - begin = s2_ptoken_begin(tok); - end = s2_ptoken_end(tok); - } - assert(end >= begin); - if(begin == end) return 0; - else{ - s2_ptoker pr = s2_ptoker_empty; - s2_ptoker_init( &pr, begin, (int)(end - begin) ); - do { - if( s2_ptoker_next_token(&pr) ) return 0; - }while( s2_ttype_is_junk(pr.token.ttype) ); - return s2_ttype_is_eof(pr.token.ttype) ? 0 : pr.token.ttype; - } -} - -/** - Expects digits to point to digLen bytes with the ASCII values '0' - or '1'. It parses them as a binary value. On success, if out is not - NULL then the final parsed result is written there, and it returns - 0. On error (non-binary-digit encountered) then CWAL_RC_RANGE is - returned and out is not modified. - - The digits may contain any number of '_' characters, which are - treated as "visual separators" (i.e. simply skipped). - - Minor achtung: this routine accepts, for simplicity, '_' as a - leading or trailing character but the core tokenizer does not. It - expects to be fed only inputs which have already been vetted by the - tokenizer. -*/ -static int s2_parse_binary_digits( char const * digits, - cwal_size_t digLen, - cwal_int_t * out ){ - cwal_uint_t i = 0 - /* use unsigned during parsing for guaranteed overflow behaviour */; - char const * end = digits + digLen; - assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t)); - if(end <= digits) return CWAL_RC_RANGE; - for( ; digits < end; ++digits ){ - switch(*digits){ - case '0': - i <<= 1; - continue; - case '1': - i = (i << 1) | 1; - continue; - case '_': - continue; - default: - return CWAL_RC_RANGE; - } - } - if(out) *out = (cwal_int_t)i; - return 0; -} - -/** - The octal-digit counterpart of s2_parse_binary_digits(), and works - identically except that it requires octal-digit input. -*/ -static int s2_parse_octal_digits( char const * digits, - cwal_size_t digLen, - cwal_int_t * out ){ - cwal_uint_t i = 0 - /* use unsigned during parsing for guaranteed overflow behaviour */; - char const * end = digits + digLen; - char digit; - assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t)); - if(end <= digits) return CWAL_RC_RANGE; - for( ; digits < end; ++digits ){ - digit = *digits; - switch(digit){ - case '0': case '1': case '2': - case '3': case '4': case '5': - case '6': case '7': - i = (i << 3) | (cwal_uint_t)(digit-'0'); - break; - case '_': - continue; - default: - return CWAL_RC_RANGE; - } - } - if(out) *out = (cwal_int_t)i; - return 0; -} - -/** - The decimal-digit counterpart of s2_parse_binary_digits(), and - works identically except that it requires decimal-digit input. -*/ -static int s2_parse_decimal_digits( char const * digits, - cwal_size_t digLen, - cwal_int_t * out ){ - cwal_uint_t i = 0 - /* use unsigned during parsing for guaranteed overflow behaviour */; - char const * end = digits + digLen; - char digit; - assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t)); - if(end <= digits) return CWAL_RC_RANGE; - for( ; digits < end; ++digits ){ - digit = *digits; - switch(digit){ - case '0': case '1': case '2': - case '3': case '4': case '5': - case '6': case '7': case '8': - case '9': - i = (i * 10) + (cwal_uint_t)(digit-'0'); - break; - case '_': - continue; - default: - return CWAL_RC_RANGE; - } - } - if(out) *out = (cwal_int_t)i; - return 0; -} - -/** - The hex-digit counterpart of s2_parse_binary_digits(), and works - identically except that it requires hex-digit input. -*/ -static int s2_parse_hex_digits( char const * digits, - cwal_size_t digLen, - cwal_int_t * out ){ - cwal_uint_t i = 0 - /* use unsigned during parsing for guaranteed overflow behaviour */; - char const * end = digits + digLen; - char digit; - assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t)); - if(end <= digits) return CWAL_RC_RANGE; - for( ; digits < end; ++digits ){ - digit = *digits; - switch(digit){ - case '0': - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - i = (i << 4) | (cwal_uint_t)(digit-'0'); - break; - case 'a': case 'b': case 'c': - case 'd': case 'e': case 'f': - i = (i << 4) | (cwal_uint_t)(10 + digit -'a'); - break; - case 'A': case 'B': case 'C': - case 'D': case 'E': case 'F': - i = (i << 4) | (cwal_uint_t)(10 + digit - 'A'); - break; - case '_': - continue; - default: - return CWAL_RC_RANGE; - } - } - if(out) *out = (cwal_int_t)i; - return 0; -} - -char s2_ptoken_parse_int( s2_ptoken const * t, cwal_int_t * rc ){ - switch(t->ttype){ - case S2_T_LiteralIntDec: - assert(s2_ptoken_end(t) > s2_ptoken_begin(t)); - return s2_parse_decimal_digits(s2_ptoken_begin(t), - s2_ptoken_len(t), - rc) - ? 0 : 1; - case S2_T_LiteralIntOct: - assert( s2_ptoken_len(t) > 2 /*"0o" prefix */); - return s2_parse_octal_digits( s2_ptoken_begin(t)+2, - s2_ptoken_len(t)-2, - rc ) - ? 0 - : 1; - case S2_T_LiteralIntHex: - assert( s2_ptoken_len(t) > 2 /*"0x" prefix */); - return s2_parse_hex_digits( s2_ptoken_begin(t)+2, - s2_ptoken_len(t)-2, - rc ) - ? 0 - : 1; - case S2_T_LiteralIntBin: - assert( s2_ptoken_len(t) > 2/*"0b" prefix*/); - return s2_parse_binary_digits( s2_ptoken_begin(t)+2, - s2_ptoken_len(t)-2, - rc ) - ? 0 - : 1; - default: - return 0; - } -} - -char s2_ptoken_parse_double( s2_ptoken const * t, cwal_double_t * dest ){ - if(t->ttype == S2_T_LiteralDouble){ - if(dest){ - return cwal_cstr_to_double( s2_ptoken_begin(t), - s2_ptoken_len(t), dest) - ? 0 : 1; - } - return 1; - }else{ - return 0; - } -} - -char s2_cstr_parse_int( char const * str, cwal_int_t slen, cwal_int_t * result ){ - s2_ptoker pr = s2_ptoker_empty; - int sign = 0; - cwal_int_t rv = 0; - if(slen<0) slen = (cwal_int_t)cwal_strlen(str); - while( slen && s2_is_space(*str) ){ - ++str; - --slen; - } - if(str && slen){ - if( '-' == *str || '+' == *str ){ - sign = ('-'==*str) ? -1 : 1; - ++str; - --slen; - } - } - while( slen && s2_is_space(*str) ){ - ++str; - --slen; - } - if(!slen) return 0; - s2_ptoker_init( &pr, str, slen ); - if(s2_ptoker_next_token(&pr) - || !s2_ptoken_parse_int( &pr.token, &rv ) - ){ - return 0; - }else{ - s2_ptoken_begin_set(&pr.token, s2_ptoken_end(&pr.token)); - s2_ptoken_end_set(&pr.token, s2_ptoker_end(&pr)); - if(s2_ptoken_has_content(&pr.token)){ - /* trailing junk */ - return 0; - }else{ - if(result) *result = sign < 0 ? -rv : rv; - return 1; - } - } -} - - -char s2_cstr_parse_double( char const * str, cwal_int_t slen, cwal_double_t * result ){ - s2_ptoker pr = s2_ptoker_empty; - int sign = 0; - cwal_double_t rv = 0; - if(slen<0) slen = (cwal_int_t)cwal_strlen(str); - while( slen && s2_is_space(*str) ){ - ++str; - --slen; - } - if(str && slen){ - if( '-' == *str || '+' == *str ){ - sign = ('-'==*str) ? -1 : 1; - ++str; - --slen; - } - } - while( slen && s2_is_space(*str) ){ - ++str; - --slen; - } - if(!slen) return 0; - s2_ptoker_init( &pr, str, slen ); - if(s2_ptoker_next_token(&pr) - || !s2_ptoken_parse_double( &pr.token, &rv)){ - return 0; - }else{ - s2_ptoken_begin_set(&pr.token, s2_ptoken_end(&pr.token)); - s2_ptoken_end_set(&pr.token, s2_ptoker_end(&pr)); - if(s2_ptoken_has_content(&pr.token)){ - /* trailing junk */ - return 0; - }else{ - if(result) *result = sign < 0 ? -rv : rv; - return 1; - } - } -} - -char const * s2_last_path_sep(char const * str, cwal_size_t slen ){ - unsigned char const * pos = (unsigned char const *)str; - unsigned char const * end = pos ? pos + slen : 0; - unsigned char const * prevSep = 0; - int sep = 0; - /** - TODO 20200828: we're doing this the hard way. We can simply start - at the end and backtrack for '/' or '\\'. - */ - if(!str || !slen) return 0; - while( pos && pos s2_ptoken_begin(token)) - ? (cwal_size_t)(s2_ptoken_end(token) - s2_ptoken_begin(token)) - : 0; -} -cwal_size_t s2_ptoken_len2( s2_ptoken const * token ){ - if(token->adjBegin && token->adjEnd && token->adjBegin<=token->adjEnd){ - return (cwal_size_t)(token->adjEnd - token->adjBegin); - } - return (token->begin && token->end > token->begin) - ? (cwal_size_t)(token->end - token->begin) - : 0; -} -#endif - -char const * s2_ptoken_cstr( s2_ptoken const * tok, - cwal_size_t * len ){ - if(len) *len = s2_ptoken_len(tok); - return s2_ptoken_begin(tok); -} - -char const * s2_ptoken_cstr2( s2_ptoken const * tok, - cwal_size_t * len ){ - if(len) *len = s2_ptoken_len2(tok); - return s2_ptoken_adjbegin(tok) - ? s2_ptoken_adjbegin(tok) - : s2_ptoken_begin(tok); -} - -cwal_value * s2_ptoken_is_tfnu( s2_ptoken const * tok ){ - cwal_value * rv = 0; - if(S2_T_Identifier == tok->ttype){ - cwal_size_t const tlen = s2_ptoken_len(tok); - switch(tlen){ - case 4: - if(0==cwal_compare_cstr("true", 4, - s2_ptoken_begin(tok), tlen)){ - rv = cwal_value_true(); - }else if(0==cwal_compare_cstr("null", 4, - s2_ptoken_begin(tok), tlen)){ - rv = cwal_value_null(); - } - break; - case 5: - if(0==cwal_compare_cstr("false", 5, - s2_ptoken_begin(tok), tlen)){ - rv = cwal_value_false(); - } - break; - case 9: - if(0==cwal_compare_cstr("undefined", 9, - s2_ptoken_begin(tok), tlen)){ - rv = cwal_value_undefined(); - } - break; - default: - break; - } - } - return rv; -} - -char const * s2_ptoker_err_pos( s2_ptoker const * pt ){ - char const * rc = s2_ptoker_err_pos_first(pt, 0); - if(!rc){ - if(s2_ptoken_begin(&pt->token) < s2_ptoken_end(&pt->token)){ - rc = s2_ptoken_begin(&pt->token); - } - if(!rc){ - if(s2_ptoken_begin(&pt->_pbToken) < s2_ptoken_end(&pt->_pbToken)){ - rc = s2_ptoken_begin(&pt->_pbToken); - } - if(!rc) rc = s2_ptoker_begin(pt); - } -#if 0 - if(rc>=s2_ptoker_end(pt) && s2_ptoker_end(pt)>s2_ptoker_begin(pt)) rc = s2_ptoker_end(pt)-1; -#endif - } - return rc; -} - - -void s2_ptoker_errtoken_set( s2_ptoker * const st, s2_ptoken const * const tok ){ - st->_errToken = tok ? *tok : s2_ptoken_empty; -} - -s2_ptoken const * s2_ptoker_errtoken_get( s2_ptoker const * st ){ - return &st->_errToken; -} - -char s2_ptoker_errtoken_has( s2_ptoker const * st ){ - return s2_ptoken_begin(&st->_errToken) - && s2_ptoken_begin(&st->_errToken) >= s2_ptoker_begin(st) - && s2_ptoken_begin(&st->_errToken) < s2_ptoker_end(st) - ? 1 : 0; -} - -char const * s2_ptoker_capture_cstr( s2_ptoker const * st, - cwal_size_t * len ){ - char const * rc = 0; - char const * begin = s2_ptoken_begin(&st->capture.begin); - char const * end = s2_ptoken_begin(&st->capture.end); - if(begin && begin>=s2_ptoker_begin(st) - && end>=begin && end<=s2_ptoker_end(st)){ - rc = begin; - if(len) *len = (cwal_size_t)(end - begin); - } - return rc; -} - -void s2_path_toker_init( s2_path_toker * pt, char const * path, cwal_int_t len ){ - *pt = s2_path_toker_empty; - pt->pos = pt->begin = path; - pt->end = pt->begin + ((len>=0) ? (cwal_size_t)len : cwal_strlen(path)); -} - -int s2_path_toker_next( s2_path_toker * pt, char const ** token, - cwal_size_t * len ){ - if(!pt->pos || pt->pos>=s2_ptoker_end(pt)) return CWAL_RC_RANGE; - else if(!pt->separators || !*pt->separators) return CWAL_RC_MISUSE; - else{ - char const * pos = pt->pos; - char const * t; - char const * sep; - for( sep = pt->separators; *sep; ++sep){ - if(*sep & 0x80) return CWAL_RC_MISUSE; - /* non-ASCII */ - } - for( ; posseparators; - *sep && *pos!=*sep; ++sep ){ - } - if(*pos == *sep) ++pos; - else break; - } - t = pos; - for( ; posseparators; - *sep && *pos!=*sep; ++sep ){ - } - if(*pos == *sep) break; - else ++pos; - } - pt->pos = pos; - if(pos>t){ - *token = t; - *len = (cwal_size_t)(pos - t); - return 0; - } - return CWAL_RC_NOT_FOUND; - } -} - -#if S2_T10N_LCCACHE -# undef s2_ptoker_lc_slot -#else -# undef s2_ptoker_lc_search -#endif -#undef MARKER -#undef S2_T10N_LCCACHE -#undef S2_T10N_LCCACHE_SLOT -#undef LCMARKER -/* end of file t10n.c */ -/* start of file tmpl.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include -/* #include */ -#include - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - -const s2_tmpl_opt s2_tmpl_opt_empty = s2_tmpl_opt_empty_m; - -int s2_tmpl_to_code( cwal_engine *e, cwal_buffer const * src, - cwal_buffer * dest, - s2_tmpl_opt const * opt){ - char const * pos; - char const * end; - char const * origin; - char const * tagOpenScript = ""; - int nCloseScript = 2; - char const * tagOpenValue = "<%"; - int nOpenValue = 2; - char const * tagCloseValue = "%>"; - int nCloseValue = 2; - char const * outAlias = "ⓞⓤⓣ" /*"➤➤"*/ /* "ᐊᐊ" */ /*"ᗛᗛ"*/ /* local var alias for OUT */; - char const * hDocPrefix; - char const * hDocId = "ⒺⓄⒻ" /*"ᐊᐊ"*/ /*heredoc ID used by output blocks */; - char const * errMsg = 0; - char const * openStart = 0; - char const * outsymLong = "TMPLOUT"; - int rc = 0; - int hIsOpen = 0 /* heredoc currently opened? */; - cwal_buffer hTag = cwal_buffer_empty; - if(!e || !src || !dest) return CWAL_RC_MISUSE; - if(!opt) opt = &s2_tmpl_opt_empty; - else{ - /* Set up custom options... */ - if(opt->outputSymbolInternal && *opt->outputSymbolInternal){ - outAlias = opt->outputSymbolInternal; - } - if(opt->outputSymbolPublic && *opt->outputSymbolPublic){ - outsymLong = opt->outputSymbolPublic; - } - if(opt->heredocId && *opt->heredocId) hDocId = opt->heredocId; - if(opt->tagCodeOpen && opt->tagCodeClose){ - tagOpenScript = opt->tagCodeOpen; - nOpenScript = (int)cwal_strlen(tagOpenScript); - tagCloseScript = opt->tagCodeClose; - nCloseScript = (int)cwal_strlen(tagCloseScript); - } - if(opt->tagValueOpen && opt->tagValueClose){ - tagOpenValue = opt->tagValueOpen; - nOpenValue = (int)cwal_strlen(tagOpenValue); - tagCloseValue = opt->tagValueClose; - nCloseValue = (int)cwal_strlen(tagCloseValue); - } - if(nOpenValue<1 /* all tags/ids need a length of at least 1 */ - || nCloseValue<1 - || nOpenScript<1 - || nCloseScript<1 - || cwal_strlen(outAlias)<1 - || cwal_strlen(outsymLong)<1 - || cwal_strlen(hDocId)<1 - ){ - return CWAL_RC_RANGE; - }else if(/* ensure that no two open/close tags are the same. */ - 0==strcmp(tagOpenValue,tagCloseValue) - || 0==strcmp(tagOpenValue,tagOpenScript) - || 0==strcmp(tagOpenValue,tagCloseScript) - || 0==strcmp(tagOpenScript,tagCloseScript) - || 0==strcmp(tagOpenScript,tagCloseValue) - ){ - return CWAL_RC_RANGE; - } - /* MARKER(("tagOpenValue=%.*s, tagCloseValue=%.*s, " - "tagOpenScript=%.*s, tagCloseScript=%.*s\n", - nOpenValue, tagOpenValue, nCloseValue, tagCloseValue, - nOpenScript, tagOpenScript, nCloseScript, tagCloseScript));*/ - } - origin = pos = (char const *)src->mem; - end = pos + src->used; - rc = cwal_buffer_printf(e, &hTag, "%s << <<<:", outAlias); - hDocPrefix = (char const *)hTag.mem; - if(!rc) rc = cwal_buffer_reserve(e, dest, src->capacity) - /* very possibly not enough, but we cannot guess - the approximate final size. */; - while(!rc){/* not a loop - we only use while() so we can break out - of this block */ - /** - Set up some bootstrapping stuff, e.g. make sure we have - outsymLong in place. - */ - rc = cwal_buffer_append( e, dest, "(", 1); - if(!rc - && (!opt->outputSymbolPublic || !*opt->outputSymbolPublic) - && !(opt->flags & S2_TMPL_ELIDE_TMPLOUT)){ - /* Set up local vars, if not done already */ - rc = cwal_buffer_printf( e, dest, - "(typeinfo(isdeclared %s) || var %s),\n" - "((undefined === %s) " - "&& (%s=s2out)),\n", - outsymLong, - outsymLong, - outsymLong, outsymLong); - } - if(rc) break; - else if(!opt->outputSymbolInternal || !*opt->outputSymbolInternal){ - rc = cwal_buffer_printf( e, dest, - "(typeinfo(islocal %s) || (var %s=%s))", - /* ^^^ can't be const b/c of operator<< rewrite below */ - outAlias, outAlias, outsymLong); - } - if(!rc) rc = cwal_buffer_append( e, dest, ");\n", 3); - if(rc) break; - /* - If TMPLOUT does not have operator<<, re-map outAlias to be a - local wrapper object which proxies operator<< to TMPLOUT. - */ - rc = cwal_buffer_printf( e, dest, - "(typeinfo(isfunction %s.'operator<<')" - " || (%s={prototype:null," - "'operator<<':" - "proc(a)using{$:%s}{return $(a),this}" - "}));\n", - outAlias, outAlias, outAlias) - /* reminder to self: we use an intermediary proc() here, instead - of directly using TMPLOUT as operator<< impl, to avoid any - problems wrt expectations of the value of 'this' in TMPLOUT's - body. */; - /* - The problem with operator<< is that it interferes with (has - unexpected side effects wrt) code in the <% %> blocks, e.g.: - - TMPLOUT << 1 << 2 - - Will not behave the same as TMPLOUT(1<<2). The -> op has - similar problems with unary values: <% -1 +2 %>. - - 20181118: we solve that by wrapping the output of value blocks - (but not code blocks) with an eval{...}. That minor - tokenization overhead is orders of magnitude smaller than what - we save by using operator<<, rather than a normal function - call, for the code block output. - */ - break; - } - if(rc) goto end; - if(1){ - /* - Scan the first bytes and ignore any leading spaces before an opening - capacity <= dest->used-2){ - rc = cwal_buffer_reserve(e, dest, dest->capacity * 3 / 2 ); - } - if(((end-pos) < nCloseScript) - && ((end-pos) < nCloseValue)) { - rc = cwal_buffer_append(e, dest, pos, end - pos); - break; - } - else if(((end-pos) > nOpenScript) /* (end-nCloseScript)){ - /* Allow nOpenValue) /* <% */ - && 0==memcmp(pos, tagOpenValue, nOpenValue)){ - /** - 20181118: we wrap VALUE blocks in eval{...} so that compound - expressions won't interfere with the operator<<. e.g.: <% - 1,2,3 %> would not behave inutitively without this. In - practice, <% %> blocks are generally small, so we don't have - an arbitrarily large re-tokenization performance hit here - (like we would if we did the same with blocks). - */ - openStart = pos; - pos += nOpenValue; - if(hIsOpen){ - rc = cwal_buffer_printf(e, dest, " %s;\n%s<< eval{ ", - hDocId, outAlias); - hIsOpen = 0; - }else if(openStart == origin - /*workaround for <% at starting pos. */){ - rc = cwal_buffer_printf(e, dest, "%s<< eval {", outAlias); - hIsOpen = 1; - } - for( ; !rc && (pos < end); ++pos ){ - if(pos > (end-nCloseValue)){ - rc = CWAL_RC_RANGE; - errMsg = "Missing closing tag."; - pos = openStart; - goto syntax_err; - } - else if(0==memcmp(pos, tagCloseValue, nCloseValue)){ - pos += nCloseValue-1 /*b/c outer loop will add one*/; - rc = cwal_buffer_printf(e, dest, - " }"/*<-- end eval block*/";\n%s%s ", - hDocPrefix, hDocId); - hIsOpen = 1; - break; - }else{ - rc = cwal_buffer_append(e, dest, pos, 1); - } - } - }else{ - /* Pipe the rest through as-is. TODO: optimize this - by scouting for the next '<' character before - pushing the output.*/ - if(!hIsOpen){ - rc = cwal_buffer_printf(e, dest, "\n%s%s ", - hDocPrefix, hDocId); - hIsOpen = 1; - } - if(!rc) rc = cwal_buffer_append(e, dest, pos, 1); - } - } - end: - if(!rc){ - if(hIsOpen){ - rc = cwal_buffer_printf(e, dest, " %s;", hDocId); - } - if(!rc) rc = cwal_buffer_printf(e, dest, - "\ntrue /*result value*/;\n"); - } - cwal_buffer_reserve(e, &hTag, 0); - return rc; - syntax_err: - { - s2_linecol_t line = 1, col = 0; - assert(errMsg); - assert(0 != rc); - s2_count_lines( (char const *)src->mem, - (char const *)src->mem+src->used, - pos, &line, &col); - return cwal_exception_setf(e, rc, - "tmpl syntax error at " - "line %d, col %d: %s", - line, col, errMsg); - } -} - -int s2_cb_tmpl_to_code( cwal_callback_args const * args, cwal_value ** rv ){ - int rc; - char const * src; - cwal_buffer srcB = cwal_buffer_empty; - cwal_buffer xbuf = cwal_buffer_empty; - cwal_engine * e = args->engine; - s2_tmpl_opt topt = s2_tmpl_opt_empty; - cwal_value * optObj = args->argc>1 - ? args->argv[1] - : 0; - src = args->argc - ? cwal_value_get_cstr(args->argv[0], &srcB.used) - : 0; - if(!src){ - return cwal_exception_setf(e, CWAL_RC_MISUSE, - "Expecting a string/buffer argument."); - }else if(!srcB.used){ - *rv = args->argv[0]; - return 0; - } - - if( optObj && cwal_props_can(optObj)){ - cwal_value const * v; - char const * vKey; -#define CHECK(SPROP,CPROP) \ - if((v = cwal_prop_get(optObj, SPROP, cwal_strlen(SPROP))) \ - && (vKey=cwal_value_get_cstr(v,0))) \ - topt.CPROP = vKey - CHECK("codeOpen", tagCodeOpen); - CHECK("codeClose", tagCodeClose); - CHECK("valueOpen", tagValueOpen); - CHECK("valueClose", tagValueClose); - CHECK("outputSymbol", outputSymbolPublic); -#undef CHECK - } - - if(0){ /* just testing, but this makes for a much shorter header! */ - topt.outputSymbolPublic = "s2out"; - /* topt.outputSymbolInternal = "print"; */ - /* topt.heredocId = "'~_~'"; */ - } -#if 0 - else if(cwal_scope_search(args->scope, -1, "TMPLOUT", 10, 0) - /* ^^^^^ Kind of a cheap (and not always valid) - optimization */){ - topt.flags |= S2_TMPL_ELIDE_TMPLOUT; - } -#endif - - srcB.mem = (unsigned char *)src; - /* Note that our misuse of srcB here, pointing it to foreign memory, - is only legal because it is used only in const contexts. */ - rc = s2_tmpl_to_code(e, &srcB, &xbuf, &topt); - if(!rc){ - cwal_buffer * rb = cwal_new_buffer(e, 0); - if(!rb) rc = CWAL_RC_OOM; - else{ - s2_buffer_swap(rb, &xbuf) - /* transfer ownership of memory, but be sure to restore - rb->self!!! */; - assert(!xbuf.self); - assert(!xbuf.mem); - assert(rb->self); - *rv = cwal_buffer_value(rb); - } - }else{ - switch(rc){ - case CWAL_RC_RANGE: - rc = cwal_exception_setf(args->engine, rc, - "Invalid tag configuration(?). " - "See the s2_tmpl_to_code() API " - "docs."); - break; - default: - break; - } - } - cwal_buffer_clear(e, &xbuf); - return rc; -} - - - -#undef MARKER -/* end of file tmpl.c */ -/* start of file time.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#include - -#if defined(S2_OS_WINDOWS) -#define S2_ENABLE_USLEEP 0 -#else -#define S2_ENABLE_USLEEP 1 -#endif - - -#if S2_ENABLE_USLEEP -# include /* strerror() */ -# include -#endif -#if defined(S2_OS_UNIX) -# include /* usleep(), possibly _POSIX_TIMERS */ -#endif - -#include - -#if defined(_POSIX_TIMERS) && _POSIX_TIMERS>0 && _POSIX_C_SOURCE>=199309L -# include -# include -# define S2_ENABLE_CLOCK_GETTIME 1 -#else -# define S2_ENABLE_CLOCK_GETTIME 0 -#endif - -#if 1 -#include -#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp -#else -#define MARKER(pfexp) (void)0 -#endif - - -/** - Internal impl of sleep()/mssleep(). delayMult must be: - - sleep(): 1000*1000 - mssleep(): 1000 - usleep(): 1 -*/ -static int s2_cb_sleep_impl(cwal_callback_args const * args, cwal_value ** rv, - unsigned int delayMult){ -#if !defined(S2_ENABLE_USLEEP) - return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED, - "usleep()-based routines not implemented " - "in this build."); -#else - if(!args->argc || !cwal_value_is_number(args->argv[0])){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting an integer argument."); - }else{ - int rc = -1; - cwal_int_t const t = cwal_value_get_integer(args->argv[0]); - if(0<=t){ - if( (rc = usleep( t * delayMult )) ){ - rc = cwal_exception_setf(args->engine, - s2_errno_to_cwal_rc(errno, CWAL_RC_ERROR), - "usleep() failed with errno %d: %s", - errno, strerror(errno)); - }else{ - *rv = cwal_value_undefined(); - } - }else{ - rc = cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Expecting a positive sleep time value."); - } - return rc; - } -#endif -} - -int s2_cb_sleep(cwal_callback_args const * args, cwal_value ** rv){ -#if !defined(S2_OS_UNIX) - return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED, - "sleep() not implemented in this build."); -#else - if(!args->argc || !cwal_value_is_number(args->argv[0])){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting an integer argument."); - }else{ - int rc = -1; - cwal_int_t const t = cwal_value_get_integer(args->argv[0]); - if(0<=t){ - rc = sleep( (unsigned int)t ); - *rv = cwal_new_integer(args->engine, rc); - rc = 0; - }else{ - rc = cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "Expecting a positive sleep time value."); - } - return rc; - } -#endif -} - -int s2_cb_mssleep(cwal_callback_args const * args, cwal_value ** rv){ - return s2_cb_sleep_impl( args, rv, 1000 ); -} - -int s2_cb_time( cwal_callback_args const * args, cwal_value **rv ){ - time_t const tm = time(NULL); - cwal_int_t const it = (cwal_int_t)tm; - if(tm != (time_t)it){ /* too large for our int type... */ - *rv = cwal_new_double(args->engine, (double)tm); - }else{ - *rv = cwal_new_integer(args->engine, it); - } - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_cb_mstime( cwal_callback_args const * args, cwal_value **rv ){ -#if !S2_ENABLE_CLOCK_GETTIME - (void)rv; - return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED, - "mstime() is not implemented in this build."); -#else - /* - Per https://upvoid.com/devblog/2014/05/linux-timers/ - CLOCK_REALTIME_COARSE has a resolution of within 1ms, which is - fine for what we're doing. - */ - struct timespec spec; - int rc; - rc = clock_gettime(CLOCK_REALTIME_COARSE, &spec); - if(rc){ - int const errNo = errno; - if(EINVAL==errNo){ - rc = CWAL_RC_UNSUPPORTED; - }else{ - rc = s2_errno_to_cwal_rc( errNo, CWAL_RC_ERROR ); - } - return cwal_exception_setf(args->engine, rc, - "clock_gettime() failed with errno %d (%s).", - errNo, strerror(errNo)); - }else{ - cwal_int_t const it = (cwal_int_t)spec.tv_sec * 1000; - if(spec.tv_sec != (time_t)(it/1000)){ - /* too large for our int type. Let's hope it will fit in a - double... */ - *rv = cwal_new_double(args->engine, (double)it - + spec.tv_nsec/1000000); - }else{ - *rv = cwal_new_integer(args->engine, (cwal_int_t)it - + spec.tv_nsec/1000000); - } - return *rv ? 0 : CWAL_RC_OOM; - } -#endif -} - -int s2_cb_strftime( cwal_callback_args const * args, cwal_value **rv ){ - time_t timt = -1; - enum {BufSize = 512}; - char buf[BufSize]; - struct tm * tim; - cwal_size_t sf; - char isLocal; - char const * fmt = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fmt){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a format string."); - } - if(args->argc>1){ -#if CWAL_INT_T_BITS > 32 - timt = (time_t)cwal_value_get_integer(args->argv[1]); -#else - /* Let's hope 48 bits is sufficient! */ - timt = (time_t)cwal_value_get_double(args->argv[1]); -#endif - } - isLocal = (args->argc>2) - ? cwal_value_get_bool(args->argv[2]) - : 0; - if(timt < 0){ - timt = time(NULL); - } - tim = isLocal ? localtime(&timt) : gmtime(&timt); - memset(buf, 0, BufSize); - sf = s2_strftime( buf, (cwal_size_t)BufSize, fmt, tim ); - if(!sf && *buf){ - return - cwal_exception_setf(args->engine, CWAL_RC_RANGE, - "strftime() failed. Possibly some " - "value is too long or some buffer " - "too short."); - } - assert(!buf[sf]); - *rv = cwal_new_string_value(args->engine, buf, sf); - return *rv ? 0 : CWAL_RC_OOM; -} - -int s2_install_time( s2_engine * se, cwal_value * tgt, - char const * name ){ - cwal_value * sub; - int rc; - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - else if(name && *name){ - sub = cwal_new_object_value(se->e); - if(!sub) return CWAL_RC_OOM; - cwal_value_ref(sub); - rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub); - cwal_value_unref(sub); - if(rc) return rc; - }else{ - sub = tgt; - } - - { - s2_func_def const funcs[] = { - S2_FUNC2("sleep", s2_cb_sleep), - S2_FUNC2("time", s2_cb_time), - S2_FUNC2("mssleep", s2_cb_mssleep), - S2_FUNC2("mstime", s2_cb_mstime), - S2_FUNC2("strftime", s2_cb_strftime), - s2_func_def_empty_m - }; - return s2_install_functions(se, sub, funcs, 0); - } -} - - -#undef MARKER -/* end of file time.c */ -/* start of file unix.c */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -#include - -#if defined(S2_OS_UNIX) -#include /* fork() and friends */ -#include /* pid_t, if not picked up via unistd.h */ -#include /* errno! */ -#include /* strerror() */ -#endif - -int s2_cb_fork( cwal_callback_args const * args, cwal_value **rv ){ -#if !defined(S2_OS_UNIX) - return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED, - "fork(2) is not available in this configuration."); -#else - pid_t pid; - char doReturn = 0; - uint16_t funcIndex = 0; - cwal_function * f = 0; - - if(args->argc>1){ - doReturn = cwal_value_get_bool(args->argv[0]); - funcIndex = 1; - } - if(!args->argc || - !(f = cwal_value_function_part(args->engine, - args->argv[funcIndex]))){ - return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, - "Expecting a Function argument."); - } - pid = fork(); - if(-1==pid){ - return (ENOMEM==errno) - ? CWAL_RC_OOM - : cwal_exception_setf(args->engine, - s2_errno_to_cwal_rc(errno, CWAL_RC_ERROR), - "Fork failed with errno %d (%s).", - errno, strerror(errno)); - } - else if(pid){ - /* Parent */ - *rv = cwal_new_integer(args->engine, (cwal_int_t)pid); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - /* Child */ - cwal_value * frv = NULL; - int frc = cwal_function_call(f, args->self, &frv, 0, NULL); - /* MARKER(("frc=%d/%s, doReturn=%d\n", frc, - cwal_rc_cstr(frc), doReturn)); */ - switch(frc){ - case CWAL_RC_OOM: - case CWAL_RC_EXIT: /* assume exit value was already set. */ - case CWAL_RC_FATAL: - break; - case 0: - if(doReturn){ - if(!frc) *rv = frv ? frv : cwal_value_undefined(); - }else{ - *rv = 0; - cwal_propagating_set(args->engine, - frv ? frv : cwal_value_undefined() - /* assertions in s2 require a value - to be propagated for RETURN/EXIT.*/); - } - break; - default: - break; - } - /* MARKER(("frc=%d/%s, doReturn=%d\n", frc, - cwal_rc_cstr(frc), doReturn)); */ - return (doReturn || frc) ? frc : CWAL_RC_EXIT; - } -#endif -} - -/* end of file unix.c */ -/* start of file strftime.c */ -/******************************************************************************* - * The Elm Mail System - $Revision: 1.3 $ $State: Exp $ - * - * Public-domain relatively quick-and-dirty implemenation of - * ANSI library routine for System V Unix systems. - * - * Arnold Robbins - * - ******************************************************************************* - * Bug reports, patches, comments, suggestions should be sent to: - * (Note: this routine is provided as is, without support for those sites that - * do not have strftime in their library) - * - * Syd Weinstein, Elm Coordinator - * elm@DSI.COM dsinc!elm - * - ******************************************************************************* - * $Log: strftime.c,v $ - * Revision 1.3 1993/10/09 19:38:51 smace - * Update to elm 2.4 pl23 release version - * - * Revision 5.8 1993/08/23 02:46:51 syd - * Test ANSI_C, not __STDC__ (which is not set on e.g. AIX). - * From: decwrl!uunet.UU.NET!fin!chip (Chip Salzenberg) - * - * Revision 5.7 1993/08/03 19:28:39 syd - * Elm tries to replace the system toupper() and tolower() on current - * BSD systems, which is unnecessary. Even worse, the replacements - * collide during linking with routines in isctype.o. This patch adds - * a Configure test to determine whether replacements are really needed - * (BROKE_CTYPE definition). The header file is now included - * globally through hdrs/defs.h and the BROKE_CTYPE patchup is handled - * there. Inclusion of was removed from *all* the individual - * files, and the toupper() and tolower() routines in lib/opt_utils.c - * were dropped. - * From: chip@chinacat.unicom.com (Chip Rosenthal) - * - * Revision 5.6 1993/08/03 19:20:31 syd - * Implement new timezone handling. New file lib/get_tz.c with new timezone - * routines. Added new TZMINS_USE_xxxxxx and TZNAME_USE_xxxxxx configuration - * definitions. Obsoleted TZNAME, ALTCHECK, and TZ_MINUTESWEST configuration - * definitions. Updated Configure. Modified lib/getarpdate.c and - * lib/strftime.c to use new timezone routines. - * From: chip@chinacat.unicom.com (Chip Rosenthal) - * - * Revision 5.5 1993/06/10 03:17:45 syd - * Change from TZNAME_MISSING to TZNAME - * From: Syd via request from Dan Blanchard - * - * Revision 5.4 1993/05/08 19:56:45 syd - * update to newer version - * From: Syd - * - * Revision 5.3 1993/04/21 01:42:23 syd - * avoid name conflicts on min and max - * - * Revision 5.2 1993/04/16 04:29:34 syd - * attempt to bsdize a bit strftime - * From: many via syd - * - * Revision 5.1 1993/01/27 18:52:15 syd - * Initial checkin of contributed public domain routine. - * This routine is provided as is and not covered by Elm Copyright. - ******************************************************************************/ - -/* - * strftime.c - * - * Public-domain relatively quick-and-dirty implementation of - * ANSI library routine for System V Unix systems. - * - * It's written in old-style C for maximal portability. - * However, since I'm used to prototypes, I've included them too. - * - * If you want stuff in the System V ascftime routine, add the SYSV_EXT define. - * For extensions from SunOS, add SUNOS_EXT. - * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE. - * For complete POSIX semantics, add POSIX_SEMANTICS. - * - * The code for %c, %x, and %X is my best guess as to what's "appropriate". - * This version ignores LOCALE information. - * It also doesn't worry about multi-byte characters. - * So there. - * - * This file is also shipped with GAWK (GNU Awk), gawk specific bits of - * code are included if GAWK is defined. - * - * Arnold Robbins - * January, February, March, 1991 - * Updated March, April 1992 - * Updated May, 1993 - * - * Fixes from ado@elsie.nci.nih.gov - * February 1991, May 1992 - * Fixes from Tor Lillqvist tor@tik.vtt.fi - * May, 1993 - * - * Fast-forward to July 2013... - * - * stephan@wanderinghorse.net copied these sources from: - * - * ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/elm/lib/strftime.c - * - * And made the following changes: - * - * Removed ancient non-ANSI decls. Added some headers to get it to - * compile for me. Renamed functions to fit into my project. Replaced - * hard tabs with 4 spaces. Added #undefs for all file-private #defines - * to safe-ify inclusion from/with other files. - * -*/ - - -/* #include */ -#include /* strchr() and friends */ -#include /* sprintf() */ -#include /* toupper(), islower() */ - -#define HAVE_GET_TZ_NAME 0 -#if HAVE_GET_TZ_NAME -extern char *get_tz_name(); -#endif - -#ifndef BSD -extern void tzset (void); -#endif -static int weeknumber (const struct tm *timeptr, int firstweekday); - -/* defaults: season to taste */ -#define SYSV_EXT 1 /* stuff in System V ascftime routine */ -#define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ -#define POSIX2_DATE 1 /* stuff in Posix 1003.2 date command */ -#define VMS_EXT 1 /* include %v for VMS date format */ - -#if 0 -#define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ -#endif - -#ifdef POSIX_SEMANTICS -#include /* malloc() and friends */ -#endif - -#if defined(POSIX2_DATE) -#if ! defined(SYSV_EXT) -#define SYSV_EXT 1 -#endif -#if ! defined(SUNOS_EXT) -#define SUNOS_EXT 1 -#endif -#endif - -#if defined(POSIX2_DATE) -static int iso8601wknum(const struct tm *timeptr); -#endif - -#undef strchr /* avoid AIX weirdness */ - -/* minimum --- return minimum of two numbers */ -#define minimum(A,B) ((A)<(B) ? (A) : (B)) -/* maximum --- return maximum of two numbers */ -#define maximum(A,B) ((A)>(B) ? (A) : (B)) - -#define range(low, item, hi) maximum(low, minimum(item, hi)) - -/* strftime --- produce formatted time */ - -cwal_midsize_t -s2_strftime(char *s, cwal_midsize_t maxsize, - const char *format, const struct tm *timeptr) -{ - char *endp = s + maxsize; - char *start = s; - char tbuf[100]; - int i; - static short first = 1; -#ifdef POSIX_SEMANTICS - static char *savetz = NULL; - static int savetzlen = 0; - char *tz; -#endif /* POSIX_SEMANTICS */ - - /* various tables, useful in North America */ - static char *days_a[] = { - "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat", - }; - static char *days_l[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", - }; - static char *months_a[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - }; - static char *months_l[] = { - "January", "February", "March", "April", - "May", "June", "July", "August", "September", - "October", "November", "December", - }; - static char *ampm[] = { "AM", "PM", }; - - if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0) - return 0; - - if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) - return 0; - -#ifndef POSIX_SEMANTICS - if (first) { - tzset(); - first = 0; - } -#else /* POSIX_SEMANTICS */ - tz = getenv("TZ"); - if (first) { - if (tz != NULL) { - int tzlen = strlen(tz); - - savetz = (char *) malloc(tzlen + 1); - if (savetz != NULL) { - savetzlen = tzlen + 1; - strcpy(savetz, tz); - } - } - tzset(); - first = 0; - } - /* if we have a saved TZ, and it is different, recapture and reset */ - if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { - i = strlen(tz) + 1; - if (i > savetzlen) { - char * newtz = (char *) realloc(savetz, i); - if(newtz){ - savetz = newtz; - savetzlen = i; - strcpy(savetz, tz); - }else{ - /* ... just keep the old one! */ - } - } else - strcpy(savetz, tz); - tzset(); - } -#endif /* POSIX_SEMANTICS */ - - for (; *format && s < endp - 1; format++) { - tbuf[0] = '\0'; - if (*format != '%') { - *s++ = *format; - continue; - } - again: - switch (*++format) { - case '\0': - *s++ = '%'; - goto out; - - case '%': - *s++ = '%'; - continue; - - case 'a': /* abbreviated weekday name */ - if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) - strcpy(tbuf, "?"); - else - strcpy(tbuf, days_a[timeptr->tm_wday]); - break; - - case 'A': /* full weekday name */ - if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) - strcpy(tbuf, "?"); - else - strcpy(tbuf, days_l[timeptr->tm_wday]); - break; - -#ifdef SYSV_EXT - case 'h': /* abbreviated month name */ -#endif - case 'b': /* abbreviated month name */ - if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) - strcpy(tbuf, "?"); - else - strcpy(tbuf, months_a[timeptr->tm_mon]); - break; - - case 'B': /* full month name */ - if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) - strcpy(tbuf, "?"); - else - strcpy(tbuf, months_l[timeptr->tm_mon]); - break; - - case 'c': /* appropriate date and time representation */ - sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d", - days_a[range(0, timeptr->tm_wday, 6)], - months_a[range(0, timeptr->tm_mon, 11)], - range(1, timeptr->tm_mday, 31), - range(0, timeptr->tm_hour, 23), - range(0, timeptr->tm_min, 59), - range(0, timeptr->tm_sec, 61), - timeptr->tm_year + 1900); - break; - - case 'd': /* day of the month, 01 - 31 */ - i = range(1, timeptr->tm_mday, 31); - sprintf(tbuf, "%02d", i); - break; - - case 'H': /* hour, 24-hour clock, 00 - 23 */ - i = range(0, timeptr->tm_hour, 23); - sprintf(tbuf, "%02d", i); - break; - - case 'I': /* hour, 12-hour clock, 01 - 12 */ - i = range(0, timeptr->tm_hour, 23); - if (i == 0) - i = 12; - else if (i > 12) - i -= 12; - sprintf(tbuf, "%02d", i); - break; - - case 'j': /* day of the year, 001 - 366 */ - sprintf(tbuf, "%03d", timeptr->tm_yday + 1); - break; - - case 'm': /* month, 01 - 12 */ - i = range(0, timeptr->tm_mon, 11); - sprintf(tbuf, "%02d", i + 1); - break; - - case 'M': /* minute, 00 - 59 */ - i = range(0, timeptr->tm_min, 59); - sprintf(tbuf, "%02d", i); - break; - - case 'p': /* am or pm based on 12-hour clock */ - i = range(0, timeptr->tm_hour, 23); - if (i < 12) - strcpy(tbuf, ampm[0]); - else - strcpy(tbuf, ampm[1]); - break; - - case 'S': /* second, 00 - 61 */ - i = range(0, timeptr->tm_sec, 61); - sprintf(tbuf, "%02d", i); - break; - - case 'U': /* week of year, Sunday is first day of week */ - sprintf(tbuf, "%d", weeknumber(timeptr, 0)); - break; - - case 'w': /* weekday, Sunday == 0, 0 - 6 */ - i = range(0, timeptr->tm_wday, 6); - sprintf(tbuf, "%d", i); - break; - - case 'W': /* week of year, Monday is first day of week */ - sprintf(tbuf, "%d", weeknumber(timeptr, 1)); - break; - - case 'x': /* appropriate date representation */ - sprintf(tbuf, "%s %s %2d %d", - days_a[range(0, timeptr->tm_wday, 6)], - months_a[range(0, timeptr->tm_mon, 11)], - range(1, timeptr->tm_mday, 31), - timeptr->tm_year + 1900); - break; - - case 'X': /* appropriate time representation */ - sprintf(tbuf, "%02d:%02d:%02d", - range(0, timeptr->tm_hour, 23), - range(0, timeptr->tm_min, 59), - range(0, timeptr->tm_sec, 61)); - break; - - case 'y': /* year without a century, 00 - 99 */ - i = timeptr->tm_year % 100; - sprintf(tbuf, "%d", i); - break; - - case 'Y': /* year with century */ - sprintf(tbuf, "%d", 1900 + timeptr->tm_year); - break; - -#if HAVE_GET_TZ_NAME - case 'Z': /* time zone name or abbrevation */ - strcpy(tbuf, get_tz_name(timeptr)); - break; -#endif - -#ifdef SYSV_EXT - case 'n': /* same as \n */ - tbuf[0] = '\n'; - tbuf[1] = '\0'; - break; - - case 't': /* same as \t */ - tbuf[0] = '\t'; - tbuf[1] = '\0'; - break; - - case 'D': /* date as %m/%d/%y */ - s2_strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); - break; - - case 'e': /* day of month, blank padded */ - sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31)); - break; - - case 'r': /* time as %I:%M:%S %p */ - s2_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr); - break; - - case 'R': /* time as %H:%M */ - s2_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr); - break; - - case 'T': /* time as %H:%M:%S */ - s2_strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr); - break; -#endif - -#ifdef SUNOS_EXT - case 'k': /* hour, 24-hour clock, blank pad */ - sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23)); - break; - - case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ - i = range(0, timeptr->tm_hour, 23); - if (i == 0) - i = 12; - else if (i > 12) - i -= 12; - sprintf(tbuf, "%2d", i); - break; -#endif - - -#ifdef VMS_EXT - case 'v': /* date as dd-bbb-YYYY */ - sprintf(tbuf, "%2d-%3.3s-%4d", - range(1, timeptr->tm_mday, 31), - months_a[range(0, timeptr->tm_mon, 11)], - timeptr->tm_year + 1900); - for (i = 3; i < 6; i++) - if (islower((int)tbuf[i])) - tbuf[i] = toupper((int)tbuf[i]); - break; -#endif - - -#ifdef POSIX2_DATE - case 'C': - sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100); - break; - - - case 'E': - case 'O': - /* POSIX locale extensions, ignored for now */ - goto again; - - case 'V': /* week of year according ISO 8601 */ -#if defined(GAWK) && defined(VMS_EXT) - { - extern int do_lint; - extern void warning(); - static int warned = 0; - - if (! warned && do_lint) { - warned = 1; - warning( - "conversion %%V added in P1003.2/11.3; for VMS style date, use %%v"); - } - } -#endif - sprintf(tbuf, "%d", iso8601wknum(timeptr)); - break; - - case 'u': - /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ - sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 : - timeptr->tm_wday); - break; -#endif /* POSIX2_DATE */ - default: - tbuf[0] = '%'; - tbuf[1] = *format; - tbuf[2] = '\0'; - break; - } - i = strlen(tbuf); - if (i){ - if (s + i < endp - 1) { - strcpy(s, tbuf); - s += i; - } else return 0; - } - } -out: - if (s < endp && *format == '\0') { - *s = '\0'; - return (cwal_midsize_t)(s - start); - } else { - return 0; - } -} - -#ifdef POSIX2_DATE -/* iso8601wknum --- compute week number according to ISO 8601 */ - -static int -iso8601wknum(const struct tm *timeptr) -{ - /* - * From 1003.2 D11.3: - * If the week (Monday to Sunday) containing January 1 - * has four or more days in the new year, then it is week 1; - * otherwise it is week 53 of the previous year, and the - * next week is week 1. - * - * ADR: This means if Jan 1 was Monday through Thursday, - * it was week 1, otherwise week 53. - */ - - int simple_wknum, jan1day, diff, ret; - - /* get week number, Monday as first day of the week */ - simple_wknum = weeknumber(timeptr, 1) + 1; - - /* - * With thanks and tip of the hatlo to tml@tik.vtt.fi - * - * What day of the week does January 1 fall on? - * We know that - * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == - * (timeptr->tm_wday - jan1.tm_wday) MOD 7 - * and that - * jan1.tm_yday == 0 - * and that - * timeptr->tm_wday MOD 7 == timeptr->tm_wday - * from which it follows that. . . - */ - jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); - if (jan1day < 0) - jan1day += 7; - - /* - * If Jan 1 was a Monday through Thursday, it was in - * week 1. Otherwise it was last year's week 53, which is - * this year's week 0. - */ - if (jan1day >= 1 && jan1day <= 4) - diff = 0; - else - diff = 1; - ret = simple_wknum - diff; - if (ret == 0) /* we're in the first week of the year */ - ret = 53; - return ret; -} -#endif - -/* weeknumber --- figure how many weeks into the year */ - -/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ - -static int -weeknumber(const struct tm *timeptr, int firstweekday) -{ - if (firstweekday == 0) - return (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7; - else - return (timeptr->tm_yday + 7 - - (timeptr->tm_wday ? (timeptr->tm_wday - 1) : 6)) / 7; -} - -#undef SYSV_EXT -#undef SUNOS_EXT -#undef POSIX2_DATE -#undef VMS_EXT -#undef POSIX_SEMANTICS -#undef range -#undef maximum -#undef minimum -#undef HAVE_GET_TZ_NAME -#undef GAWK -/* end of file strftime.c */ DELETED bindings/s2/s2_amalgamation.h Index: bindings/s2/s2_amalgamation.h ================================================================== --- bindings/s2/s2_amalgamation.h +++ bindings/s2/s2_amalgamation.h @@ -1,20658 +0,0 @@ -#if !defined(WANDERINGHORSE_NET_CWAL_S2_AMALGAMATION_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_S2_AMALGAMATION_H_INCLUDED -#if !defined(S2_AMALGAMATION_BUILD) -# define S2_AMALGAMATION_BUILD -#endif -#if !defined(_WIN32) -# if ! defined(_XOPEN_SOURCE) - /** on Linux, required for usleep(). */ -# define _XOPEN_SOURCE 500 -# endif -# ifndef _XOPEN_SOURCE_EXTENDED -# define _XOPEN_SOURCE_EXTENDED -# endif -# ifndef _BSD_SOURCE -# define _BSD_SOURCE -# endif -# ifndef _DEFAULT_SOURCE -# define _DEFAULT_SOURCE -# endif -#endif -/* start of file /home/stephan/fossil/cwal/cwal_amalgamation.h */ -#if !defined(WANDERINGHORSE_NET_CWAL_AMALGAMATION_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_AMALGAMATION_H_INCLUDED -#if !defined(CWAL_VERSION_STRING) -# define CWAL_VERSION_STRING "cwal 9364c7f5e7516e518902d282fc9fee887405b752 2021-07-24 10:57:44 built 2021-07-24 11:03" -#endif -#if defined(__cplusplus) && !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/ -# define __STDC_FORMAT_MACROS -#endif -#if !defined(CWAL_CPPFLAGS) -# define CWAL_CPPFLAGS "-I/home/stephan/include -DCWAL_OBASE_ISA_HASH=0 -I. -I/home/stephan/fossil/cwal/include -I/home/stephan/include -DDEBUG=1" -#endif -#if !defined(CWAL_CFLAGS) -# define CWAL_CFLAGS "-Werror -Wall -Wextra -Wsign-compare -fPIC -std=c99 -g -Wpedantic -O0" -#endif -#if !defined(CWAL_CXXFLAGS) -# define CWAL_CXXFLAGS "-g -O2 -fPIC -O0" -#endif -/* start of file include/wh/cwal/cwal_config.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=4 et sw=2 tw=80: */ -#if !defined(WANDERINGHORSE_NET_CWAL_CONFIG_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_CONFIG_H_INCLUDED 1 - -#if defined(HAVE_CONFIG_H) -# include "config.h" -#endif - -#if !defined(CWAL_VERSION_STRING) -#define CWAL_VERSION_STRING "?CWAL_VERSION_STRING?" -#endif - -#if !defined(CWAL_CPPFLAGS) -#define CWAL_CPPFLAGS "?CWAL_CPPFLAGS?" -#endif - -#if !defined(CWAL_CFLAGS) -#define CWAL_CFLAGS "?CWAL_CFLAGS?" -#endif - -#if !defined(CWAL_CXXFLAGS) -#define CWAL_CXXFLAGS "?CWAL_CXXFLAGS?" -#endif - -#if !defined(CWAL_SWITCH_FALL_THROUGH) -# if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7) -/* - #define CWAL_USING_GCC - - gcc v7+ treats implicit 'switch' fallthrough as a warning - (i.e. error because we always build with -Wall -Werror -Wextra - -pedantic). Because now it's apparently considered modern to warn - for using perfectly valid features of the language. Holy cow, guys, - what the hell were you thinking!?!?!? - - Similarly braindead, clang #defines __GNUC__. - - So now we need this ugliness throughout the source tree: - - #if defined(CWAL_USING_GCC) - __attribute__ ((fallthrough)); - #endif - - It turns out that one can write "fall through", case sensitive (or - not, depending on the warning level), as a _standalone C-style - comment_ to (possibly) achieve the same result (depending on the - -Wimplicit-fallthrough=N warning level, which can be set high enough - to disable that workaround or change its case-sensitivity). - - Facepalm! FacePalm!! FACEPALM!!! - - PS: i wanted to strip comments from one large piece of generated - code to reduce its distribution size, but then gcc fails to compile - it because of these goddamned "fall through" comments. gcc devs, i - hate you for this. -*/ -# define CWAL_SWITCH_FALL_THROUGH __attribute__ ((fallthrough)) -# else -# define CWAL_SWITCH_FALL_THROUGH -# endif -#endif -/* /CWAL_SWITCH_FALL_THROUGH - - TODO: add support for the C++ attributes for doing this. -*/ - -#if defined(__arm__) || defined(__thumb__) \ - || defined(__TARGET_ARCH_ARM) \ - || defined(__TARGET_ARCH_THUMB) \ - || defined(_ARM) \ - || defined(_M_ARM) \ - || defined(_M_ARMT) -/* adapted from http://sourceforge.net/p/predef/wiki/Architectures/ */ -#define CWAL_PLATFORM_ARM 1 -#endif - -#if defined(__cplusplus) && !defined(__STDC_FORMAT_MACROS) -/* inttypes.h needs this for the PRI* and SCN* macros in C++ mode. */ -# define __STDC_FORMAT_MACROS -#endif -#include /* C99 PRIuXX macros and fixed-size - integers. */ -/*#define CWAL_VOID_PTR_IS_BIG 1*/ - -#if defined(CWAL_SIZE_T_BITS) -# error "CWAL_SIZE_T_BITS must not be defined before including this file! Edit this file instead!" -#endif - -/** - A workaround for late-2015 gcc versions adding __func__ warnings - to -pedantic mode. -*/ -#if !defined(__cplusplus) && !defined(__func__) -# if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# define __func__ "__func__" -# endif -#endif - - -/** @def CWAL_ENABLE_JSON_PARSER - - If CWAL_ENABLE_JSON_PARSER is set to 0 then the cwal_json_parse() - family of functions get disabled (they still exist, but will return - errors). It has no effect on the JSON output routines. - - The reason for this is only one of licensing: the (3rd-party) - parser uses a BSD-style license with a "do no evil" clause, and - that clause prohibits the code from being used in certain package - repositories. - - Note that setting this macro might not be enough to please package - maintainers - they may also require physical removal of the code - blocked off by this macro. It can all be found in cwal_json.c. -*/ -#if !defined(CWAL_ENABLE_JSON_PARSER) -#define CWAL_ENABLE_JSON_PARSER 1 -#endif - -/** @def CWAL_DISABLE_FLOATING_POINT - - "The plan is" to allow compiling cwal with no support for doubles, but - that is not yet in place. -*/ -#if !defined(CWAL_DISABLE_FLOATING_POINT) -#define CWAL_DISABLE_FLOATING_POINT 0 -#endif - -/** @def CWAL_VOID_PTR_IS_BIG - - ONLY define this to a true value if you know that - - (sizeof(cwal_int_t) <= sizeof(void*)) - - If that is the case, cwal does not need to dynamically allocate - integers. ALL CLIENTS must use the same value for this macro. - - This value is (or was?) also used to - optimize/modify/make-more-portable a couple other internal bits. - - FIXME: figure out exactly how this plays together with - CWAL_INT_T_BITS and document what combinations are portable across - c89/c99 and 32/64 bits. - */ -#if !defined(CWAL_VOID_PTR_IS_BIG) - - /* Largely taken from http://predef.sourceforge.net/prearch.html - - See also: http://poshlib.hookatooka.com/poshlib/trac.cgi/browser/posh.h - */ -# if defined(_WIN64) || defined(__LP64__)/*gcc*/ \ - || defined(_M_X64) || defined(__amd64__) || defined(__amd64) \ - || defined(__x86_64__) || defined(__x86_64) \ - || defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) \ - || defined(_M_IA64) \ - || defined(__sparc_v9__) || defined(__sparcv9) || defined(_ADDR64) \ - || defined(__64BIT__) -# define CWAL_VOID_PTR_IS_BIG 1 -# else -# define CWAL_VOID_PTR_IS_BIG 0 -# endif -#endif - -/** @def CWAL_SIZE_T_BITS - - CWAL_SIZE_T_BITS defines the number of bits used by cwal's primary - cwal_size_t. This is used so that cwal can guaranty and enforce - certain number ranges. - - This value must be one of (16,32,64), though values lower than 32 - may or may not work well with any given component of the library - (e.g. string interning can use relatively much compared to the - rest of the lib). -*/ - -/** @def CWAL_INT_T_BITS - - CWAL_INT_T_BITS is the cwal_int_t counterpart of - CWAL_SIZE_T_BITS. cwal_int_t is independent of cwal_size_t, and may - have a different size. - -*/ - -#if 0 -/** - For testing purposes, or if a client wants to override this - globally... - - Reminder to self: a value of 64 does not work on ODroid ARM/Linux. - Problems are mostly related to hash seeds and bitmasks being too - large for unsigned long on that platform. -*/ -# define CWAL_SIZE_T_BITS 16 -#endif - -#if !defined(CWAL_SIZE_T_BITS) -/** - CWAL_SIZE_T_BITS "really should" not be higher than the pointer size - (in bits), since it would be impossible to allocate anywhere near - that number of items and this value is largely used as a length- and - reference counter. It is NOT anticipated that cwal will be used in - any environments where an unsigned 32-bit limit could ever be - reached for the things it uses cwal_size_t for (discounting - artifical/malicious inflation of reference counts and such). - - A value of 16 is perfectly reasonable for small use cases. It - doesn't save _much_ memory, but it does save some. -*/ -# if CWAL_VOID_PTR_IS_BIG -# define CWAL_SIZE_T_BITS 64 -# else -# define CWAL_SIZE_T_BITS 32 -# endif -#endif - -#if !defined(CWAL_INT_T_BITS) -/** - The ONLY reason we fall back to 32 bits here is because C89 lacks a - portable printf format string for the equivalent of PRIi64 - :/. Other than that 64-bits will (should!) work find on 32-bit - platforms as long as CWAL_VOID_PTR_IS_BIG is false. In C99 mode - 64-bit int compiles fine on 32-bit (because the result of PRIi64 is - well-defined there). -*/ -# if CWAL_VOID_PTR_IS_BIG || (defined(__STDC_VERSION__) && (__STDC_VERSION__>=199901L)) -# define CWAL_INT_T_BITS CWAL_SIZE_T_BITS -# else -# define CWAL_INT_T_BITS CWAL_SIZE_T_BITS -# endif -#endif - - -/** @def CWAL_SIZE_T_PFMT - - Is is a printf-style specifier, minus the '%' prefix, for - use with cwal_size_t arguments. It can be used like this: - - @code - cwal_size_t x = 42; - printf("The value of x is %"CWAL_SIZE_T_PFMT".", x ); - @endcode - - Using this constant ensures that the printf-style commands - work when cwal_size_t is of varying sizes. - - @see CWAL_SIZE_T_SFMT -*/ - -/** @def CWAL_SIZE_T_SFMT - -CWAL_SIZE_T_SFMT is the scanf counterpart of CWAL_SIZE_T_PFMT. - -@see CWAL_SIZE_T_PFMT -@see CWAL_SIZE_T_SFMT -*/ - -/** @def CWAL_SIZE_T_PFMTX - -CWAL_SIZE_T_PFMTX is the upper-case hexidecimal counterpart of -CWAL_SIZE_T_PFMT. - -@see CWAL_SIZE_T_PFMT -*/ - -/** @def CWAL_SIZE_T_PFMTx - -CWAL_SIZE_T_PFMTX is the lower-case hexidecimal counterpart of -CWAL_SIZE_T_PFMT. - -@see CWAL_SIZE_T_PFMT -*/ - - -/** @def CWAL_SIZE_T_PFMTo - -CWAL_SIZE_T_PFMTo is the octal counterpart of CWAL_SIZE_T_PFMT. - -@see CWAL_INT_T_SFMT -*/ - - -/** @def CWAL_SIZE_T_SFMTX - -CWAL_SIZE_T_SFMTX is the hexidecimal counterpart to CWAL_SIZE_T_SFMT. - -@see CWAL_SIZE_T_PFMT -@see CWAL_SIZE_T_SFMT -*/ - -/** @def CWAL_INT_T_PFMT - -CWAL_INT_T_PFMT is the cwal_int_t counterpart of CWAL_SIZE_T_PFMT. - -@see CWAL_SIZE_T_PFMT -@see CWAL_INT_T_SFMT -*/ - -/** @def CWAL_INT_T_SFMT - -CWAL_INT_T_SFMT is the scanf counterpart of CWAL_INT_T_PFMT. - -@see CWAL_INT_T_PFMT -*/ - -/** @def CWAL_INT_T_PFMTX - -CWAL_INT_T_PFMTX is the upper-case hexidecimal counterpart of -CWAL_INT_T_PFMT. - -@see CWAL_INT_T_SFMT -*/ - -/** @def CWAL_INT_T_PFMTx - -CWAL_INT_T_PFMTX is the lower-case hexidecimal counterpart of -CWAL_INT_T_PFMT. - -@see CWAL_INT_T_SFMT -*/ - -/** @def CWAL_INT_T_PFMTo - -CWAL_INT_T_PFMTo is the octal counterpart of CWAL_INT_T_PFMT. - -@see CWAL_INT_T_SFMT -*/ - - -/** @def CWAL_INT_T_SFMTX - -CWAL_INT_T_SFMTX is the hexidecimal counterpart to CWAL_INT_T_SFMT. - -@see CWAL_INT_T_PFMT -@see CWAL_INT_T_SFMT -*/ - - -/** @def CWAL_INT_T_MIN - -CWAL_INT_T_MIN is the minimum value of the data type cwal_int_t. - -@see CWAL_INT_T_MAX -*/ - -/** @def CWAL_INT_T_MAX - -CWAL_INT_T_MAX is the maximum value of the data type cwal_int_t. - -@see CWAL_INT_T_MAX -*/ - - - -/** typedef some_unsigned_int_type_which_is_CWAL_SIZE_T_BITS_long cwal_size_t - -cwal_size_t is a configurable unsigned integer type specifying the -ranges used by this library. Its exact type depends on the value of -CWAL_SIZE_T_BITS: it will be uintXX_t, where XX is the value of -CWAL_SIZE_T_BITS (16, 32, or 64). - -We use a fixed-size numeric type, instead of relying on a standard -type with an unspecified size (e.g. size_t) to help avoid nasty -surprises when porting to machines with different size_t -sizes. - -For cwal's intended purposes uint16_t is "almost certainly" fine, but -those who are concerned about 64kb limitations on certain contexts -might want to set this to uint32_t. - -*/ - -/** typedef some_unsigned_int_type cwal_midsize_t - -A cwal_size_t counterpart which is intended to be capped at 32 bits -and used in contexts for which 64 bits is simply a massive waste (e.g. -arrays, strings, and hashtables). -*/ - -/** @typedef some_signed_integer cwal_int_t - - This is the type of integer value used by the library for its - "script-visible" integers. -*/ - -/** @typedef some_unsigned_integer cwal_refcount_t - - This is the type of integer value used by the library to keep - track of both reference counts and their embedded flags (which - reduce the useful range of the reference count). -*/ - -/** @def CWAL_REFCOUNT_T_BITS - - @internal - - MUST be equal to (sizeof(cwal_refcount_t) * 8), but must be a - constant usable by the preprocessor. -*/ - - -/* Set up CWAL_SIZE_T... */ -#if CWAL_SIZE_T_BITS == 16 -# define CWAL_SIZE_T_PFMT PRIu16 -# define CWAL_SIZE_T_PFMTx PRIx16 -# define CWAL_SIZE_T_PFMTX PRIX16 -# define CWAL_SIZE_T_PFMTo PRIo16 -# define CWAL_SIZE_T_SFMT SCNu16 -# define CWAL_SIZE_T_SFMTX SCNx16 -# define CWAL_SIZE_T_MAX 65535U - typedef uint16_t cwal_size_t; - typedef uint32_t cwal_refcount_t /* yes, 32, because we store flags in cwal_value refcounts */; -# define CWAL_REFCOUNT_T_BITS 32 -#elif CWAL_SIZE_T_BITS == 32 -# define CWAL_SIZE_T_PFMT PRIu32 -# define CWAL_SIZE_T_PFMTx PRIx32 -# define CWAL_SIZE_T_PFMTX PRIX32 -# define CWAL_SIZE_T_PFMTo PRIo32 -# define CWAL_SIZE_T_SFMT SCNu32 -# define CWAL_SIZE_T_SFMTX SCNx32 -# define CWAL_SIZE_T_MAX 4294967295U - typedef uint32_t cwal_size_t; - typedef uint32_t cwal_refcount_t; -# define CWAL_REFCOUNT_T_BITS 32 -#elif CWAL_SIZE_T_BITS == 64 -# define CWAL_SIZE_T_PFMT PRIu64 -# define CWAL_SIZE_T_PFMTx PRIx64 -# define CWAL_SIZE_T_PFMTX PRIX64 -# define CWAL_SIZE_T_PFMTo PRIo64 -# define CWAL_SIZE_T_SFMT SCNu64 -# define CWAL_SIZE_T_SFMTX SCNx64 -# define CWAL_SIZE_T_MAX 18446744073709551615U - typedef uint64_t cwal_size_t; - typedef uint64_t cwal_refcount_t /*32 bits "should be" fine, but using - 64 doesn't (because of padding) actually change - the sizeof of cwal_value */; -# define CWAL_REFCOUNT_T_BITS 64 -#else -# error "CWAL_SIZE_T_BITS must be one of: 16, 32, 64" -#endif - -/* Set up CWAL_MIDSIZE_... */ -#if CWAL_SIZE_T_BITS == 16 - typedef uint16_t cwal_midsize_t; -# define CWAL_MIDSIZE_T_PFMT CWAL_SIZE_T_PFMT -# define CWAL_MIDSIZE_T_PFMTx CWAL_SIZE_T_PFMTx -# define CWAL_MIDSIZE_T_PFMTX CWAL_SIZE_T_PFMTX -# define CWAL_MIDSIZE_T_PFMTo CWAL_SIZE_T_PFMTo -# define CWAL_MIDSIZE_T_SFMT CWAL_SIZE_T_SFMT -# define CWAL_MIDSIZE_T_SFMTX CWAL_SIZE_T_SFMTX -# define CWAL_MIDSIZE_T_MAX CWAL_SIZE_T_MAX -#else - typedef uint32_t cwal_midsize_t; -# define CWAL_MIDSIZE_T_PFMT PRIu32 -# define CWAL_MIDSIZE_T_PFMTx PRIx32 -# define CWAL_MIDSIZE_T_PFMTX PRIX32 -# define CWAL_MIDSIZE_T_PFMTo PRIo32 -# define CWAL_MIDSIZE_T_SFMT SCNu32 -# define CWAL_MIDSIZE_T_SFMTX SCNx32 -# define CWAL_MIDSIZE_T_MAX 4294967295U -#endif - -/* Set up CWAL_INT_... */ -#if CWAL_INT_T_BITS == 16 -# define CWAL_INT_T_PFMT PRIi16 -# define CWAL_INT_T_PFMTx PRIx16 -# define CWAL_INT_T_PFMTX PRIX16 -# define CWAL_INT_T_PFMTo PRIo16 -# define CWAL_INT_T_SFMT SCNi16 -# define CWAL_INT_T_SFMTX SCNX16 -# define CWAL_INT_T_MAX 32767 - typedef int16_t cwal_int_t; - typedef uint16_t cwal_uint_t; -#elif CWAL_INT_T_BITS == 32 -# define CWAL_INT_T_PFMT PRIi32 -# define CWAL_INT_T_PFMTx PRIx32 -# define CWAL_INT_T_PFMTX PRIX32 -# define CWAL_INT_T_PFMTo PRIo32 -# define CWAL_INT_T_SFMT SCNi32 -# define CWAL_INT_T_SFMTX SCNx32 -# define CWAL_INT_T_MAX 2147483647 - typedef int32_t cwal_int_t; - typedef uint32_t cwal_uint_t; -#elif CWAL_INT_T_BITS == 64 -# define CWAL_INT_T_PFMT PRIi64 -# define CWAL_INT_T_PFMTx PRIx64 -# define CWAL_INT_T_PFMTX PRIX64 -# define CWAL_INT_T_PFMTo PRIo64 -# define CWAL_INT_T_SFMT SCNi64 -# define CWAL_INT_T_SFMTX SCNx64 -# define CWAL_INT_T_MAX 9223372036854775807 - typedef int64_t cwal_int_t; - typedef uint64_t cwal_uint_t; -#else -# error "CWAL_INT_T_BITS must be one of: 16, 32, 64" -#endif -#define CWAL_INT_T_MIN ((-CWAL_INT_T_MAX)-1) -/* - Reminder: the definition ((-CWAL_INT_T_MAX)-1) was gleaned from the - clang headers, but AFAIK C does not actually define what happens - for under/overflow for _signed_ types. Trying to use the literal - value in 64-bit mode gives me a compile error on gcc ("constant - value is only signed in C99", or some such). -*/ - -/** @typedef some_unsigned_int_type cwal_hash_t - - Hash value type used by the library. It must be an unsigned integer - type. -*/ -#if 16 == CWAL_INT_T_BITS -typedef uint32_t cwal_hash_t /* need 32-bit for some hash seeds */; -#elif 32 == CWAL_INT_T_BITS -typedef uint32_t cwal_hash_t; -#elif 64 == CWAL_INT_T_BITS -typedef uint64_t cwal_hash_t; -#endif - -/** @typedef double_or_long_double cwal_double_t - - This is the type of double value used by the library. It is only - lightly tested with long double, and when using long double the - memory requirements for such values goes up (of course). - - Note that by default cwal uses C-API defaults for - numeric precision. To use a custom precision throughout - the library, one needs to define the macros CWAL_DOUBLE_T_SFMT - and/or CWAL_DOUBLE_T_PFMT macros to include their desired - precision, and must build BOTH cwal AND the client using - these same values. For example: - - @code - #define CWAL_DOUBLE_T_PFMT ".8Lf" - #define HAVE_LONG_DOUBLE - @endcode -*/ -#if CWAL_DISABLE_FLOATING_POINT - /* No doubles support: use integers instead so we don't have to - block out portions of the API. - */ - typedef cwal_int_t cwal_double_t; -# define CWAL_DOUBLE_T_SFMT CWAL_INT_T_SFMT -# define CWAL_DOUBLE_T_PFMT CWAL_INT_T_PFMT -#else -# if defined(HAVE_LONG_DOUBLE) - typedef long double cwal_double_t; -# ifndef CWAL_DOUBLE_T_SFMT -# define CWAL_DOUBLE_T_SFMT "Lf" -# endif -# ifndef CWAL_DOUBLE_T_PFMT -# define CWAL_DOUBLE_T_PFMT "Lf" -# endif -# else - typedef double cwal_double_t; -# ifndef CWAL_DOUBLE_T_SFMT -# define CWAL_DOUBLE_T_SFMT "f" -#endif -# ifndef CWAL_DOUBLE_T_PFMT -# define CWAL_DOUBLE_T_PFMT "f" -#endif -# endif -#endif - -#if !defined(CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS) -/** - If true when cwal is compiled (as opposed to later on, when - including this header via client code), cwal includes all 128 - length-1 ASCII strings in its list of static builtin values. This - list includes a length-1 cwal_string instance (builtin/constant) - for all ASCII values (0 to 127, inclusive). This has a static - memory cost of ~7.5k on a 64-bit build but has the potential to - save scads of allocations, especially if client code consciously - takes advantage of it (mine does!). Here's an out-take from the - cwal metrics dump after running the s2 unit test suit (20181128): - - @code - Length-1 ASCII string optimization savings... - strings: 2681 allocation(s), 150136 bytes. - x-strings: 1 allocation(s), 56 bytes. - z-strings: 1 allocation(s), 56 bytes. - Total savings: 2683, allocation(s), 150248 bytes. - @endcode -*/ -# define CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS 1 -#endif - -#if !defined(CWAL_ENABLE_TRACE) -/** - Setting CWAL_ENABLE_TRACE to a true value enables - cwal-internal tracing. Profiling shows it to be very - expensive, and its level of detail is too great for most - users to be able to do anything with, so it is recommended - that it be left off unless needed. - */ -# define CWAL_ENABLE_TRACE 1 -#endif - - -#if !defined(CWAL_OBASE_ISA_HASH) -/** @def CWAL_OBASE_ISA_HASH - - If CWAL_OBASE_ISA_HASH is true then cwal_obase will use a hashtable, - instead of a sorted list, for its property management. That works - the same as the "legacy" mode (sorted doubly-linked property lists) - except that (1) it's computationally faster, (2) requires more - memory, and (3) has type-strict property keys. e.g. in legacy mode - the property keys "1" (string) and 1 (integer) are equivalent, but - they are distinct keys in hashtable mode. - - Because this flag changes the sizeof() of the cwal container types, - it is critical that all compilation units (including loadable - modules and any downstream client code) use the same value! The - easiest(?) way to ensure that that happens when using the cwal - amalgamation build (the preferred approach) from a client tree is to - define HAVE_CONFIG_H for the whole build, which will cause this file - to #include "config.h", where this macro can be set to a default - value. -*/ -# define CWAL_OBASE_ISA_HASH 0 -#endif - -#endif -/* WANDERINGHORSE_NET_CWAL_CONFIG_H_INCLUDED */ -/* end of file include/wh/cwal/cwal_config.h */ -/* start of file include/wh/cwal/cwal.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -#if !defined(WANDERINGHORSE_NET_CWAL_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_H_INCLUDED 1 - -#if defined(HAVE_CONFIG_H) || defined(HAVE_AUTOCONFIG_H) -# include "config.h" -#endif -#include /* va_list */ -#include /* FILE decl */ -#include - -/** - @page page_cwal cwal API - - cwal (pronounced "sea wall") is the Scriping Engine Without A - Language (sewal, shortened to cwal): an object-oriented C99 API - providing _part_ of a scripting engine, namely the engine and not - the scripting. The intention is that custom scripting - languages/mini-languages can be written on top of this basis, which - takes care of the core type system, de/allocation of values, - tracking references, and other low-level work which is largely - independent of any specific language syntax. Its design does impose - some conventions/requirements on host languages, but nothing too - onerous, it is hoped. Alternately, it can be used as sort of - garbage collection system for clients willing to use its type - system. Another potential use might be a data-binding mechanism - between, e.g. database drivers and client code, acting as a - type-normalization layer between the two (so the client code can be - written without much knowledge of the underlying driver(s)). - - cwal's API is stable in the sense that much client-side code relies - on it which we don't want broken, but will always be beta in the - sense that it's open to experimentation and change. It is very rare - (nowadays) that it is modified in ways which "break stuff." As of - mid-2014 we have a good deal of add-on code, in particular with the - s2 scripting engine, which we would like to keep running, so - massive changes are unlikely. Though s2 is co-developed with cwal, - the core cwal engine is intentionally kept free of - scriping-language-specific artifacts. - - Project home page: https://fossil.wanderinghorse.net/r/cwal - - Author: Stephan Beal (https://www.wanderinghorse.net/home/stephan/) - - License: Dual Public Domain/MIT - - The full license text is in the main header file (cwal.h or - cwal_amalgamation.h, depending on the distribution used): search - the file for the word LICENSE. The optional JSON parser, from a 3rd - party, has its own license, equivalent to the MIT license. - - Examples of how to use the library are scattered throughout the API - documentation, in the test.c file in the source repo, in the wiki - on the project home page, and (in a big way) in the s2 subdirectory - of the main source tree. - - Main properties of cwal: - - - cwal does NOT provide a scripting language. It is an engine which - "could" be used as a basis for one. It can also be used as simple - form of garbage collector for client apps willing to live with its - scoping mechanism. - - - Provides a type system similar (not identical) to that of - ECMAScript. Values are opaque handles with some polymorphic - behaviours depending on their logical type. Provides support for - binding client-specified "native" values, such that they can - participate in the normal lifetime tracking and destruction process - and can be converted from their Value handles with 100% - type-safety. - - - Uses a reference-counting/scope-ownership hybrid for value - sharing and garbage collection. Its destruction mechanism behaves - sanely when faced with cycles provided the rules of the API are - followed (they're pretty easy). - - - Destruction (finalizer calls) is gauranteed and happens more or - less like it does in C++, with the addition that refcounting can be - used to extend the lifetime of a value outside of the scope where - it is created. While refcounting in conjunction with container can - make destruction order somewhat difficult to predict, it is still - deterministic in that finalizers are guaranteed to be called, so - long as the API is properly used. - - - The wrapping of client-provided native types uses a type-safety - mechanism to ensure that clients are getting the type of pointer - they expect when fetching the (void*) from its cwal value type - counterpart. Such types participate as first-class Values and may - contain properties (meaning they can participate in graphs). - - - Provides optional automatic "internalizing" of new string values, - and all strings which share the same length and byte content are - automatically shared via the reference-counting mechanism. When the - final reference is released the string is un-internalized - automatically. This causes a couple corner cases internally but can - drastrically reduce allocations in scripts which make heavy use of - identifier strings. - - - Highly optimized to reduce calls to malloc() and free(), and - optionally makes use of memory recycling. The recycling limits can - be set on a per-data-type basis. Practice has shown recycling to - be tremendously effective at reducing malloc calls by over 90% - in typical script code (a 98% reduction is not uncommon!). - - - Optionally provides tracing (via callbacks) so that clients - can see what it is doing on the inside. - - - "Relatively clean" code is a design goal, as is having relatively - good documentation. -*/ - -/** - @page page_cwal_gc cwal Garbage Collection - - This section describes how cwal manages the memory of Values - created by the client. - - cwal's main concern, above all, is memory management. This includes - at least the following aspects: - - - Allocation and deallocation of raw memory. All memory allocation - in the context of cwal is handled via a cwal_engine instance (a - context type of which an application has one per "scripting - engine"), and clients may optionally specify their own allocator - when initializing a cwal_engine instance. cwal internally manages a - number of optional recycling bins, broken down by data type, where - it stores freed Values (and other internals) for re-use. Once the - recycle bins have been populated a bit, it has to allocate memory - far less often (how often depends on usage and recycler - configuration). - - - Tracking the lifetimes of components which clients cannot - reasonably track themselves. This refers specifically to Values, - since their inter-relationships (via hierachies and key/value - properties) can easily lead to unpredictable patterns which would - be unrealistically burdensome for client code to try to properly - manage. - - cwal's garbage collection mechanism has two basic components: - reference counts and scopes. - - cwal uses convential refcount semantics. The reference count of a - Value is increased when code "expresses an interest" in the Value, - e.g. it is inserted into a container or an explicit call to - cwal_value_ref() is called. Reference counts are decreased as - Values are removed from containers (e.g. Objects and Arrays) or - cwal_value_unref() is called. When the refcount goes to 0, the - Value is typically cleaned up (recursively for containers), but the - engine also offers an operation which says "reduce the refcount but - do not destroy the object if it reaches 0," which is amazingly - useful in lifetime management of opaque values. - - Scopes act as a "root" for a collection of values, such that - cleaning up a scope will clean up all values under that scope - (regardless of their reference count). cwal uses C++-like scoping - rules: a stack of scopes, only the youngest of which is active at - any given time. - - Reference counting is straightforward and easy to implement but is - conventionally problematic when it comes to managing graphs of - Values (i.e. cyclic data structures). When a cwal scope is closed, - it destructs any values it owns by iteratively dereferencing - them. This process removes the values from the scope in such a way - that graphs get weeded out incrementally (one level at a time) and - (more or less) cleanly. Consider this pseudo-script-code: - - var obj = new Object(); - obj.set(obj, obj); // (key, value) - - (Admitedly unusual, but cwal's value system supports this.) - - We now have an object with 3 references: 1 held by the identifier - mapping (i.e. the declared variable), one held by the property - key, and one held by the property value. (Possibly others, - depending on the scripting implementation.) - - Now we do: - - unset obj - - That removes the identifier and its reference. That leaves us with - a refcount of 2 and no handle back to the object. The object must - be kept alive because there are references (the key/value refering - to the object itself). That value will remain an orphan until its - owning scope is cleaned, at which point the scope's cleaning - process will weed out the cycles and finalize the object when the - final reference is removed. (cwal_scope_vacuum() can weed that out - and clean it up, though.) - - There are of course wrinkles in that equation. For example, the - finalization process for an object must recursively climb down - properties, and in the above case doing so will trigger - finalization of an object while it is traversing itself. cwal's - cleanup mechanism temporarily delays freeing of Values' memory - during scope cleanup, queuing them up for later destruction. The - end effect is that Values get cleaned up but the memory remains - valid until scope cleanup has finished. This makes it safe (if - psychologically a bit unsettling) to traverse "destroyed" objects - during finalization (it is a harmless no-op). Once scope cleanup is - complete, all queued-up destroyed values are then flushed to the - deallocator (or the recycling bin, if it has space). - - Summary: in cwal it is possible to orphan graphs in such a way that - the client has no reference to them but circular references prevent - cleanup. Such values will be freed when their parent scope is - cleaned. There is also a "sweep" mechanism to trigger cleanups of - values with no references and a more intensive "vacuum" operation - which can also weed out unreachable cyclic structures. (Note that - cwal does not use a "mark-and-sweep" approach: all memory it - manages is carefully accounted for in internal bookkeeping, and - there is no guesswork about which memory might or might not be - "marked" for cleanup.) - - Speaking of parent scopes... cwal complements the reference - counting with the concept of "owning scope." Every value belongs to - exactly one scope, and this scope is always the highest-level - (oldest) scope which has ever referenced the value. When a value is - created, it belongs to the scope which is active at the - time. Values can be rescoped, however, moving up in the stack (into - an older scope, never a newer scope). When a value is inserted into - a container, the value is scoped into the container's owning - scope. When the container is added to another, the container is - recursively re-scoped (if necessary) to match the scope of its - parent container. This ensures that values from a higher-level - (older) scope are always valid through the lifetime of lower-level - (newer) scopes. It also allows a scope to pass a value to its - parent scope. When a value is re-scoped, it is removed from its - owning scope's management list and added to the new scope's - list. These list are linked lists and use O(1) algos for the list - management (except for cleanup, which is effectively O(N) on the - number of values being cleaned up). - - As of this writing, in Dec. 2019, cwal has been in moderately heavy - use for more than 5 years, first in the now-deceased "th1ish" - scripting engine and subsequently in the "s2" engine, which has - demonstrated, beyond the shadow of a doubt, the engine's ability to - handle modest scripting-language needs. s2's range of features - extend far, far beyond any initial hopes i had for cwal, and - provide me with a useful tool with which to script my other - software or do certain day-to-day tasks such as generate static - pages for my website from s2-enabled template files (kind of like a - poor man's PHP) and power a number of my JSON-based CGI - applications (backend services for various web apps). -*/ -#if defined(__cplusplus) -extern "C" { -#endif - -/* Forward declarations. Most of these types are opaque to client - code. */ -typedef struct cwal_scope cwal_scope; -typedef struct cwal_engine_vtab cwal_engine_vtab; -typedef struct cwal_engine cwal_engine; -typedef struct cwal_value cwal_value; -typedef struct cwal_array cwal_array; -typedef struct cwal_object cwal_object; -typedef struct cwal_string cwal_string; -typedef struct cwal_kvp cwal_kvp; -typedef struct cwal_native cwal_native; -typedef struct cwal_buffer cwal_buffer; -typedef struct cwal_exception cwal_exception; -typedef struct cwal_hash cwal_hash; -typedef struct cwal_weak_ref cwal_weak_ref; -typedef struct cwal_callback_hook cwal_callback_hook; -typedef struct cwal_callback_args cwal_callback_args; -typedef struct cwal_tuple cwal_tuple; -typedef struct cwal_error cwal_error; - -/** - Typedef for flags fields which are limited to 16 bits. -*/ -typedef uint16_t cwal_flags16_t; -/** - Typedef for flags fields which are limited to 32 bits. -*/ -typedef uint32_t cwal_flags32_t; - -/** - A callback type for pre-call callback hooks. If a hook is - installed via cwal_callback_hook_set(), its "pre" callback is - called before any "script-bound" function is called. - - The first parameter is the one which will be passed to the - callback and post-callback hook if this hook succeeds. - - The state parameter (2nd argument) is the one passed to - cwal_callback_hook_set(). Its interpretation is - implementation-defined. - - The callback must return 0 on success. On error, the non-0 - result code (from the CWAL_RC_xxx set) is returned from the - cwal API which triggered the hook (cwal_function_call() and - friends). On success, the callback is called and then - (regardless of callback success!) the "post" callback is - called. - - The intention of the callback hook mechanism is to give script - engines a places to do pre-call setup such as installing local - variables (e.g. similar to JavaScript's "this", "arguments", and - "arguments.callee"). It can also be used for instrumentation - purposes (logging) or to implement multi-casting of client-side - pre-call hook mechanism to multiple listeners. - - Note that the hook mechanism is "global" - it affects all - cwal_function_call_in_scope() calls, which means all - script-triggered callbacks and any other place a - cwal_function_call() (or similar) is used. It _is_ possible - (using cwal_function_state_get()) for a client to determine - whether the callback exists in script code or native code, - which means that hooks can act dependently of that if they need - to. e.g. there is generally no need to inject "this" and - "arguments" symbols into native-level callbacks, whereas it is - generally useful to do so for script-side callbacks. Native - callbacks have access to the same information via the argv - object, so they don't need scope-level copies of those values. - (That said, native callbacks which call back into script code - might need such variables in place!) - - @see cwal_callback_hook_post_f() - @see cwal_callback_hook - @see cwal_callback_hook_set() - @see cwal_callback_f -*/ -typedef int (*cwal_callback_hook_pre_f)(cwal_callback_args const * argv, void * state); - -/** - The "post" counterpart of cwal_callback_hook_pre_f(), this hook is - called by the cwal core after calling a cwal_function via and of - the cwal_function_call() family of functions. - - The state parameter (2nd argument) is the one passed to - cwal_callback_hook_set(). Its interpretation is - implementation-defined. - - The fRc (3rd) parameter is the return code of the callback, in case - it interests the hook (e.g. error logging). If this value is not 0 - then the rv parameter will be NULL. - - The rv (4rd) parameter is the result value of the function. The - engine will re-scope rv, if necessary, brief milliseconds after - this hook returns. rv MAY be NULL, indicating that the callback did - not set a value (which typically equates to the undefined value - (cwal_value_undefined()) downstream or may be because an exception - was thrown or a non-exception error code was returned). - - If the "pre" hook is called and returns 0, the API guarantees - that the post-hook is called. Conversely, if the pre hook - returns non-0, the post hook is never called. - - If this callback returns non-0, that will be the result returned - from the cwal_function_call()-like function which triggered it. - - @see cwal_callback_hook_pre_f() - @see cwal_callback_hook - @see cwal_callback_hook_set() -*/ -typedef int (*cwal_callback_hook_post_f)(cwal_callback_args const * argv, - void * state, - int fRc, cwal_value * rv); - -/** - Holds state information for a set of cwal_engine callback - hooks. - - @see cwal_callback_hook_pre_f() - @see cwal_callback_hook_post_f() - @see cwal_callback_hook_set() -*/ -struct cwal_callback_hook { - /** - Implementation-dependent state pointer which gets passed - as the 2nd argument to this->pre() and this->post(). - */ - void * state; - /** - The pre-callback hook. May be NULL. - */ - cwal_callback_hook_pre_f pre; - /** - The post-callback hook. May be NULL. - */ - cwal_callback_hook_post_f post; -}; - -/** - An initialized-with-defaults instance of cwal_callback_hook, - intended for const-copy intialization. -*/ -#define cwal_callback_hook_empty_m {NULL,NULL,NULL} - -/** - An initialized-with-defaults instance of cwal_callback_hook, - intended for copy intialization. -*/ -extern const cwal_callback_hook cwal_callback_hook_empty; - - -/** - The set of result codes used by most cwal API routines. Not all of - these are errors, per se, and may have context-dependent - interpretations. - - Client code MUST NOT rely on any of these entries having a - particular value EXCEPT for CWAL_RC_OK, which is guaranteed to - be 0. All other entries are guaranteed to NOT be 0, but their - exact values may change from time to time. The values are - guaranteed to stay within a standard enum range of signed - integer bits, but no other guarantees are made (e.g. whether - the values are positive or negative, 2 digits or 5, are - unspecified and may change). -*/ -enum cwal_rc_e { -/** - The canonical "not an error" code. -*/ -CWAL_RC_OK = 0, -/** - Generic "don't have anything better" error code. -*/ -CWAL_RC_ERROR = 1, -/** - Out-of-memory. -*/ -CWAL_RC_OOM = 2, -/** - Signifies that the cwal engine may be in an unspecific state - and must not be used further. -*/ -CWAL_RC_FATAL = 3, -/** - Signals that the returning operation wants one of its callers - to implement "continue" semantics. -*/ -CWAL_RC_CONTINUE = 101, -/** - Signals that the returning operation wants one of its callers - to implement "break" semantics. -*/ -CWAL_RC_BREAK = 102, -/** - Signals that the returning operation wants one of its callers - to implement "return" semantics. -*/ -CWAL_RC_RETURN = 103, - -/** - Indicates that the interpreter should stop running - the current script immediately. -*/ -CWAL_RC_EXIT = 104, - -/** - Indicates that the interpreter "threw an exception", which - "should" be reflected by passing a cwal_exception value down - the call stack via one of the cwal_exception_set() family of - functions. Callers "should" treat this return value as fatal, - immediately passing it back to their callers (if possible), - until a call is reached in the stack which handles this return - type (e.g. the conventional "catch" handler), at which point - the propagation should stop. -*/ -CWAL_RC_EXCEPTION = 105, - -/** - Indicates that the interpreter triggered an assertion. Whether - these are handled as outright fatal errors or exceptions (or some - other mechanism) is up to the interpreter. It is also sometimes - used in non-debug builds to flag cwal_engine::fatalCode when an - assert() would have triggered in a debug build. -*/ -CWAL_RC_ASSERT = 106, - -/** - Indicates that some argument value is incorrect or a precondition - is not met. -*/ -CWAL_RC_MISUSE = 201, -/** - Indicates that a resource being searched for was not found. -*/ -CWAL_RC_NOT_FOUND = 301, -/** - Indicates that a resource being searched for or replaced already - exists (whether or not this is an error is context-dependent). -*/ -CWAL_RC_ALREADY_EXISTS = 302, -/** - A more specific form of CWAL_RC_MISUSE, this indicates that some - value (argument or data a routine depends on) is "out of range." -*/ -CWAL_RC_RANGE = 303, -/** - Indicates that some value is not of the required type (or family of - types). -*/ -CWAL_RC_TYPE = 304, -/** - Indicates an unsupported/not-yet-implemented operation was - requested. -*/ -CWAL_RC_UNSUPPORTED = 305, - -/** - Indicates that access to some resource was denied. -*/ -CWAL_RC_ACCESS = 306, - -/** - Indicates that an operation has failed because the value in - question is a container which is being visited, and the operation - in question is illegal for being-visited containers. - - Historically we have used CWAL_RC_ACCESS to note that iteration or - modification is not allowed because it would be recursive. That - code has proven to be ambiguous in some contexts, so this code was - added specifically for that case. -*/ -CWAL_RC_IS_VISITING = 307, - -/** - Indicates that an operation was requested which is disallowed - because the resources is currently iterating over a list component - which would be negatively affected. An example would be resizing an - array while it's being sorted, or sorting twice concurrently. -*/ -CWAL_RC_IS_VISITING_LIST = 308, - -/** - Indicates a CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES constraint - violation. - - A special case of CWAL_RC_ACCESS. -*/ -CWAL_RC_DISALLOW_NEW_PROPERTIES = 309, - -/** - Indicates a CWAL_CONTAINER_DISALLOW_PROP_SET constraint violation. - - A special case of CWAL_RC_ACCESS. -*/ -CWAL_RC_DISALLOW_PROP_SET = 310, - -/** - Indicates a CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET constraint - violation. - - A special case of CWAL_RC_ACCESS. -*/ -CWAL_RC_DISALLOW_PROTOTYPE_SET = 311, - -/** - Indicates a CWAL_VAR_F_CONST constraint violation. - - A special case of CWAL_RC_ACCESS. -*/ -CWAL_RC_CONST_VIOLATION = 312, - -/** - Indicates that a value is locked against the requested operation - for some reason. e.g. an array may not be traversed while a sort() - is underway. - - A special case of CWAL_RC_ACCESS. -*/ -CWAL_RC_LOCKED = 313, - -/** - Indicates that visitation of a value would step into a cycle - collision. Whether or not this is an error is context-dependent. -*/ -CWAL_RC_CYCLES_DETECTED = 401, -/** - Used by value cleanup to help ensure that values with cycles do not - get freed multiple times. Returned only by cwal_value_unref() and - friends and only when a value encounters a reference to itself - somewhere in the destruction process. In that context this value is - a flag, not an error, but it is also used in assert()s to ensure - that pre- and post-conditions involving cycle traversal during - destruction are held. -*/ -CWAL_RC_DESTRUCTION_RUNNING = 402, - -/** - Returned by cwal_value_unref() when it really finalizes a value. -*/ -CWAL_RC_FINALIZED = 403, - -/** - Returned by cwal_value_unref() when it really the value it is given - still has active references after unref returns. -*/ -CWAL_RC_HAS_REFERENCES = 404, - -/** - Reserved for future use. -*/ -CWAL_RC_INTERRUPTED = 501, -/** - Reserved for future use. -*/ -CWAL_RC_CANCELLED = 502, -/** - Indicates an i/o error of some sort. -*/ -CWAL_RC_IO = 601, -/** - Intended for use by routines which normally assert() a - particular condition but do not do so when built in non-debug - mode. -*/ -CWAL_RC_CANNOT_HAPPEN = 666, - -CWAL_RC_JSON_INVALID_CHAR = 700, -CWAL_RC_JSON_INVALID_KEYWORD, -CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE, -CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE, -CWAL_RC_JSON_INVALID_NUMBER, -CWAL_RC_JSON_NESTING_DEPTH_REACHED, -CWAL_RC_JSON_UNBALANCED_COLLECTION, -CWAL_RC_JSON_EXPECTED_KEY, -CWAL_RC_JSON_EXPECTED_COLON, - -/** - The CWAL_SCR_xxx family of values are intended for use by - concrete scripting implementations based on cwal. - - 2020-02-10: in practice, the only one of the SCR values which sees - any use is CWAL_SCR_SYNTAX. The rest are, it turns out, essentially - special cases of conditions which are covered just as well by the - more generic CWAL_RC_xxx values. s2 also uses CWAL_SCR_DIV_BY_ZERO, - but that could just as easily be covered by CWAL_RC_RANGE. -*/ -CWAL_SCR_readme = 2000, - -/** - Indicates that the provided token "could not be consumed" by - the given handler, but that there is otherwise no known error. -*/ -CWAL_SCR_CANNOT_CONSUME, - -/** - Indicates that an invalid operation was performed on a value, or - that the type(s) required for a given operation are incorrect. - - More concrete case of CWAL_RC_TYPE or CWAL_RC_UNSUPPORTED. -*/ -CWAL_SCR_INVALID_OP, - -/** - Special case of CWAL_RC_NOT_FOUND, indicates that an - identifier string could not be found in the scope - path. -*/ -CWAL_SCR_UNKNOWN_IDENTIFIER, - -/** - Indicates a (failed) attempt to call() a non-Function value. - More concrete case of CWAL_RC_TYPE. -*/ -CWAL_SCR_CALL_OF_NON_FUNCTION, - -/** - More concrete case of CWAL_SCR_SYNTAX. -*/ -CWAL_SCR_MISMATCHED_BRACE, - -/** - More concrete case of CWAL_SCR_SYNTAX. -*/ -CWAL_SCR_MISSING_SEPARATOR, -/** - More concrete case of CWAL_SCR_SYNTAX. -*/ -CWAL_SCR_UNEXPECTED_TOKEN, - -/** - Indicates division or modulus by 0 would have been attempted. -*/ -CWAL_SCR_DIV_BY_ZERO, -/** - Indicates a generic syntax error. -*/ -CWAL_SCR_SYNTAX, -/** - Indicates that an unexpected EOF was encountered (e.g. while - reading a string literal). -*/ -CWAL_SCR_UNEXPECTED_EOF, -/** - Indicates EOF was encountered. Whether or not this is an error - is context-dependent, and CWAL_SCR_UNEXPECTED_EOF is intended - for the error case. -*/ -CWAL_SCR_EOF, -/** - More concrete case of CWAL_RC_RANGE. -*/ -CWAL_SCR_TOO_MANY_ARGUMENTS, -/** - More concrete case of CWAL_SCR_SYNTAX. -*/ -CWAL_SCR_EXPECTING_IDENTIFIER, - -/** - The "legal" result code starting point for adding client-specific - RC values for use with cwal. In other words, do not use codes with - values lower than this one in cwal-bound client-side code, e.g. as - return codes from cwal_callback_f() implementations, as doing so - risks "semantic collisions" with cwal-internal/cwal-conventional - handling of certain codes. Examples: CWAL_RC_OOM is used solely to - propagate out-of-memory (OOM) conditions and CWAL_RC_EXCEPTION - signifies that a script-side exception has been thrown (which - typically requires different handling than other C-level errors). -*/ -CWAL_RC_CLIENT_BEGIN = 3000 -}; - -/** Convenience typedef. */ -typedef enum cwal_rc_e cwal_rc; - -/** - Compile-time limits not covered by configuration macros. -*/ -enum cwal_e_options_e { -/** - Max number of arguments cwal_function_callf() and (variadic) - friends. Remember that each one takes up sizeof(cwal_value*) in - stack space. -*/ -CWAL_OPT_MAX_FUNC_CALL_ARGS = 32 -}; - - -/** - A collection of values which control what tracing messages get - emitted by a cwal_engine. - - By an unfortunate fluke of mis-design, entries which are - themselves not group masks (groups are named xxxx_MASK) cannot - be effecitvely mixed together via bitmasking. The end effect is - that only the MASK, NONE, or ALL entries can be - usefully/predictibly applied. i'll see about fixing that. -*/ -enum cwal_trace_flags_e { - -CWAL_TRACE_NONE = 0, -CWAL_TRACE_GROUP_MASK = 0x7F000000, - -CWAL_TRACE_MEM_MASK = 0x01000000, -CWAL_TRACE_MEM_MALLOC = CWAL_TRACE_MEM_MASK | (1 << 0), -CWAL_TRACE_MEM_REALLOC = CWAL_TRACE_MEM_MASK | (1 << 1), -CWAL_TRACE_MEM_FREE = CWAL_TRACE_MEM_MASK | (1 << 2), -CWAL_TRACE_MEM_TO_RECYCLER = CWAL_TRACE_MEM_MASK | (1 << 3), -CWAL_TRACE_MEM_FROM_RECYCLER = CWAL_TRACE_MEM_MASK | (1 << 4), -CWAL_TRACE_MEM_TO_GC_QUEUE = CWAL_TRACE_MEM_MASK | (1 << 5), - -CWAL_TRACE_VALUE_MASK = 0x02000000, -CWAL_TRACE_VALUE_CREATED = CWAL_TRACE_VALUE_MASK | (1 << 0), -CWAL_TRACE_VALUE_SCOPED = CWAL_TRACE_VALUE_MASK | (1 << 1), -CWAL_TRACE_VALUE_UNSCOPED = CWAL_TRACE_VALUE_MASK | (1 << 2), -CWAL_TRACE_VALUE_CLEAN_START = CWAL_TRACE_VALUE_MASK | (1 << 3), -CWAL_TRACE_VALUE_CLEAN_END = CWAL_TRACE_VALUE_MASK | (1 << 4), -CWAL_TRACE_VALUE_CYCLE = CWAL_TRACE_VALUE_MASK | (1 << 5), -CWAL_TRACE_VALUE_INTERNED = CWAL_TRACE_VALUE_MASK | (1 << 6), -CWAL_TRACE_VALUE_UNINTERNED = CWAL_TRACE_VALUE_MASK | (1 << 7), -CWAL_TRACE_VALUE_VISIT_START = CWAL_TRACE_VALUE_MASK | (1 << 8), -CWAL_TRACE_VALUE_VISIT_END = CWAL_TRACE_VALUE_MASK | (1 << 9), -CWAL_TRACE_VALUE_REFCOUNT = CWAL_TRACE_VALUE_MASK | (1 << 10), - -CWAL_TRACE_SCOPE_MASK = 0X04000000, -CWAL_TRACE_SCOPE_PUSHED = CWAL_TRACE_SCOPE_MASK | (1 << 0), -CWAL_TRACE_SCOPE_CLEAN_START = CWAL_TRACE_SCOPE_MASK | (1 << 1), -CWAL_TRACE_SCOPE_CLEAN_END = CWAL_TRACE_SCOPE_MASK | (1 << 2), -CWAL_TRACE_SCOPE_SWEEP_START = CWAL_TRACE_SCOPE_MASK | (1 << 3), -CWAL_TRACE_SCOPE_SWEEP_END = CWAL_TRACE_SCOPE_MASK | (1 << 4), - -CWAL_TRACE_ENGINE_MASK =0X08000000, -CWAL_TRACE_ENGINE_STARTUP = CWAL_TRACE_ENGINE_MASK | (1 << 1), -CWAL_TRACE_ENGINE_SHUTDOWN_START = CWAL_TRACE_ENGINE_MASK | (1 << 2), -CWAL_TRACE_ENGINE_SHUTDOWN_END = CWAL_TRACE_ENGINE_MASK | (1 << 3), - -CWAL_TRACE_FYI_MASK = 0x10000000, -CWAL_TRACE_MESSAGE = CWAL_TRACE_FYI_MASK | (1<<1), - -CWAL_TRACE_ERROR_MASK = 0x20000000, -CWAL_TRACE_ERROR = CWAL_TRACE_ERROR_MASK | (1<<0), - -/** - Contains all cwal_trace_flags_e values except CWAL_TRACE_NONE. -*/ -CWAL_TRACE_ALL = 0x7FFFFFFF/*1..31*/ - -}; -/** - Convenience typedef. -*/ -typedef enum cwal_trace_flags_e cwal_trace_flags_e; - - -#if CWAL_ENABLE_TRACE -typedef struct cwal_trace_state cwal_trace_state; -/** - State which gets passed to a cwal_engine_tracer_f() callback when - cwal_engine tracing is enabled. -*/ -struct cwal_trace_state { - cwal_trace_flags_e event; - int32_t mask; - cwal_rc code_NYI; - cwal_engine const * e; - cwal_value const * value; - cwal_scope const * scope; - void const * memory; - cwal_size_t memorySize; - char const * msg; - cwal_size_t msgLen; - char const * cFile; - char const * cFunc; - int cLine; -}; -#else -typedef char cwal_trace_state; -#endif - -#if CWAL_ENABLE_TRACE -# define cwal_trace_state_empty_m { \ - CWAL_TRACE_NONE/*event*/, \ - 0/*mask*/, CWAL_RC_OK/*code*/, \ - 0/*engine*/,0/*scope*/,0/*value*/, \ - 0/*mem*/,0/*memorySize*/,0/*msg*/,0/*msgLen*/, \ - 0/*cFile*/,0/*cFunc*/,0/*cLine*/ \ - } -#else -# define cwal_trace_state_empty_m 0 -#endif -extern const cwal_trace_state cwal_trace_state_empty; -/** - Converts the given cwal_rc value to "some string", or returns an - unspecified string if rc is not a cwal_rc value. The returned bytes - are always the same for a given value, and static, and are thus - guaranteed to survive at least until main() returns or exit() is - called. - - If passed a non-cwal_rc value then it will delegate the call to - one of its registered fallbacks. - - @see cwal_rc_cstr_fallback() -*/ -char const * cwal_rc_cstr(int rc); - -/** - Functionally identical to cwal_rc_cstr() except that it returns 0 - if rc is not a code known by this library or one of its registered - rc-to-string fallbacks. - - @see cwal_rc_cstr_fallback(). -*/ -char const * cwal_rc_cstr2(int rc); - -/** - Callback signature for a cwal_rc_cstr() fallback. This is intended - to allow downstream API extensions to plug in their own rc codes - into cwal_rc_cstr(), such that certain generic/high-level - algorithms do not need to specifically know about which set of - result codes they might be dealing with (e.g. cwal vs s2). - - Implementations must accept a result code integer. If it is one of - that API's designated codes then the function must return a pointer - to static/immutable memory which contains a human-readable name for - that code (by convention the string form of its enum value), in the - same style that cwal_rc_cstr() does. If it does not recognize the - code then it must return NULL so that the next fallback (if any) - can be tried. - - @see cwal_rc_cstr_fallback() -*/ -typedef char const * (*cwal_rc_cstr_f)(int); - -/** - Installs a fallback handler for cwal_rc_str() and cwal_rc_cstr2(), - such that if those routines cannot answer a request then they will - try one of the registered fallbacks. - - CAVEATS: - - - The list of callbacks has a hard-coded maximum size and exceeding - it will trigger an assert() (in debug builds) or an abort() (in - non-debug). It is not expected that more than one, maybe two, such - fallbacks will ever be useful/necessary in a given binary. - - - This function is not thread-safe. It is intended to be called - once, maybe twice, during an app's main() and then never touched - again. - - - Fallbacks are called in the _reverse_ order of which they are - registered. - */ -void cwal_rc_cstr_fallback(cwal_rc_cstr_f); - -/** - Type IDs used by cwal. They correspond roughly to - JavaScript/JSON-compatible types, plus some extensions. - - These are primarily in the public API to allow O(1) client-side - dispatching based on cwal_value types, as opposed to using - O(N) if/else if/else. - - Note that the integer values assigned here are not guaranteed to - stay stable: they are intended only to assist human-level debug - work in the cwal code. -*/ -enum cwal_type_id { -/** - GCC likes to make enums unsigned at times, which breaks - strict comparison of integers with enums. Soooo... -*/ -CWAL_TYPE_FORCE_SIGNED_ENUM = -1, -/** - The special "undefined" value constant. - - Its value must be 0 for internal reasons. -*/ -CWAL_TYPE_UNDEF = 0, -/** - The special "null" value constant. -*/ -CWAL_TYPE_NULL = 1, -/** - The bool value type. -*/ -CWAL_TYPE_BOOL = 2, -/** - The integer value type, represented in this library - by cwal_int_t. -*/ -CWAL_TYPE_INTEGER = 3, -/** - The double value type, represented in this library - by cwal_double_t. -*/ -CWAL_TYPE_DOUBLE = 4, -/** The immutable string type. This library stores strings - as immutable UTF8. -*/ -CWAL_TYPE_STRING = 5, -/** The "Array" type. */ -CWAL_TYPE_ARRAY = 6, -/** The "Object" type. */ -CWAL_TYPE_OBJECT = 7, -/** The "Function" type. */ -CWAL_TYPE_FUNCTION = 8, -/** A handle to a generic "error" or "exception" type. - */ -CWAL_TYPE_EXCEPTION = 9, -/** A handle to a client-defined "native" handle. */ -CWAL_TYPE_NATIVE = 10, -/** - The "buffer" type, representing a generic memory buffer. -*/ -CWAL_TYPE_BUFFER = 11, - -/** - Represents a hashtable type (cwal_hash), which is similar to - OBJECT but guarantees a faster property store. -*/ -CWAL_TYPE_HASH = 12, - -/** - A pseudo-type-id used internaly, and does not see use in the - public API (it might at some future point). -*/ -CWAL_TYPE_SCOPE = 13, - -/** - KVP (Key/Value Pair) is a pseudo-type-id used internally, and - does not see use in the public API. -*/ -CWAL_TYPE_KVP = 14, - -/** - Used _almost_ only internally. The only public API use - for this entry is with cwal_engine_recycle_max() and friends. -*/ -CWAL_TYPE_WEAK_REF = 15, - -/** - Used only internally during the initialization of "external - strings." After initializations these take the type - CWAL_TYPE_STRING. The only public API use for this entry is - with cwal_engine_recycle_max() and friends. -*/ -CWAL_TYPE_XSTRING = 16, - -/** - Used only internally during the initialization of "z-strings." - After initializations these take the type CWAL_TYPE_STRING. - The only public API use for this entry is with - cwal_engine_recycle_max() and friends. -*/ -CWAL_TYPE_ZSTRING = 17, - -/** - A type which is true in a boolean context but never compares - equivalent to any other value, including true. Used as sentries or - guaranteed-unique property keys. They optionally wrap a single - arbitrary value, like a single-child container. -*/ -CWAL_TYPE_UNIQUE = 18, - -/** - An Array variant optimized for/restricted to a fixed size - and with different comparison semantics. -*/ -CWAL_TYPE_TUPLE = 19, - -/** - Only used internally for metrics tracking of memory stored in - cwal_list::list. -*/ -CWAL_TYPE_LISTMEM = 20, - -/** CWAL_TYPE_end is internally used as an iteration sentinel and MUST - be the last entry in this enum. */ -CWAL_TYPE_end - -}; -/** - Convenience typedef. -*/ -typedef enum cwal_type_id cwal_type_id; - -/** - Convenience typedef. -*/ -typedef struct cwal_function cwal_function; - -/** @struct cwal_callback_args - - A type holding arguments generated from "script" code which - call()s a Function value. Instances of this type are created only - by the cwal_function_call() family of functions, never by client - code. The populated state is, via cwal_function_call() and - friends, passed to the cwal_callback_f() implementation which is - wrapped by the call()'d Function. -*/ -struct cwal_callback_args{ - /** - The engine object making the call. - */ - cwal_engine * engine; - /** - The scope in which the function is called. - */ - cwal_scope * scope; - /** - The "this" value for this call. - */ - cwal_value * self; - /** - In certain call contexts (namely interceptors), this gets set - to the container in which the callee property was found. It may - be self or some protototype of self, and may be of a distinctly - different type. - */ - cwal_value * propertyHolder; - /** - The function being called. - */ - cwal_function * callee; - /** - State associated with the function by native client code. - This is set via cwal_new_function() or equivalent. - - @see cwal_args_state() - @see cwal_function_state_get() - */ - void * state; - - /** - A client-provided "tag" which can be used to determine if - this->state is of the type the client expects. This allows us - to do type-safe conversions from (void*) to (T*). This value is - set via the cwal_new_function() family of APIs. - - In practice this value is simply the pointer to an arbitrary - file-static data structure. e.g.: - - @code - // value is irrelevant - we only use the pointer - static const my_type_id = 1; - @endcode - - Then we internally use the address of my_type_id as the - stateTypeID when binding native data to a function instance via - cwal_new_function(). - - @see cwal_args_state() - @see cwal_function_state_get() - */ - void const * stateTypeID; - - /** - Number of arguments. - */ - uint16_t argc; - - /** - Array of arguments argc items long. - */ - cwal_value * const * argv; -}; -#define cwal_callback_args_empty_m \ - {0/*engine*/,0/*scope*/,0/*self*/,0/*propertyHolder*/, \ - 0/*callee*/,0/*state*/,NULL/*stateTypeID*/, \ - 0/*argc*/,0/*argv*/ \ - } -extern const cwal_callback_args cwal_callback_args_empty; - -/** - Callback function interface for cwal "script" functions. args - contains various state information related to the call. The - callback returns a value to the framework by assigning *rv to it - (assigning it to NULL is equivalent to assigning it to - cwal_value_undefined()). Implementations can rely on rv being - non-NULL but must not rely on any previous contents of *rv. In - practice, callbacks are passed a pointer to an initially-NULL - value, and callback implementations will, on success, set *rv to - the result value. - - Callbacks must return 0 on success, CWAL_RC_EXCEPTION if they set - the cwal exception state, or (preferably) one of the other relevant - CWAL_RC values on a genuine error. Practice strongly suggests that - implementations should assign a new value to *rv only if they - "succeed" (for a client-dependent definition of "succeed"), and - "really shouldn't" assign it a new value if they "fail" (again, - where "fail" sometimes has as client-specific meaning). - - This interface is the heart of client-side cwal bindings, and any - non-trivial binding will likely have many functions of this type. - - ACHTUNG: it is critical that implementations return CWAL_RC_xxx - values, as the framework relies on several specific values to - report information to the framework and to scripting engines - built on it. e.g. CWAL_RC_RETURN, CWAL_RC_OOM, CWAL_RC_BREAK, - and CWAL_RC_EXCEPTION are often treated specially. If clients - return non-cwal result codes from this function, cwal may get - confused and downstream behaviour is undefined. -*/ -typedef int (*cwal_callback_f)( cwal_callback_args const * args, cwal_value ** rv ); - -/** - Framework-wide interface for finalizer functions for memory managed - by/within a cwal_engine instance. Generally speaking it must - semantically behave like free(3), but if the implementor knows what - s/he's doing these can also be used for "cleanup" (as opposed to - free()ing). - - The memory to free/clean up is passed as the second argument. First - argument is the cwal_engine instance which is (at least ostensibly) - managing that memory, and some implementations permit this to be - NULL. - - For semantic compatibility with free(), implementations must - accept NULL as the 2nd argument and must "do nothing" if - passed a NULL second argument. -*/ -typedef void (*cwal_finalizer_f)( cwal_engine * e, void * m ); - -/** - A cwal_finalizer_f() implementation which requires that s be a - (FILE*). If s is not NULL and not one of (stdin, stdout, stderr) - then this routine fclose()s it, otherwise it has no side - effects. This implementation ignores the e parameter. If s is NULL - this is a harmless no-op. Results are undefined if s is not NULL - and is not a valid opened (FILE*). -*/ -void cwal_finalizer_f_fclose( cwal_engine * e, void * s ); - -/** - Generic list type. - - It is up to the APIs using this type to manage the "count" member - and use cwal_list_reserve() to manage the "alloced" member. - - @see cwal_list_reserve() - @see cwal_list_append() -*/ -struct cwal_list { - /** - Array of entries. It contains this->alloced - entries, this->count of which are "valid" - (in use). - */ - void ** list; - /** - Number of "used" entries in the list. - - Reminder to self: we could reasonably use a 32-bit size type - even in 64-bit builds, and that would save 8 bytes per array. - It would require many changes to list-related API signatures - which take cwal_size_t (which may be larger than 32-bits). - */ - cwal_midsize_t count; - /** - Number of slots allocated in this->list. Use - cwal_list_reserve() to modify this. Doing so - might move the this->list pointer but the values - it points to will stay stable. - */ - cwal_midsize_t alloced; - - /** - An internal consistency/misuse marker to let us know that this - list is currently undergoing iteration/visitation and must - therefore not be modified. Do not use this from client-level - code. - */ - bool isVisiting; -}; -typedef struct cwal_list cwal_list; -/** - Empty-initialized cwal_list object. -*/ -#define cwal_list_empty_m { NULL, 0, 0, false } -/** - Empty-initialized cwal_list object. -*/ -extern const cwal_list cwal_list_empty; - -/** - A helper class for holding arbitrary state with an optional - associated finalizer. The interpretation of the state and the - finalizer's requirements are context-specific. -*/ -struct cwal_state { - /** - The raw data. Its interpretation is of course very - context-specific. The typeID field can be used to "tag" - this value with type info so that clients can ensure that - they do not mis-cast this pointer. - */ - void * data; - /** - An arbitrary "tag" value which clients can use to indicate - that this->data is of a specific type. In practice this is - normally set to the address of some internal structure or - value which is not exposed via public APIs. - */ - void const * typeID; - /** - Cleanup function for this->data. It may be NULL if - this->data has no cleanup requirements or is owned by - someone else. - */ - cwal_finalizer_f finalize; -}; -/** Convenience typedef. */ -typedef struct cwal_state cwal_state; -/** - Empty-initialized cwal_state object. -*/ -#define cwal_state_empty_m { NULL, NULL, NULL } -/** - Empty-initialized cwal_state object. -*/ -extern const cwal_state cwal_state_empty; - -/** - A generic output interface intended (primarily) to be used via - cwal_engine_vtab via cwal_output(). Script-side code which - generates "console-style" output intended for the user should use - cwal_output() to put it there. An implementation of this interface - is then responsible for sending the output somewhere. - - Must return 0 on success or an error code (preferably from cwal_rc) - on error. Because an output mechanism can modify the output, there - is not direct 1-to-1 mapping of input and output lengths, and thus - it returns neither of those. - - The state parameter's meaning is implementation-specific. e.g. - cwal_output_f_FILE() requires it to be an opened-for-writing - (FILE*). It is up to the caller to provide a state value which is - appropriate for the given cwal_output_f() implementation. -*/ -typedef int (*cwal_output_f)( void * state, void const * src, cwal_size_t n ); - -/** - Library-wide interface for allocating, reallocating, freeing memory. - It must semantically behave like realloc(3) with the minor clarification - that the free() operation (size==0) it must return NULL instead of - "some value suitable for passing to free()." - - The state argument (typically) comes from the state member of the - cwal_engine_vtab which holds one of these functions. The (mem,size) - parameters are as for realloc(3). In summary: - - If (mem==NULL) then it must semantically behave like malloc(3). - - If (size==0) then it must sematically behave like free(3). - - If (mem!=NULL) and (size!=0) then it must semantically behave like - realloc(3). -*/ -typedef void * (*cwal_engine_realloc_f)( void * state, void * mem, cwal_size_t size ); - -/** - A callback for use in low-level tracing of cwal-internal - activity. It is only used when tracing is enabled via the - CWAL_ENABLE_TRACE configuration macro. - - 2020-02-10: the cwal_engine tracing features are extremely - low-level and produce absolute tons of output. They are intended - solely to assist cwal's developer in debugging (in particular - during early development of the library). It has, as of this - writing, been 5+ years since tracing has been enabled in a cwal - build, and there are no guarantees that the traced data have been - kept entirely relevant vis-a-vis changes in the engine since then. - i.e. do not use tracing unless you are trying to decode the cwal - internals. -*/ -typedef void (*cwal_engine_tracer_f)( void * state, cwal_trace_state const * event ); -/** - The combined state for a cwal_engine tracer. It is only used when - tracing is enabled via the CWAL_ENABLE_TRACE configuration macro. -*/ -struct cwal_engine_tracer{ - /** Callback for the tracer. */ - cwal_engine_tracer_f trace; - /** A finalizer for this->state. If not NULL, it gets called - during finalization of its associated cwal_engine instance. */ - void (*close)( void * state ); - /** Optional client-specific state for tracing. Gets passed, - without interpretation, to the callback. */ - void * state; -}; -typedef struct cwal_engine_tracer cwal_engine_tracer; -#define cwal_engine_tracer_empty_m { 0, 0, 0 } -extern const cwal_engine_tracer cwal_engine_tracer_empty; -extern const cwal_engine_tracer cwal_engine_tracer_FILE; -void cwal_engine_tracer_f_FILE( void * filePtr, cwal_trace_state const * event ); -void cwal_engine_tracer_close_FILE( void * filePtr ); - -/** - Part of the cwal_engine_vtab interface, this - defines the API for a memory allocator used - by the cwal_engine API. -*/ -struct cwal_allocator{ - /** - The memory management function. cwal_engine - uses this exclusively for all de/re/allocations. - */ - cwal_engine_realloc_f realloc; - /** - State for the allocator. Its requirements/interpretation - depend on the concrete realloc implementation. - */ - cwal_state state; -}; -/** Convenience typedef. */ -typedef struct cwal_allocator cwal_allocator; -/** Empty-initialized cwal_allocator object. */ -#define cwal_allocator_empty_m { 0, cwal_state_empty_m } -/** Empty-initialized cwal_allocator object. */ -extern const cwal_allocator cwal_allocator_empty; -/** cwal_allocator object configured to use realloc(3). */ -extern const cwal_allocator cwal_allocator_std; - -/** - Part of the cwal_engine_vtab interface, this defines a generic - interface for outputing "stuff" (presumably script-generated - text). The intention is that script-side output should all go - through a common channel, to provide the client an easy to to - intercept/redirect it, or to add layers like output buffer - stacks (this particular output interface originates from such - an implementation in TH1). -*/ -struct cwal_outputer{ - cwal_output_f output; - /** - Intended to flush the output channel, if needed. If not - needed, this member may be NULL, in which case it is - ignored, or it may simply return 0. - - It is passed this.state.data as its argument. - */ - int (*flush)( void * state ); - cwal_state state; -}; -typedef struct cwal_outputer cwal_outputer; -/** Empty-initialized cwal_outputer object. */ -#define cwal_outputer_empty_m { 0, NULL, cwal_state_empty_m } -/** Empty-initialized cwal_outputer object. */ -extern const cwal_outputer cwal_outputer_empty; -/** - cwal_outputer object set up to use cwal_output_f_FILE. After - copying this value, set it the copy's state.data to a (FILE*) to - redirect it. If its file handle needs to be closed during cleanup, - the state.finalize member should be set to a function which will - close the file handle (e.g. cwal_finalizer_f_fclose()). -*/ -extern const cwal_outputer cwal_outputer_FILE; - -/** - Typedef for a predicate function which tells a cwal_engine - whether or not a given string is "internable" or not. Clients - may provide an implementation of this via - cwal_engine_vtab::internable. If interning is enabled, when a new - string is created, this function will be called and passed: - - - The state pointer set in cwal_engine_vtab::internable::state. - - - The string which is about to be created as a cwal_string. - - - The length of that string. - - This function is only called for non-empty strings. Thus len is - always greater than 0, str is never NULL, and never starts with - a NUL byte. Client implementations need not concern themselves - with NULL str or a len of 0. - - Once a given series of bytes have been interned, this function - will not be called again for that same series of bytes as long - as there is at least one live interned reference to an - equivalent string. -*/ -typedef bool (*cwal_cstr_internable_predicate_f)( void * state, char const * str, cwal_size_t len ); - -/** - The default "is this string internable?" predicate. The state parameter is ignored. - - The default impl uses only a basic length cutoff point to - determine "internalizableness." - - @see cwal_cstr_internable_predicate_f() -*/ -bool cwal_cstr_internable_predicate_f_default( void * state, char const * str, cwal_size_t len ); - -/** - Configuration used to optionally cap the memory allocations made - via a cwal_engine instance (i.e. via cwal_malloc() and - cwal_realloc()). In client apps, these are set up via the - cwal_engine_vtab::memcap member of the vtab used to initialize a - cwal_engine. - - It is important that these config values not be modified after - calling cwal_engine_init(), or undefined behaviour will ensue. - - Some memory-capping features require knowing exactly how much - memory the vtab's realloc function has doles out and freed over - time, and the only way to do that is to over-allocate all - allocations and store their sizes in that memory. Over-allocation - is only enabled when an option which requires it is enabled. - - The overhead imposed by over-allocation _is_ counted against any - byte totals configured via this type. It also, when using - CWAL_SIZE_T_BITS=64, restricts allocation sizes to a 32-bit range - (as an optimization/compensation for 32-bit builds). - - - Client-reported memory totals (via cwal_engine_adjust_client_mem()) - are not counted by this mechanism. In practice, client-reported - memory is allocated via cwal_malloc() or cwal_realloc(), and those - track/honor the configured caps. -*/ -struct cwal_memcap_config { - /** - Caps the cumulative total memory allocated by the engine. - - Rejects (re)allocations which would take the absolute allocated - byte total (including the memcap tracking overhead) over this - value. Such a condition is not recoverable, as the total only - increases (never decreases) over time. - - Set to 0 to disable. - - Requires (forces enabling of) over-allocation so that - re-allocations can be counted consistently. - */ - uint64_t maxTotalMem; - - /** - Caps the cumulative total number of memory allocations made by - the engine. - - Rejects _new_ allocations which would take the current total - allocation over this value. This condition is unrecoverable, as - the total only increases (never decreases) over time. - - Set to 0 to disable. - */ - uint64_t maxTotalAllocCount; - - /** - Caps the concurrent total memory usage. - - Rejects (re)allocations which would take the current byte total - (including the memcap tracking overhead) over this value. - - Set to 0 to disable. - - Requires (forces enabling of) over-allocation. - */ - cwal_size_t maxConcurrentMem; - - /** - Caps the concurrent total memory allocation count. - - Rejects _new_ allocations (not reallocs) which would take - the current total allocation count over this value. - - Set to 0 to disable. - */ - cwal_size_t maxConcurrentAllocCount; - - /** - Caps the size of any single allocation. - - If a single allocation request is larger than this, the - allocator signals an OOM error instead of allocating. - - Set to 0 to disable. - - Design note: this is explicitly uint32_t, not cwal_size_t, so - that 32-bit builds with CWAL_SIZE_T_BITS=64 do not need to - over-allocate by 8 bytes. - */ - uint32_t maxSingleAllocSize; - - /** - If true (non-0) then during cwal_engine_init() memory - allocation size tracking (and its required over-allocation) is - enabled regardless of which other limits are enabled. - - When over-allocation is enabled, apps can generally expect a - small reduction in malloc counts and a slight increase in - peak/total memory usage from scripts. - - When recycling is _disabled_, enabling this option can be - notably more memory-costly. - */ - char forceAllocSizeTracking; -}; -typedef struct cwal_memcap_config cwal_memcap_config; -/** - Initialized-with-defaults cwal_memcap_config instance, intended for - const-copy initialization. -*/ -#define cwal_memcap_config_empty_m { \ - 0/*maxTotalMem*/, 0/*maxTotalAllocCount*/, \ - 0/*maxConcurrentMem*/, 0/*maxConcurrentAllocCount*/, \ - /*maxSingleAllocSize*/((CWAL_SIZE_T_BITS==16) \ - ? 0x7FFF/*32k*/ \ - : (cwal_size_t)(1 << 24/*16MB*/)), \ - 0/*forceAllocSizeTracking*/ \ - } -/** - Cleanly initialized cwal_memcap_config instance intended for - non-const copy initialization. -*/ -extern const cwal_memcap_config cwal_memcap_config_empty; - -/** - A piece of the infrastructure for hooking into cwal_scope - push/pop operations performed on a cwal_engine. - - If cwal_engine_vtab::hook::scope_push is not NULL, it gets called - during cwal_scope_push(), _after_ cwal has set up the scope as its - current scope. Thus, during this callback, cwal_scope_current_get() - will return the being-pushed scope. If this function returns non-0, - pushing of the scope will fail, cwal will immediately pop the scope - _without_ calling the scope_pop hook, and the result of this - callback will be returned to the caller of cwal_scope_push(). If - this hook succeeds (returns 0) then cwal will call the - cwal_engine_vtab::hook::scope_pop callback when the scope is - popped. - - This hook gets passed the just-pushed scope and any state set in - cwal_engine_vtab::hook::scope_state. - - It is strictly illegal for this routine to call cwal_scope_push(), - cwal_scope_pop(), or any other routine which modifies the scope - stack. - - One interesting (but solvable) problem: cwal necessarily pushes a - scope during initialization, before the init hook can be triggered - and before the engine can be used with cwal_malloc(). That is - likely to pose a problem for client code. This means that the - client may, depending on where this hook get connected, get one - more pop call than push calls. Any memory the client wants to - allocate before cwal_engine_init() can set up the engine must be - done directly via the engine's vtab::allocator member, rather than - via cwal_malloc(), because cwal_malloc()'s behaviour is undefined - before the engine has been initialized. In the case of s2 the - problem more or less resolves itself: when s2 starts up it pops - cwal's installed top scope so that it can push its own scope (it's - always done this, even before this change). The timing of that - allows for popping all of the scopes, setting up the push/pop - hooks, and then pushing its own top-most scope, which then goes - through the push hook. - - Added 20181123. - - @see cwal_scope_hook_pop_f() - @see cwal_engine_vtab -*/ -typedef int (*cwal_scope_hook_push_f)( cwal_scope * s, void * clientState ); - -/** - If cwal_engine_vtab::hook::scope_pop is not NULL, it gets called - during cwal_scope_pop(), _before_ cwal has removed the scope from - the stack. - - This hook gets passed the being-popped scope and any state set in - cwal_engine_vtab::hook::scope_state. - - It is strictly illegal for this routine to call cwal_scope_push(), - cwal_scope_pop(), or any other routine which modifies the scope - stack. - - Reminder to self: should we pass any being-propagated result value - (via cwal_scope_pop2()), if any, to this routine? i don't think we - need to, but that's something to keep in mind as a possibility. - - Added 20181123. - - @see cwal_scope_hook_push_f() - @see cwal_engine_vtab -*/ -typedef void (*cwal_scope_hook_pop_f)( cwal_scope const *, void * clientState ); - -/** - The "virtual table" of cwal_engine instances, providing the - functionality which clients can override with their own - implementations. - - Multiple cwal_engine instances may share if vtab instance if - and only if: - - - The state member (if used) may legally share the same values - across engines AND state::finalize() does not destroy - state::state (if it does, each engine will try to clean it up). - - - The vtab does not make values from one engine visible to - another. This will lead to Undefined Behaviour. - - - The app is single-threaded OR... - - - all access to cwal_engine_vtab is otherwise serialized via - a client-side mutex OR... - - - the vtab instance is otherwise "immune" the threading effects - (e.g. because its underlying APIs do the locking). - - Mutex locking is not a feature planned for the cwal API. -*/ -struct cwal_engine_vtab { - /* - - Potential TODOs: - - void (*shutdown)( cwal_engine_vtab * self ); - - shutdown() would be called when an engine is cleaned up (after - it has finished cleaning up), instead of state.finalize(), and - would be responsible for cleaning up allocator.state and - outputer.state, if needed. - */ - - /** - The memory allocator. All memory allocated in the context - of a given cwal_engine is (re)allocated/freed through it's - vtab's allocator member. - */ - cwal_allocator allocator; - - /** - The handler which receives all data passed to - cwal_output(). - */ - cwal_outputer outputer; - - /** - Handles cwal_engine tracing events (if tracing is enabled). - */ - cwal_engine_tracer tracer; - - /** - A place to store client-defined state. The engine places - no value on this, other than to (optionally) clean it up - when the engine is finalized. - - The finalize() method in the state member is called when an - engine using this object shuts down. Because it happens - right after the engine is destroyed, it is passed a NULL - engine argument. Thus it is called like: - - vtab->state.finalize( NULL, vtab->state.data ); - - This of course means that a single vtab cannot differentiate - between multiple engines for the shutdown phase, and we - might have to add reference counting to the vtab in order to - account for this (currently it would need to be somewhere in - state.data). - */ - cwal_state state; - - /** - A place to add client-side hooks into engine - post-initialization, and possibly for other events at some - point (if we can find a use for it). - */ - struct { - /** - May be used to add post-init code to cwal_engine - instances. If this member is not 0 then it is called - right after cwal_engine_init() is finished, before it - returns. If this function returns non-0 then - initialization fails, the engine is cleaned up, and the - return value is passed back to the caller of - cwal_engine_init(). - - Note that the vtab parameter is guaranteed to be the - vtab which initialized e. e->vtab==vtab is guaranteed - to be true, but client code "should really" use the - passed-in vtab pointer instead of relying on the - private/internal e->vtab member (its name/placement may - change). - - This is only called one time per initialization of an engine, - so the client may (if needed) clean up the init_state member - (i.e. vtab->hook->init_state). - */ - int (*on_init)( cwal_engine * e, cwal_engine_vtab * vtab ); - /** - Arbitrary state passed to on_init(). - */ - void * init_state; - - /** - Gets triggered when cwal_scope_push() and friends are - called. This callback may be NULL. Clients which need to - keep their own scope-level state in sync with cwal's scope - levels should create a pair of scope push/pop hook routines - to manage that state. - - See cwal_scope_hook_push_f() for the docs. - */ - cwal_scope_hook_push_f scope_push; - - /** - Gets triggered when cwal_scope_pop() and friends - are called. - - See cwal_scope_hook_pop_f() for the docs. - */ - cwal_scope_hook_pop_f scope_pop; - - /** - scope_state is passed as-is to scope_push() and - scope_pop() every time they are called. - - The cwal engine does not manage or own this state. It is up - to the client to manage it, if needed. In almost(?) every - conceivable case in which this state is used, the client - will have to (because of allocator availability) set it - AFTER cwal has initialized, which means after cwal has - pushed its first scope. - */ - void * scope_state; - } hook; - - /** - Holds state for determining whether a given string is - internable or not. It is not expected that such state will need - to be finalized separately, thus this member holds no finalizer - function. - */ - struct { - /** - The is-internable predicate. If NULL, interning is - disabled regardless of any other considerations - (e.g. the CWAL_FEATURE_INTERN_STRINGS flag). - - @see cwal_cstr_internable_predicate_f() - */ - cwal_cstr_internable_predicate_f is_internable; - /** - State to be passed as the first argument to is_internable(). - */ - void * state; - } interning; - - /** - Memory capping configuration. - */ - cwal_memcap_config memcap; -}; -/** - Empty-initialized cwal_engine_vtab object. -*/ -#define cwal_engine_vtab_empty_m { \ - cwal_allocator_empty_m, \ - cwal_outputer_empty_m, \ - cwal_engine_tracer_empty_m, \ - cwal_state_empty_m, \ - {/*hook*/ \ - NULL/*on_init()*/,0/*init_state*/, \ - NULL/*scope_push*/,NULL/*scope_pop*/, \ - NULL/*scope_state*/}, \ - {/*interning*/ cwal_cstr_internable_predicate_f_default, NULL}, \ - cwal_memcap_config_empty_m \ -} - -/** - Empty-initialized cwal_engine_vtab object. -*/ -extern const cwal_engine_vtab cwal_engine_vtab_empty; - -/** - A cwal_realloc_f() implementation which uses the standard C - memory allocators. -*/ -void * cwal_realloc_f_std( void * state, void * m, cwal_size_t n ); - -/** - A cwal_output_f() implementation which requires state to be - a valid (FILE*) opened in write mode. It sends all output - to that file and returns n on success. If state is NULL then - this is a harmless no-op. -*/ -int cwal_output_f_FILE( void * state, void const * src, cwal_size_t n ); - -/** - A cwal_output_f() implementation which requires that state be a valid - (cwal_engine*). This outputer passes all output to cwal_output() using - the given cwal_engine instance. -*/ -int cwal_output_f_cwal_engine( void * state, void const * src, cwal_size_t n ); - -/** - A cwal_outputer::flush() implementation whichr equires that f - be a (FILE*). For symmetry with cwal_output_f_FILE, if !f then - stdout is assumed. -*/ -int cwal_output_flush_f_FILE( void * f ); - -/** - A state type for use with cwal_output_f_buffer(). - - @see cwal_output_f_buffer() -*/ -struct cwal_output_buffer_state { - cwal_engine * e; - cwal_buffer * b; -}; - -/** - Convenience typedef. -*/ -typedef struct cwal_output_buffer_state cwal_output_buffer_state; - -/** - Empty-initialized cwal_output_buffer_state instance. -*/ -extern const cwal_output_buffer_state cwal_output_buffer_state_empty; - -/** - A cwal_output_f() implementation which requires state to be a - valid (cwal_output_buffer_state*), that state->e points to a - valid (cwal_engine*), and that state->b points to a valid - (cwal_buffer*). It sends all output to state->b, expanding the - buffer as necessary, and returns 0 on success. Results are - undefined if state is not a cwal_output_buffer_state. -*/ -int cwal_output_f_buffer( void * state, void const * src, cwal_size_t n ); - -/** - A cwal_output_f() implementation which requires state to be a - (cwal_outputer*). This routine simply redirects all (src,n) input - to the cwal_outputer::output() method. Results are undefined if - state is not a cwal_outputer. -*/ -int cwal_output_f_cwal_outputer( void * state, void const * src, cwal_size_t n ); - -/** - A cwal_finalizer_f() which requires that m be a - (cwal_output_buffer_state*). This function calls - cwal_buffer_reserve(e, state->buffer, 0) to free up the - buffer's memory, then zeroes out state's contents. - - In theory this can be used together with cwal_output_f_buffer() - and cwal_outputer to provide buffering of all - cwal_output()-generated output, but there's a chicken-egg - scenario there, in that the outputer "should" be set up before - the engine is intialized. In this case it has to be modified - after the engine is intialized because the engine is part of - the outputer's state. -*/ -void cwal_output_buffer_finalizer( cwal_engine * e, void * bufState ); - -/** - A cwal_engine_vtab instance which can be bitwise copied to inialize - a "basic" vtab instance for use with cwal_engine_init(). It uses - cwal_allocator_std for its memory, cwal_outputer_FILE for output, - and cwal_finalizer_f_fclose() as its output file finalizer. - To enable output, the client must simply assign an - opened (FILE*) handle to the vtab's outputer.state.state. - To remove the finalizer and take over responsibility - for closing the stream, set outputer.state.finalize - to 0. -*/ -extern const cwal_engine_vtab cwal_engine_vtab_basic; - -/** - Allocates n bytes of memory in the context of e. - - The returned memory is "associated with" (but strictly owned by) e - and is owned (or shared with) the caller, who must eventually pass - it to cwal_free() or cwal_realloc(), passing the same engine - instance as used for the allocation. - - It is NEVER legal to share malloc/free/realloc memory across - engine instances, even if they use the same allocator, because - doing so can lead to "missing" entries in one engine or the - other and mis-traversal of Value graphs. - - It is NEVER legal to call this before the given engine - has been initialized via cwal_engine_init(). -*/ -void * cwal_malloc( cwal_engine * e, cwal_size_t n ); - -/** - Works similarly to cwal_malloc(), but first tries to pull a memory - chunk from the chunk recycler, looking for a chunk of size n or - some "reasonable" factor larger (unspecified, but less than - 2*n). If it cannot find one, it returns the result of passing (e,n) - to cwal_malloc(). - - When clients are done with the memory, they "should" pass it to - cwal_free2(), passing it the same value for n (which may recycle - it), but they "may" alternately pass it to cwal_free() (which will - not recycle it). They "must" do one or the other. - - It is NEVER legal to call this before the given engine - has been initialized via cwal_engine_init(). - - Minor caveat: when recycling a larger chunk, the client doesn't - know how much larger than n it is. Whether or not cwal really knows - that depends on whether over-allocating memory capping is enabled - or not. If so, it is able to notice the difference when the memory - is passed to cwal_free2() and will recover those "slack" bytes. If - not, those slack bytes will be "lost" if/when they land back in the - chunk recycler, in that the recycler will not know about the slack - bytes at the end. The effect _could_ be, depending on usage, that - such a block gets smaller on each recycling trip, but in practice - that has never been witnessed to be a problem (and even if it - was/is, _any_ re-use of the memory is a win, so it's not "really" a - problem). -*/ -void * cwal_malloc2( cwal_engine * e, cwal_size_t n ); - -/** - Frees memory allocated via cwal_malloc() or cwal_realloc(). - - e MUST be a valid, initialized cwal_engine instance. - - If !m then this is a no-op. - - @see cwal_free2(). -*/ -void cwal_free( cwal_engine * e, void * m ); - -/** - This alternate form of cwal_free() will put mem in the recycling - bin, if possible, else free it immediately. sizeOfMem must be the - size of the memory block. If mem or sizeOfMem it is 0 then this - function behaves like cwal_free()). - - Recycled memory goes into a pool used internally for various forms - of allocations, e.g. buffers and arrays. -*/ -void cwal_free2( cwal_engine * e, void * mem, cwal_size_t sizeOfMem ); - -/** - Works as described for cwal_realloc_f(). See cwal_malloc() for - important notes. -*/ -void * cwal_realloc( cwal_engine * e, void * m, cwal_size_t n ); - -/** Convenience typedef. */ -typedef struct cwal_exception_info cwal_exception_info; - -/** - NOT YET USED. - - Holds error state information for a cwal_engine - instance. -*/ -struct cwal_exception_info { - /** - Current error code. - */ - cwal_rc code; - /** - Length (in bytes) of cMsg. - */ - cwal_size_t msgLen; - /** - Pointer to string memory not owned by the engine but which - must be guaranteed to live "long enough." If zMsg is set - then this must point to zMsg's. This is primarily a malloc - optimization, to allow us to point to strings we know are - static without having to strdup() them or risk accidentally - free()ing them. - */ - char const * cMsg; - /** - Dynamically-allocated memory which is owned by the containing - engine and might be freed/invalidated on the next call - into the engine API. - */ - char * zMsg; - /** - Error value associated with the error. This would - presumably be some sort of language-specific error type, - or maybe a cwal_string form of cMsg. - */ - cwal_value * value; - - /* TODO?: stack trace info, if tracing is on. */ -}; - -/** - Empty-initialized cwal_exception_info object. -*/ -#define cwal_exception_info_empty_m { \ - CWAL_RC_OK /*code*/, \ - 0U /*msgLen*/, \ - 0 /*cMsg*/, \ - 0 /*zMsg*/, \ - 0 /*value*/ \ - } -/** - Empty-initialized cwal_exception_info object. -*/ -extern const cwal_exception_info cwal_exception_info_empty; - - -/** @internal - - Internal part of the cwal_ptr_table construct. Each - cwal_ptr_table is made up of 0 or more cwal_ptr_page - instances. Each slot in a page is analog to a hash code, and - hash code collisions are resolved by creating a new page (as - opposed to linking the individual colliding items into a list - as a hashtable would do). -*/ -struct cwal_ptr_page { - /** List of pointers, with a length specified by the containing - cwal_ptr_table::hashSize. - */ - void ** list; - /** - Number of live entries in this page. - */ - uint16_t entryCount; - /** - Link to the next entry in a linked list. - */ - struct cwal_ptr_page * next; -}; -/** Convenience typedef. */ -typedef struct cwal_ptr_page cwal_ptr_page; - -/** @internal - - A "key-only" hashtable, the intention being, being able to - quickly answer the question "do we know about this pointer - already?" It is used for tracking interned strings and weak - references. It was originally conceived to help track cycles - during traversal, but it is not used for that purpose. -*/ -struct cwal_ptr_table{ - /** - The number of (void*) entries in each page. - */ - uint16_t hashSize; - /** - A "span" value for our strange hash function. Ideally this - value should be the least common sizeof() shared by all values - in the table, and it degrades somewhat when using mixed-size - values (which most cwal_values actually are, internally, as a - side-effect of malloc() reduction optimizations). For tables - where the sizeof() is the same for all members this type should - provide near-ideal access speed and a fair memory cost if - hashSize can be predicted (which it most likely cannot). - */ - uint16_t step; - /** - Where we keep track of pages in the table. - */ - struct { - /** - First page in the list. - */ - cwal_ptr_page * head; - /** - Last page in the list. We keep this pointer only to - speed up a small handful of operations. - */ - cwal_ptr_page * tail; - } pg; - /** - Internal allocation marker. - */ - void const * allocStamp; -}; -typedef struct cwal_ptr_table cwal_ptr_table; -/** - Empty-initialized cwal_ptr_table, for use in in-struct - initialization. -*/ -#define cwal_ptr_table_empty_m { \ - 0/*hashSize*/, \ - 0/*step*/, \ - {/*pg*/ NULL/*head*/, NULL/*tail*/}, \ - NULL/*allocStamp*/ \ - } -/** - Empty-initialized cwal_ptr_table, for use in copy - initialization. -*/ -extern const cwal_ptr_table cwal_ptr_table_empty; - -/** - Holds the state for a cwal scope. Scopes provide one layer of - the cwal memory model, and are modeled more or less off of - their C++ counterparts. - - All allocation of new values in a cwal_engine context happen - within an active scope, and the engine tracks a stack of scopes - which behave more or less as scopes do in C++. When a scope is - popped from the stack it is cleaned up and unreferences any - values it currently owns (those allocated by it and not since - taken over by another scope). Unreferencing might or might not - destroy the values, depending on factors such as reference - counts from cycles in the value graph or from other scopes. If - values remain after cleaning up, it cleans up again and again - until all values are gone (this is how it resolves cycles). - - When values are manipulated the engine (tries to) keep(s) them - in the lowest-level (oldest) scope from which they are ever - referenced. This ensures that the values can survive - destruction of their originating scope, while also ensuring - that a destructing scope can clean up values which have _not_ - been taken over by another scope. This "can" (under specific - usage patterns) potentially lead to some values being - "orphaned" in a lower-level scope for an undue amount of time - (unused but still owned by the scope), and the - cwal_scope_sweep() API is intended to help alleviate that - problem. -*/ -struct cwal_scope { - /** - The engine which created and manages this scope. - */ - cwal_engine * e; - - /** - Parent scope. All scopes except the top-most have a parent. - */ - cwal_scope * parent; - - /** - Stores this object's key/value properties (its local - variables). It may be an Object (cwal_object) or Hash (cwal_hash) - Value. Which one gets created depends on the combination of - compile-time CWAL_OBASE_ISA_HASH setting and the runtime - cwal_engine-level CWAL_FEATURE_SCOPE_STORAGE_HASH flag. - */ - cwal_value * props; - - /** - Internal memory allocation stamp. Client code must never touch - this. - */ - void const * allocStamp; - - /** - Values allocated while this scope is the top of the stack, or - rescoped here after allocation, are all placed here and unref'd - when the scope is cleaned up. We split it into multiple lists - to simplify and improve the performance of certain operations - (while slightly complicating others ;). - - Client code MUST NOT touch any of the fields in this - sub-struct. They are intricate bits of the internal memory - management and will break if they are modified by client code. - - TODO(?): refactor this into an array of lists, like - cwal_engine::recycler. We can then refine it easily by adding - extra lists for specific types or groups of types. - */ - struct { - /** - Head of the "PODs" (Plain old Data) list. This includes - all non-containers. - */ - cwal_value * headPod; - /** - Head of the "Objects" list. This includes all container - types. (This distinction largely has to do with cycles.) - */ - cwal_value * headObj; - - /** - Holds items which just came into being and have a refcount - of 0, or have been placed back into a probationary state - with refcount 0. This potentially gives us a - faster/safer/easier sweep() operation. - */ - cwal_value * r0; - - /** - Values marked with the flag CWAL_F_IS_VACUUM_SAFE are - managed in this list and treated basically like named vars - for purposes of vacuuming. The intention is to provide a - place where clients can put non-script-visible values which - are safe from sweep/vacuum operations, but otherwise have - normal lifetimes. Making a value vacuum-proof does not make - it sweep-proof. - */ - cwal_value * headSafe; - } mine; - - /** - The depth level this scope was created at. This is used in - figuring out whether a value needs to be migrated to a - lower-numbered (a.k.a. "higher") scope for memory - management reasons. - - Scope numbers start at 1, with 0 being reserved for - "invalid scope.". - */ - cwal_size_t level; - - /** - Internal flags. - */ - uint32_t flags; -}; -/** - Empty-initialized cwal_scope object. -*/ -#define cwal_scope_empty_m { \ - NULL/*engine*/, \ - NULL/*parent*/, \ - NULL/*props*/, \ - NULL/*allocStamp*/, \ - {/*mine*/ 0/*headPod*/,0/*headObj*/,0/*r0*/, 0/*headSafe*/}, \ - 0U/*level*/, \ - 0U/*flags*/ \ - } - -/** - Empty-initialized cwal_scope_api object. -*/ -extern const cwal_scope cwal_scope_empty; - -/** - Used to store "recyclable memory" - that which has been finalized - but not yet free()d. Each instance is responsible for holding - memory of a single type. Most instances manage Value (cwal_value) - memory, but specialized instances handle recycling of other types - (cwal_kvp and String-type values, as those need special handling - due to their allocation mechanism (which needs to be reconsidered - for refactoring)). -*/ -struct cwal_recycler { - /** - Client-interpreted "ID" for this instance. It is used - internally for sanity checking. - */ - int id; - /** - Current length of this->list. - */ - cwal_size_t count; - /** - Preferred maximum length for this list. Algorithms which - insert in this->list should honor this value and reject - insertion if it would be exceeded. - */ - cwal_size_t maxLength; - /** - Underlying list. The exact type of entry is - context-dependent (e.g. cwal_value or cwal_kvp pointers). - */ - void * list; - /** - Each time a request is made to fetch a recycled value and we - have one to serve the request, this counter gets incremented. - */ - cwal_size_t hits; - /** - Each time a request is made to fetch a recycled value and we do - not have one to serve the request, this counter gets - incremented. - - Note this count includes requests which cannot possibly - succeed, e.g. the initial allocation of any Value. In the - general case (barring allocation errors or falling back to the - chunk recycler), this number will correspond directly to the - number of allocations made for the type(s) stored in this - recycler. - */ - cwal_size_t misses; -}; -/** Convenience typedef. */ -typedef struct cwal_recycler cwal_recycler; -/** Default-initialized cwal_recycler object. */ - -#define cwal_recycler_empty_m {-1/*id*/, 0U/*count*/, 128U/*maxLength*/,NULL/*list*/,0/*hits*/,0/*misses*/} - -/** Default-initialized cwal_recycler object. */ -extern const cwal_recycler cwal_recycler_empty; -/** - Configurable bits for cwal_memchunk. - - @see cwal_engine_memchunk_config() -*/ -struct cwal_memchunk_config { - /** - Maximum number of entries to allow (0 means disable). This is - also the initial capacity of this->pool, so DO NOT set it to - something obscenely huge. Some related algos are linear, so do - not set it to something unreasonably large. - - MUST currently be set up before the recycler is used (it is - allocated the first time something tries to store memory in - it), as the related at-runtime resizing code is untested. - */ - cwal_size_t maxChunkCount; - - /** - The largest single chunk size to recycle (those larger than - this will be freed immediately instead of recycled). Set to - (cwal_size_t)-1 for (effectively) no limit or 0 to disable. If - if there is any semantic dispute between maxTotalSize and - maxChunkSize, maxTotalSize wins. - */ - cwal_size_t maxChunkSize; - - /** - The maximum total chunk size. The recycler will not store - more than this. Set to (cwal_size_t)-1 for no (effective) - limit and 0 to disable chunk recycling. - */ - cwal_size_t maxTotalSize; - - /** - If true (non-0) then cwal will try to use the - memchunk allocator for allocating arbitrary new cwal_value - instances _if_ it cannot find one in the value-type-specific - recycler. That has the following properties and implications: - - - Requires an exact-size match. - - - Based on s2 tests, lowers the hit/miss ratio in the - chunk lookup notably: dropping from ~5% to ~20% misses. - - - it's a micro-optimization: <1% total allocation reduction in - s2's test suite. - - - This adds a O(N) component to Value allocations when the - type-specific recycler is empty. - */ - char useForValues; -}; -typedef struct cwal_memchunk_config cwal_memchunk_config; - -/** - Convenience typedef. -*/ -typedef struct cwal_memchunk_overlay cwal_memchunk_overlay; -/** @internal - - Internal utility for recycling chunks of memory. It can only be - used with chunks having a size >= sizeof(cwal_memchunk_overlay). -*/ -struct cwal_memchunk_overlay { - /** The size of this chunk, in bytes. */ - cwal_size_t size; - /** - Next chunk in this linked list. - */ - cwal_memchunk_overlay * next; -}; -/** - Initialized-with-defaults cwal_memchunk_config struct, used - for const copy initialization. The values defined here are - the defaults for the cwal framework. -*/ -#if 16 == CWAL_SIZE_T_BITS -#define cwal_memchunk_config_empty_m { \ - 25/*maxChunkCount*/, \ - 1024 * 32/*maxChunkSize*/, \ - 1024 * 63/*maxTotalSize*/, \ - 1/*useForValues*/ \ - } -#else -#define cwal_memchunk_config_empty_m { \ - 25/*maxChunkCount*/, \ - 1024 * 32/*maxChunkSize*/, \ - 1024 * 64/*maxTotalSize*/, \ - 1/*useForValues*/ \ - } -#endif -/** - Intended for use with copy construction. Is guaranteed - to hold the same bits as cwal_memchunk_config_empty_m. -*/ -extern const cwal_memchunk_config cwal_memchunk_config_empty; - -/** - A helper type for recycling "chunks" of memory (for use with - arrays, buffers, hash tables, etc.). -*/ -struct cwal_memchunk_recycler { - /** - The head of the active (awaiting recyling) chunks. Holds - this->headCount entries. - */ - cwal_memchunk_overlay * head; - - /** - The number of entries in this->head. - */ - cwal_size_t headCount; - - /** - The total value of the size member of all entries of - this->head. i.e. the amount of memory currently awaiting reuse. - This does not include the memory held by this->pool, which is - (this->capacity * sizeof(void*)) bytes. - */ - cwal_size_t currentTotal; - - /** - Various internal metrics. - */ - struct { - cwal_size_t totalChunksServed; - cwal_size_t totalBytesServed; - cwal_size_t peakChunkCount; - cwal_size_t peakTotalSize; - cwal_size_t smallestChunkSize; - cwal_size_t largestChunkSize; - cwal_size_t requests; - cwal_size_t searchComparisons; - cwal_size_t searchMisses; - cwal_size_t runningAverageSize; - cwal_size_t runningAverageResponseSize; - } metrics; - - cwal_memchunk_config config; -}; -/** Convenience typedef. */ -typedef struct cwal_memchunk_recycler cwal_memchunk_recycler; -/** - An empty-initialized const cwal_memchunk_recycler. -*/ -#define cwal_memchunk_recycler_empty_m { \ - 0/*head*/, 0/*headCount*/, 0/*currentTotal*/, \ - {/*metrics*/ \ - 0/*totalChunksServed*/, \ - 0/*totalBytesServed*/, \ - 0/*peakChunkCount*/, \ - 0/*peakTotalSize*/, \ - 0/*smallestChunkSize*/, \ - 0/*largestChunkSize*/, \ - 0/*requests*/, \ - 0/*searchComparisons*/, \ - 0/*searchMisses*/, \ - 0/*runningAverageSize*/, \ - 0/*runningAverageResponseSize*/, \ - }, \ - cwal_memchunk_config_empty_m \ - } - -/** - A generic buffer class used throughout the cwal API, most often for - buffering arbitrary streams and creating dynamic strings. - - For historical reasons (and because a retrofit would be awkward, in - terms of new APIs, and relatively expensive in terms of new costs), - cwal_buffer has two distinct uses: - - - "Value Buffers" are created using cwal_new_buffer() and their - "buffer part" (cwal_value_get_buffer()) is owned by the Value which - wraps it. - - - "Plain" buffers are created not associated with a Value instance - and are used as demonstrated below. They are, in practice, always - created on the stack or embedded in another struct. - - Note that the memory managed by this class does not partake in any - cwal-level lifetime management. It is up to the client to use it - properly, such that the memory is freed when no longer needed. - - They can be used like this: - - @code - cwal_buffer b = cwal_buffer_empty - // ALWAYS initialize this way, else risk Undefined Bahaviour! - // For in-struct initialization, use cwal_buffer_empty_m. - ; - int rc = cwal_buffer_reserve( e, &buf, 100 ); - if( 0 != rc ) { - ... allocation error ... - assert(!buf.mem); // just to demonstrate - }else{ - ... use buf.mem ... - ... then free it up ... - cwal_buffer_reserve( e, &buf, 0 ); - } - @endcode - - To take over ownership of a buffer's memory: - - @code - void * mem = b.mem; - // mem is b.capacity bytes long, but only b.used - // bytes of it has been "used" by the API. - b = cwal_buffer_empty; - @endcode - - The memory now belongs to the caller and must eventually be - cwal_free()'d. Even better, if you remember the buffer's original - capacity after taking over ownership, you can use cwal_free2(), - passing it the memory and the block's size (the buffer's former - capacity), which may allow cwal to recycle the memory better. -*/ -struct cwal_buffer -{ - /** - The number of bytes allocated for this object. - Use cwal_buffer_reserve() to change its value. - */ - cwal_size_t capacity; - /** - The number of bytes "used" by this object's mem member. It must - be <= capacity. - */ - cwal_size_t used; - - /** - The memory allocated for and owned by this buffer. - Use cwal_buffer_reserve() to change its size or - free it. To take over ownership, do: - - @code - void * myptr = buf.mem; - buf = cwal_buffer_empty; - @endcode - - (You might also need to store buf.used and buf.capacity, - depending on what you want to do with the memory.) - - When doing so, the memory must eventually be passed to - cwal_free() to deallocate it. - */ - unsigned char * mem; - - /** - For internal use by cwal to differentiate between - container-style buffers and non-container style without - requiring a complete overhaul of the buffer API to make - Container-type representations of them. - - Clients MUST NOT modify this. It is only non-NULL for buffers - created via cwal_new_buffer() resp. cwal_new_buffer_value(), - and its non-NULL value has a very specific meaning. To - reiterate: clients MUST NOT modify this. - - 20181126: i'm not certain that we still need this. We can(?) - ensure that a buffer is-a Value by doing the conversion from a - buffer to a value, then back again, and see if we get the same - result. If not, it wasn't a Value. If we do, it's _probably_(? - possibly?) a Value. - */ - void * self; -}; - -/** - An empty-initialized cwal_buffer object. - - ALWAYS initialize embedded-in-struct cwal_buffers by copying - this object! -*/ -#define cwal_buffer_empty_m {0/*capacity*/,0/*used*/,NULL/*mem*/, 0/*self*/} - -/** - An empty-initialized cwal_buffer object. ALWAYS initialize - stack-allocated cwal_buffers by copying this object! -*/ -extern const cwal_buffer cwal_buffer_empty; - -/** - A typedef used by cwal_engine_type_name_proxy() to allow - clients to hook their own type names into - cwal_value_type_name(). - - Its semantics are as follows: - - v is a valid, non-NULL value. If the implementation can map - that value to a type name it must return that type name string - and set *len (if len is not NULL) to the length of that string. - The returned bytes must be guaranteed to be static/permanent in - nature (they may be dynamically allocated but must outlive any - values associated with the name). - - If it cannot map a name to the value then it must return NULL, - in which case cwal_value_type_name() will fall back to its - default implementation. - - Example implementation: - - @code - static char const * my_type_name_proxy( cwal_value const * v, - cwal_size_t * len ){ - cwal_value const * tn = cwal_prop_get(v, "__typename", 10); - return tn ? cwal_value_get_cstr(tn, len) : NULL; - } - @endcode - - @see cwal_engine_type_name_proxy() -*/ -typedef char const * (*cwal_value_type_name_proxy_f)( cwal_value const * v, cwal_size_t * len ); - -/** - A generic error code/message combination. Intended for reporting - non-exception errors, possibly propagating them on their way to - becoming exceptions. The core library does not need this, but it - has proven to be a useful abstraction in client-side code, so was - ported into the main library API. - - @see cwal_error_set() - @see cwal_error_get() - @see cwal_error_reset() - @see cwal_error_clear() -*/ -struct cwal_error { - /** - Error code, preferably a CWAL_RC_xxx value. - */ - int code; - /** - Line-number of error, if relevant. 1-based, so use 0 - as a sentinel value. - */ - int line; - /** - Column position of error, if relevant. 0-based. - */ - int col; - /** - The error message content. - */ - cwal_buffer msg; - - /** - Holds a script name associated with this error (if any). We use a - buffer instead of a string because it might be re-set fairly - often, and we can re-use the memory. - */ - cwal_buffer script; -}; - -/** - Empty-initialized cwal_error structure, intended for const-copy - initialization. -*/ -#define cwal_error_empty_m {0, 0, 0, cwal_buffer_empty_m, cwal_buffer_empty_m} - -/** - Empty-initialized cwal_error structure, intended for copy - initialization. -*/ -extern const cwal_error cwal_error_empty; - - -/** - The core manager type for the cwal API. Each "engine" instance - manages a stack of scopes and (indirectly) the memory associated - with Values created during the life of a Scope. -*/ -struct cwal_engine { - cwal_engine_vtab * vtab; - /** - Internal memory allocation marker. - */ - void const * allocStamp; - /** - A handle to the top scope. Used mainly for internal - convenience and sanity checking of the scope stack - handling. - */ - cwal_scope * top; - /** - Scope stack. Manipulated via cwal_scope_push() and - cwal_scope_pop(). - - TODO: rename this to currentScope someday. - */ - cwal_scope * current; - - /** - When a scope is cleaned, if deferred freeing is not active - then this pointer is set to some opaque value known only by - the currently-being-cleaned scope before it starts cleaning - up. As long as this is set, freeing and recycling of - containers is deferred until cleanup returns to the - being-freed scope, at which point this value is cleared and - the gc list is flushed, all of its entries being submitted - for recycling (or freeing, if recycling is disabled or - full). - - This mechanism acts as a safety net when traversing cycles - where one of the traversed values was freed along the way. The - lowest-level scope from which destruction is initiated - (normally also the bottom-most scope, but i would like to - consider having scopes as first-class values) is the "fence" - for this operation because destruction theoretically cannot - happen for values in higher scopes during cleanup of a lower - scope. i.e. when destructing scopes from anywhere but the top - of the stack the initial scope in the destruction loop is the - one which will queue up any to-be-freed containers for - recycling, and it will flush the gc list. Because values form - linked lists, we use those to form the chain of deferred - destructions, so this operation costs us no additional memory - (it just delays deallocation/recycling a bit) and is O(1) - (flushing the queue is O(N)). - - Note that types which cannot participate in graphs are not - queued - they are (normally) immediately recycled or - cwal_free()d when their refcount drops to 0 (or is reduced - when it is already 0, as is the case for "temporary" values - which never get a reference). - */ - void const * gcInitiator; - - /** - Internal flags. See the API-internal CWAL_FLAGS enum for - the gory details. - */ - uint32_t flags; - - /** - A flag which will someday be used to flag assertion-level - failures in non-debug builds, such that the engine, if a - condition arises which is normally fatal/assert()ed, then it - will refuse to do anything further except (reluctantly) - cwal_engine_destroy(). - */ - int fatalCode; - - /** - May hold non-exception error state, potentially on behalf of - client code. - */ - cwal_error err; - - /** - List of list managers for (cwal_value*) of types which we can - recycle. We can recycle memory for these types: - - integer, double, array, object, hash, native, buffer, function, - exception, x-string/z-string (in the same list), cwal_kvp, - cwal_scope, cwal_weak_ref, unique, cwal_tuple. - - The lists are in an order determined by the internal - function cwal_recycler_index(). Other types cannot be - recycled as efficiently (e.g. cwal_string use a separate - mechanism) or because they are never allocated (null, bool, - undefined and any built-in shared value instances). - - Reminder: the default recycle bin sizes do not reflect any - allocation size, simply the number of objects we don't free - immediately, so there is little harm is setting them - relatively high (e.g. 100 or 1000). Because cwal_value and - cwal_kvp objects form a linked list, a given recycle bin - may grow arbitrarly large without requiring extra memory to - do so (we just link the values in each recycle bin, and - those values have already been allocated). This all happens in - O(1) time by simply making each new entry the head of the - list (and removing them in that order as well). - - Maintenance reminder: the indexes of this list which actually - get used depend on how the engine sets up the bins. It combines - like-sized types into the same bins. Thus this list has to be - larger than strictly necessary for platforms where it cannot - combine types. On x86/64, it needs 8 slots as of this writing. - */ - cwal_recycler recycler[15]; - - /** - reString is a special-case recycler for cwal_string values. - String values are recycled based on their size. i.e. we - won't recycle a 36-byte string's memory to serve a 10-byte - string allocation request. - - As an optimization, we (optionally) pad string allocations - to a multiple of some small number of bytes (e.g. 4 or 8), - as this lets us recycle more efficiently (up to 36% more - string recycling in some quick tests). - - See the API-internal CwalConsts::StringPadSize for details. - */ - cwal_recycler reString; - - /** - A generic memory chunk recycler. - */ - cwal_memchunk_recycler reChunk; - - /** - A place for client code to associated a data pointer and - finalizer with the engine. It is cleaned up early in the engine - finalization process. - */ - cwal_state client; - - /** - A value-to-type-name proxy, manipulated via - cwal_engine_type_name_proxy(). - */ - cwal_value_type_name_proxy_f type_name_proxy; - - /** - Where we store internalized strings. - - Maintenance note: this is-a cwal_ptr_table but uses its own - hashing/searching/insertion/removal API. Do NOT use the - equivalent cwal_ptr_table ops on this instance. See the - internal cwal_interned_search(), cwal_interned_insert(), - and cwal_interned_remove() for details. - */ - cwal_ptr_table interned; - - /** - Memory for which we have a weak reference is annotated by - simply inserting its address into this table. When the - memory is cleaned up, if it has an entry here, we - invalidate any cwal_weak_refs which point to it. - */ - cwal_ptr_table weakp; - - /** - cwal_weak_ref instances are stored here, grouped by - underlying memory type to speed up the invalidate-ref - operation. Weak refs to buit-in constants are handled - specially (to avoid allocating new instances and because - they can never be invalidated). Some slots of this array - (those of constant types, e.g. null/undefined/bool) are - unused, but we keep their slots in this array because it - greatly simplifies our usage of this array. - - The (void*) memory pointed to by weak references is held in - weakr[CWAL_TYPE_WEAK_REF], since that slot is otherwise - unused. We "could" use the NULL/BOOL/UNDEF slots for - similar purposes. - */ - cwal_weak_ref * weakr[CWAL_TYPE_end]; - - /** - The top-most scope. This is an optimization to avoid an - allocation. - */ - cwal_scope topScope; - - /** - If built with CWAL_ENABLE_TRACE to 0 then this is a no-op - dummy placeholder, else it holds information regarding - engine tracing. This state continually gets overwritten if - tracing is active, and sent to the client via - this->api->tracer. - */ - cwal_trace_state trace; - - /** - Buffer for internal string conversions and whatnot. This - buffer is volatile and its contents may be re-allocated or - modified by any calls into the public API. Internal APIs - need to be careful not to stomp the buffer out from under - higher-scope public APIs and internal calls. - */ - cwal_buffer buffer; - - /** - Where Function-type call() hooks are stored. - */ - cwal_callback_hook cbHook; - - /** - _Strictly_ interal bits for managing various specific values or - categories of values. - */ - struct { - /** - Holds any current pending exception, taking care to - propagate it up the stack when scopes pop. - */ - cwal_value * exception; - - /** - A slot for a single propagating value which will - automatically be pushed up the stack as scopes - pop. Intended for keywords which propagate via 'return' - semantics or error reporting, so that they have a place to - keep their result (if any). - */ - cwal_value * propagating; - - /** - Where clients may store their customized base prototypes - for each cwal_type_id. The indexes in this array correspond - directly to cwal_type_id values, but (A) that is an - implementation detail, and (B) some slots are not used (but - we use this structure as a convenience to save cycles in - type-to-index conversions and to keep those prototypes - vacuum-safe). - */ - cwal_array * prototypes; - - /** - A place to store values which are being destroyed during - the traversal of cycles. Used for delayed freeing of - cwal_value memory during scope cleanup. See gcInitiator - for more details. - */ - cwal_value * gcList; - - /** - Internal optimization for cwal_hash_resize() and - cwal_hash_take_props() to allow us to transfer a kvp - directly from the source to the target. This gets - transfered (or not) from a container in the calling op to - the target hashtable during the cwal_hash_insert_v(). If, - after calling cwal_hash_insert_v(), it's not 0, it's up to - the caller to manage its memory and reset this to 0. - */ - cwal_kvp * hashXfer; - } values; - - /** - Holds metrics related to honoring memory capping. Note that - recycled memory only counts once for purposes of these metrics. - i.e. recycling a chunk of memory counts as a single allocation - (the initial one), not a separate allocation each time the - chunk is recycled. - */ - struct { - /** - Caps concurrent cwal-allocated memory to this ammount. - */ - cwal_size_t currentMem; - /** - Caps the concurrent cwal allocation count to this number - (or thereabouts - reallocations are kind of a grey area). - */ - cwal_size_t currentAllocCount; - /** - Caps the peak amount of cwal-allocated memory to - this amount. - */ - cwal_size_t peakMem; - /** - Records the peak concurrent allocation count (or - thereabouts - reallocations are kind of a grey area). This - is not a memory capping constraint. - */ - cwal_size_t peakAllocCount; - /** - Caps the total cwal allocation count to this number (or - thereabouts - reallocations are kind of a grey area). - */ - uint64_t totalAllocCount; - /** - Caps the total amount of cwal-allocated memory to this - amount. - */ - uint64_t totalMem; - } memcap; - - /** - A place for storing metrics. - */ - struct { - /** - Each time a request is made to allocate a Value, the - value type's entry in this array is increments. Does - not apply to certain optimized-away situations like - empty strings, bools/null/undef, and the constant - numeric values. - */ - cwal_size_t requested[CWAL_TYPE_end]; - /** - Each time we have to reach into the allocator to - allocate an engine resource, its type's entry in this - array is incremented. This values will always be less - than or equal to the same offered in the 'requested' - member. - */ - cwal_size_t allocated[CWAL_TYPE_end]; - - /** - The number of allocated bytes for each type is totaled - here. We can't simply use (allocated*sizeof) for some - types (e.g. strings, arrays, buffers, and hashtables), - and this value requires some fiddling with in certain - areas to ensure it gets all memory for some types - (namely arrays and buffers, whose sizes change with - time). In any case, it is only a close approximation - because reallocs play havoc with our counting in some - cases. - */ - cwal_size_t bytes[CWAL_TYPE_end]; - - /** - clientMemTotal keeps a running total of memory usage - declared by the client. - - @see cwal_engine_adjust_client_mem() - */ - cwal_size_t clientMemTotal; - - /** - Holds the current amount of memory declared by the client. - - @see cwal_engine_adjust_client_mem() - */ - cwal_size_t clientMemCurrent; - - /** - Each time an attempt to recycle a recyclable Value - type (or cwal_kvp) succeeds, this is incremented. - */ - cwal_size_t valuesRecycled; - - /** - Each time we look in the value recyclers for a value and do - not find one, this is incremented. - */ - cwal_size_t valuesRecycleMisses; - - /** - Counts the number of blocks for which - this->recoveredSlackBytes applies. - */ - cwal_size_t recoveredSlackCount; - /** - When over-allocating to account for memory capping, - the chunk recycler can recover more "slack" bytes. - Those are counted here. - */ - cwal_size_t recoveredSlackBytes; - - /** - The highest-ever refcount on any Value. - */ - cwal_refcount_t highestRefcount; - - /** - The data type of the value for which the highestRefcount - metric was measured. - */ - cwal_type_id highestRefcountType; - - /** - Records the number of allocations saved by the using the - built-in list of length-1 ASCII strings to serve - cwal_new_string(), cwal_new_xstring(), and - cwal_new_zstring() allocations. - - Indexes: 0: string, 1: x-string, 2: z-string - */ - cwal_size_t len1StringsSaved[3]; - } metrics; -}; -/** @def cwal_engine_empty_m - Empty initialized const cwal_engine struct. -*/ -#define cwal_engine_empty_m { \ - NULL /* vtab */, \ - NULL /* allocStamp */, \ - NULL /* top */, \ - NULL /* current */, \ - NULL /* gcInitiator */, \ - 0U /* flags */, \ - 0 /* fatalCode */, \ - cwal_error_empty_m, \ - {/* recycler */ \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m, cwal_recycler_empty_m, \ - cwal_recycler_empty_m \ - }, \ - {/*reString*/ CWAL_TYPE_STRING, 0, 40, NULL, 0, 0 }, \ - /* reChunk*/ cwal_memchunk_recycler_empty_m, \ - cwal_state_empty_m /* client */, \ - NULL/*type_name_proxy*/, \ - cwal_ptr_table_empty_m/*interned*/, \ - cwal_ptr_table_empty_m/*weakp*/, \ - {/*weakr*/ \ - NULL,NULL,NULL,NULL,NULL, \ - NULL,NULL,NULL,NULL,NULL, \ - NULL,NULL,NULL,NULL,NULL, \ - NULL,NULL,NULL,NULL,NULL, \ - NULL \ - }, \ - cwal_scope_empty_m/*topScope*/, \ - cwal_trace_state_empty_m/*trace*/, \ - cwal_buffer_empty_m/*buffer*/, \ - cwal_callback_hook_empty_m/*cbHook*/, \ - {/*values*/ \ - NULL/* exception */, \ - NULL/* propagating */, \ - NULL/* prototypes */, \ - NULL/* gcList */, \ - NULL /* hashXfer */ \ - }, \ - {/*memcap*/ \ - 0/*currentMem*/, 0/*currentAllocCount*/, \ - 0/*peakMem*/, 0/*peakAllocCount*/, \ - 0/*totalAllocCount*/, 0/*totalMem*/ \ - }, \ - {/*metrics*/ \ - /*requested[]*/{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,0}, \ - /*allocated[]*/{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,0}, \ - /*bytes[]*/ {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,0}, \ - 0/*clientMemTotal*/, 0/*clientMemCurrent*/, \ - 0/*valuesRecycled*/, 0/*valuesRecycleMisses*/, \ - 0/*recoveredSlackCount*/, 0/*recoveredSlackBytes*/, \ - 0/*highestRefcount*/, CWAL_TYPE_UNDEF/*highestRefcountType*/, \ - {0,0,0}/*len1StringsSaved*/ \ - } \ -}/*end cwal_engine_empty_m*/ - -/** - Empty-initialized cwal_engine object. When initializing - stack-allocated cwal_engine instances, always copy this instance - over them to set up any default state. For in-struct - initialization, use cwal_engine_empty_m. -*/ -extern const cwal_engine cwal_engine_empty; - -/** - Initializes a cwal_engine instance. vtab must not be NULL and must - be populated in accordance with the cwal_engine_vtab - documentation. e must either be a pointer to a NULL-initialized - pointer or a pointer to a pre-allocated instance (possibly from - the stack or embedded in another struct) with a clean state - (e.g. by copying cwal_engine_empty or cwal_engine_empty_m over - it). - - vtab MUST outlive e, and in practice it can normally be static. - - On success 0 is returned and *e points to the engine instance. - - On success e is initialized with a top scope, so clients need not - use cwal_scope_push() before using the cwal_new_xxx() family - of factory functions to allocate values. - - On error non-0 is returned and *e is NOT cleaned up so that - any error state in the object can be collected by the client - before freeing it. If *e is NULL on returning and argument - validation succeeds, then allocation of the instance failed - and CWAL_RC_OOM will be returned. If vtab.on_init() returns - non-0, this function will return that code, but vtab.on_init() - is only called if the priliminary initialization succeeds - (it can only fail on allocation errors). - - In short: the caller must, regardless of whether or not this - function succeeds, if *e is not NULL, eventually pass *e to - cwal_engine_destroy(). If this function allocated it, that - function will free it. - - Errors include: - - CWAL_RC_MISUSE: one of the arguments is NULL - - CWAL_RC_OOM: memory allocation failed - - Any other error code should be considered a bug in this code. - - - Potential TODO: require the client to push the first scope, - giving him a time slot between initialization and the first - scope to set configuration options which might affect the first - scope (currently we have none built in, and the vtab's on_init() - hook can be used to make changes for the time being). -*/ -int cwal_engine_init( cwal_engine ** e, cwal_engine_vtab * vtab ); - -/** - Frees all resources owned by e. If e was allocated dyanamically by - cwal_engine_init() (or equivalent) then this function - deallocates it, otherwise e is a left in an empty state after this - call (suitable for re-using with cwal_engine_init()). - - As a special case, if e has no vtab then it is assumed that - cwal_engine_init() hhas not yet been called and this function - returns 0 and has no side effects. -*/ -int cwal_engine_destroy( cwal_engine * e ); - -/** - Installs f as a proxy for cwal_value_type_name(). See - cwal_value_type_name_proxy_f for full details. f may be NULL - but e may not. e may only have one type name proxy installed at - a time, and this replaces any existing one. - - Returns the existing proxy (possibly NULL), so that the client - may swap it in and out, but in practice the client sets this up - only once in the initialization phase. - - @see cwal_value_type_name() - @see cwal_value_type_name2() -*/ -cwal_value_type_name_proxy_f -cwal_engine_type_name_proxy( cwal_engine * e, - cwal_value_type_name_proxy_f f ); - -/** - Pushes a new scope onto e's stack, making it the current scope. - If s is not NULL and (*s) is NULL then *s is assigned to a - newly-allocated/recycled scope on success. - - If s is not NULL and (*s) is not NULL when this function is - called then it must be a cleanly-initialized scope value, and - this function will use it instead of allocating/recycling - one. For example: - - @code - cwal_scope sub_ = cwal_scope_empty; - cwal_scope * sub = &sub_; - int rc = cwal_scope_push(e, &sub); - if(rc) { ...error... do NOT pop the scope ...} - else { - ...do your work, then... - cwal_scope_pop(sub); - } - @endcode - - (An easy way to determine, at the end of the function, whether - 'sub' was pushed is to check if sub->parent is 0 (in which case it - was not pushed).) - - Using scopes this way can be considered a malloc-count - optimization over simply passing NULL as the 's' - parameter. Initializing scopes this way does not change how - they are popped: cwal_scope_pop() is still the correct way to - clean up the scope. Client who want to ensure that downstream - code has not corrupted the stack can check if - cwal_scope_current_get()==theirScopePointer before popping the - stack (and should fail loudly if they do not match). - - Returns 0 on success. On error: - - CWAL_RC_MISUSE: e is NULL - - CWAL_RC_OOM: memory allocation error. This error historically could - only happen if the user passes a NULL or a _pointer to NULL_ as the - second parameter, otherwise this function allocates no memory and - cannot fail if e and s are valid. However... - - As of 20181123, if the client application has a scope push hook - (cwal_engine_vtab::hook:scope_push) installed then that hook gets - called during this process (unless cwal needs to allocate the scope - but cannot do so), in which case the result code from that hook is - returned. It's conceivable that it may return a CWAL_RC_OOM, but - any other error result seems unlikely in practice. - - When passed a client-supplied scope, this function has no error - conditions as long as e is valid, with the caveat that a - scope push hook (mentioned above) might fail. - - Reminder to self: practice suggests that, because scopes are - effectively always stack-allocated, this really should take a - pointer, not pointer-to-pointer 2nd argument. We'll add a second - function for that case. - - @see cwal_scope_push2() - @see cwal_scope_pop() - @see cwal_scope_pop2() -*/ -int cwal_scope_push( cwal_engine * e, cwal_scope ** s ); - -/** - This is a convenience form of cwal_scope_push() which requires that - s be non-NULL and be a freshly-initialized instance by having - cwal_scope_empty copied over it to initialize its state. In - practice, cwal_scope instances are always stack-allocated in a - local function, and this variant simplifies the usage of - such instances. - - Returns CWAL_RC_MISUSE if e or s are NULL or if s appears to - contain any non-default state, else it returns as for - cwal_scope_push(). - - This routine is effectively simplifies this: - - @code - cwal_scope _s = cwal_scope_empty; - cwal_scope * s = &_s; - int rc = cwal_scope_push( e, &s ); - @endcode - - to this: - - @code - cwal_scope s = cwal_scope_empty; - int rc = cwal_scope_push2( e, &s ); - @endcode - - @see cwal_scope_push() - @see cwal_scope_pop() - @see cwal_scope_pop2() -*/ -int cwal_scope_push2( cwal_engine * e, cwal_scope * s ); - -/** - Pops the current scope from the stack. - - Returns 0 on success. Returns CWAL_RC_MISUSE if !e and - CWAL_RC_RANGE if e has no current scope (use cwal_scope_push() - first)). - - This "really shouldn't" ever fail, and a failure is likely either - the result of a cwal-internal bug or serious offenses against the - memory management rules. - - @see cwal_scope_pop2() -*/ -int cwal_scope_pop( cwal_engine * e ); - -/** - A variant of cwal_scope_pop() which rescopes the given value (if - not NULL) to the parent of the to-pop scope (if needed). - - This is intended to simplify propagating result values up the scope - stack. - - Returns 0 on success, else: - - - CWAL_RC_MISUSE if !e - - - CWAL_RC_RANGE if resultVal is not NULL and e's top-most scope is - the current scope (because no value may outlive the top scope). - - Returns 0 on success, else (except as mentioned above) as - documented for cwal_scope_pop(). - - Note that the reference count of the 2nd argument is not modified - by this routine. Whether or not it needs/has a reference is - entirely up to the surrounding code. - - Example: - - @code - int rc; - cwal_value * v; - cwal_scope scope = cwal_scope_empty; - rc = cwal_scope_push2( e, &scope ); - if(rc) return rc; - assert(scope.parent); // just to demonstrate - v = ...; - cwal_value_ref(v); - ... other stuff ... - rc = cwal_scope_pop2( e, v ); - assert(!scope.parent); // just to demonstrate - assert(cwal_value_refcount(v) || cwal_value_is_builtin(v)); - // Eventually we need to relinquish that ref. In the case - // of propagating values, we normaly want: - cwal_value_unhand(v); - // So that we can pass the value back up to the caller with - // a predictable, yet possibly zero, refcount. - // If we instead want to discard the value altogether then - // we need: cwal_value_unref(v); - @endcode - - @see cwal_scope_pop() - @see cwal_scope_push() - @see cwal_scope_push2() - @see cwal_value_unhand() -*/ -int cwal_scope_pop2( cwal_engine * e, cwal_value * resultVal ); - -/** - "Might partially clean up" the given scope, as follows... - - For each value owned by scope which has a reference count of - _exactly_ 0, it is unref'd. Values with a refcount of 0 are - considered "probationary" values, subject to summary cleanup. - Once a value has ever had a reference added to it, it moves out - of probationary status and cannot be affected by sweep - operations unless they are once again re-probated (which can - happen in one of several special cases). Thus this could be - called after (or periodically during) loops which create lots - of anonymous/throw-away values. - - It is only safe to call this if the client has explicitly - referenced all values from the current scope which he is still - holding a pointer to (and expects that pointer to be valid after - this call). See cwal_new_VALUE() for a description of how - references are acquired. - - Returns the number of unref's triggered by the sweep, or 0 - if any arguments are invalid. Note that it does not count - recursively-removed values in the return code because - those cleanups happen at a different level. - - Performance is effectively O(N+M) when no cycles are - introduced, where N=total number of probationary values and M - is the cleaning costs of those values cleaned. Cycles - theoretically cannot happen in a probationary object because - that would necessarily cause a refcount increase of the value - partaking in the cycle, which would move the value out of - probationary state. - - Note that if two calls are made to this function without having - allocated/transfered new values from/to s, the second (and - subsequent) will be no-ops because only probationary values are - affected in any way. - - There are certain abstract operation chains where calling this - will almost certainly be fatal to the app. For example, - consider this pseudocode: - - @code - myFunction( 1+2, 7*8, somethingWhichSweeps ); - @endcode - - The sweep activated in the 3rd argument could (depending on how the - arguments are collected) destroy the temporaries before they get - passed on to the function. Thus it is important that evaluation - engines hold refs to all values they're working with. - - Earlier in the docs we mentioned a special corner case where a - value can re-enter probationary state. This happens when moving - values up scopes while containers in lower (newer) scopes holding - references to them get cleaned up. Example code taken from th1ish: - - @code - assert 17 === false || scope { - var obj = object {a:17} - obj.a // implicit scope result value - } - @endcode - - Normally obj.a would be cleaned up by obj at scope's end, but the - 'scope' operator supports implicit returns and thus needs to pass - it unmolested up the scope chain. In this case, that moves (former) - obj.a into the parent scope with a refcount of 0, moving it back - into probationary state. - - @see cwal_engine_sweep() - @see cwal_engine_vacuum() -*/ -cwal_size_t cwal_scope_sweep( cwal_scope * s ); - -/** - Returns the cwal_engine which owns s, or NULL if s is NULL. -*/ -cwal_engine * cwal_scope_engine(cwal_scope const * s); - -/** - Calls cwal_scope_sweep() on e's current scope, returning the - result of that call. Returns 0 if !e or if e has no current - scope. - - @see cwal_scope_sweep() - @see cwal_engine_vacuum() -*/ -cwal_size_t cwal_engine_sweep( cwal_engine * e ); - -/** - If allScopes is false, this behaves like cwal_engine_sweep(), - otherwise it sweeps each scope, as per cwal_scope_sweep(), - starting at the current scope and working upwards in the - stack. Returns the total of all resulting cwal_scope_sweep() - calls. - - This is inherently a dangerous operation as it can sweep up values - in older scopes which are being used by the current scope if those - values do not have a reference somewhere. Potential culprits here - include: - - - Temporaries created while evaluating function arguments, - which then get passed (without an explicit ref) to a function - which triggers the recursive sweep. If the arguments get - a reference, they are not problematic here. - - - Propagating result values, because they are not tracked directly - by cwal, are not exempt from sweep-up. Initially, cwal kept track - of a single result value, but it turned out (in th1ish) to be much - easier to do from client code (the script interpreter). Maybe we - can revisit that design decision someday. (Someday: see - cwal_propagating_get() and cwal_propagating_set().) - - - Propagating exceptions have a reference, so are immune to - sweep-up. -*/ -cwal_size_t cwal_engine_sweep2( cwal_engine * e, char allScopes ); - - -/** - This function cleans up all values owned by the current scope - by determining whether or not they refer to, or are referred to - by, scope-level properties (variables). - - The mechanism is relatively simple: - - 1) Push a new scope onto the stack with the same parent as e's - current scope, but fake its level to (s->level-1) so that it - looks like an older scope. We'll call the current scope s1 and - this new scope s2. - - 2) upscope s1's object properties and any values in s1 marked - as vacuum-proof into s2. Because s2 looks like an older scope, - this will transfer any values in s1 which are variables, - vacuum-proof, or reachable via either of those, leaving any - orphaned values in s1 but not in s2. - - 3) clean all values remaining in s1. - - 4) re-set s2's parent to be s1 and fake s2's level to - (s1->level+1) so that s2 looks like a newer scope. - - 5) upscope (again) the object properties and vacuum-proofed values, - this time from s2 to s1. Because of step 4, s1 now looks like a - higher/older scope to the rescope process, which will move the - variables, and values referenced by them, back into s1. Note that - we cannot simply move the value lists from s2 to s1 because we need - to ensure that the value->scope pointers all point to where they - need to, and the underlying engine does that for us if we just copy - (well, move) the values back again. - - 6) Clean up scope s2, as if it had been popped from the stack. - - The end result is that after this call (on success), only variabes, - vacuum-proofed values, and values reachable via either variables or - vacuum-proofed values, will be in the scope, all other (presumably - script-unreachable) values having been cleaned up. - - This operation requires no allocation, just traversal of values to - tag them with their new scope. It is, computationally, speaking, - difficult to predict the performance. For current th1ish/s2 uses it - is quite fast enough to run very often (after every expression - evaluation), but very complex graphs will slow it down a bit. For - most purposes (no graphs or only few simple ones) it can be - considered linear on the number of values owned by the scope. - - ACHTUNG: this invalidates any and all of the following pointers: - - - Values owned by this scope but which are not reachable from - either scope-level variables or a vacuum-proof value. They are - destroyed. - - Returns 0 on success. On success, if sweepCount is not 0 then - it is set to the number of values removed from the scope by - this operaiton. If sweepCount is 0, this operation is a few - ticks faster because it does not have to do any extra counting. - - Any error other than argument validation (CWAL_RC_MISUSE) indicates - either an allocation problem or unexpected bits were found while - fiddling around, either of which must be treated as fatal to the - cwal_engine. In this case, the current scope will be cleaned up in - its entirety (but not popped from the scope stack) because we - simply have no other sane recovery strategy where all known values - have some reasonable lifetime. (Sidebar: that has never happened - in practice.) - - The only possible errors are invalid arguments or corruption - detected during the operation. In debug builds it will assert() if - it detects anything wrong. - - To make specific Values immune to vacuuming, use - cwal_value_make_vacuum_proof(). - - Design note: sweepCount is a signed int because initial tests - in th1ish have added values to the scope (possibly internals - not visible from script code), leading to an overall negative - sweep count. i believe this to be either a th1ish-side usage - error or bug, however: sweepCount should always be 0 or - positive on success, and this code assert()s that that is so. - - @see cwal_value_make_vacuum_proof() - @see cwal_engine_sweep() -*/ -int cwal_engine_vacuum( cwal_engine * e, int * sweepCount ); - -/** - DANGEROUS! DO NOT USE! - - This works identically to cwal_engine_vacuum() but applies to the - given scope, as opposed to the current (though it may be the - current scope). Whether or not this operation is "safe" on any - scope other than the current one very much depends on how - client-side code manages its own cwal_value instances. DO NOT call - this function unless you know for absolute certain that doing so is - legal vis-a-vis the app's cwal_value management. When uncertain, - don't call it. - - @see cwal_engine_vacuum(). -*/ -int cwal_scope_vacuum( cwal_scope * s, int * sweepCount ); - -/** - Sets the given pointers as client state in e. What that means is: - - - e applies no meaning to the state but will, at cleanup time, - call dtor() (early on in the engine shutdown process) to clean - up the state if dtor is not 0. - - - There can be only one piece of client state installed at a time, - and this function fails with CWAL_RC_ACCESS if state - is already set (to avoid having to answer questions about its - lifetime). - - - cwal_engine_client_state_get() can be used, passed the same - (e, typeId) values used here, to fetch the pointer later on. - Calls to that function with other (e, typeId) combinations will - return 0. - - The typeId can be an arbitrary pointer value, but must outlive e. - It is typically the address of some static const value associated - with state's concrete data type. - - Returns 0 on success. Errors include: - - - CWAL_RC_MISUSE if e or state are 0 (typeId and dtor may be 0) - - - CWAL_RC_ACCESS if state has already been sete on e. -*/ -int cwal_engine_client_state_set( cwal_engine * e, - void * state, void const * typeId, - cwal_finalizer_f dtor); - -/** - If cwal_engine_client_state_set() was passed e and typeId at some - point, and it has not be re-set since then, then this returns the - state pointer, otherwise it returns 0. -*/ -void * cwal_engine_client_state_get( cwal_engine * e, void const * typeId ); - - -/** - On success, *s is assigned to the current scope and 0 is returned. - On error *s is not modified and one of the following are returned: - - CWAL_RC_MISUSE: one of the arguments is NULL. - - CWAL_RC_RANGE: e currently has no scope. -*/ -int cwal_scope_current( cwal_engine * e, cwal_scope ** s ); - -/** - Simplified form for cwal_scope_current() which returns the - current scope, or 0 if !e or if there are no scopes. -*/ -cwal_scope * cwal_scope_current_get( cwal_engine * e ); - -/** - Interpreter-level flags for "variables." Maintenance reminder: keep - these flags at 16 bits or less (see cwal_kvp::flags). -*/ -enum cwal_var_flags { -/** - The no-flags/default value. -*/ -CWAL_VAR_F_NONE = 0, -/** - Indicates that the variable/property should be "const." - Property-setting routines must refuse to re-set a property which - has this flag. cwal_props_clear() (and similar) must ignore this - flag and clear the property. -*/ -CWAL_VAR_F_CONST = 0x0001, - -/** - Indicates that property iteration operations on types capable of - holding key/value pairs should not expose properties with this flag - via iteration-like routines, e.g. cwal_props_visit_kvp() and - friends. Such values will be found normally if searched for by - their key. -*/ -CWAL_VAR_F_HIDDEN = 0x0002, - -/** - Reserved. Unused. -*/ -CWAL_VAR_F_PROP_GETTER = 0x0010, - -/** - Reserved. Unused. -*/ -CWAL_VAR_F_PROP_SETTER = 0x0020, - -/** - Reserved. Unused. -*/ -CWAL_VAR_F_PROP_INTERCEPTOR = CWAL_VAR_F_PROP_GETTER | CWAL_VAR_F_PROP_SETTER, - -/** - Indicates that any existing flags of the property should be - kept as-is. For newly-created properties this is applied as if - it were CWAL_VAR_F_NONE. - - Maintenance reminder: must currently be 16 bits. -*/ -CWAL_VAR_F_PRESERVE = 0xFFFF - -}; - -/** - Flags for use exclusively with container-type values, - cwal_container_flags_set(), and cwal_container_flags_get(). - - These are limited to 16 bits. - - @see cwal_container_flags_set() - @see cwal_container_flags_get() -*/ -enum cwal_container_flags { -/** - Tells the engine that setting object properties OR hash entries on - this container is not allowed, and should trigger a - CWAL_RC_DISALLOW_PROP_SET error. - - The restriction on hash entries is arguably a bug, but s2 currently - relies on it (201912). FIXME: separate that case into a separate - flag and have s2 accommodate that. - - Note that this restriction does not apply to array/tuple indexes, - but it probably should (noting that tuples do not have container - flags!). That behaviour may change in the future or another flag - may be added to cover that case (for arrays, at least). In - particular, it would be useful for tuples which are used as - object/hash keys, to ensure that they keep a stable sort order. -*/ -CWAL_CONTAINER_DISALLOW_PROP_SET = 0x0001, - -/** - Tells the engine that setting NEW properties on this container is - not allowed, and should trigger a CWAL_RC_DISALLOW_NEW_PROPERTIES - error. - - Note that this restriction does not apply to array indexes, but it - probably should. That behaviour may change in the future. -*/ -CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES = 0x0002, - -/** - Specifies that cwal_value_prototype_set() should fail with code - CWAL_RC_DISALLOW_PROTOTYPE_SET for a value with this flag. -*/ -CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET = 0x0004, - -CWAL_CONTAINER_RESERVED1 = 0x0008 /* DISALLOW_LIST_SET? arrays? */, -CWAL_CONTAINER_RESERVED2 = 0x0010 /* DISALLOW_HASH_INSERT? */, - -/** - EXPERIMENTAL and likely to never enter public service. A flag for - Functions indicating that they are to be treated as property - interceptors via certain getter/setter APIs. -*/ -CWAL_CONTAINER_INTERCEPTOR = 0x0020, -/** - EXPERIMENTAL and likely to never enter public service. Transient - flag to avoid recursion. -*/ -CWAL_CONTAINER_INTERCEPTOR_RUNNING = 0x0040 - -}; - -/** - Returns s's property storage object, instantiating it if - necessary. If s is NULL, or on allocation error, it returns - NULL. The returned Value will be of "some container type," - currently Object or Hashes, depending on whether the - CWAL_FEATURE_SCOPE_STORAGE_HASH flag is active when the properties - are initialized. - - s holds a reference to this container and the API offers no way to - take ownership away from s, other than prying it from s's cold, - dead fingers (which we'll leave as an exercise to the reader (tip: - reference and rescope the properties before popping s)). - - It is strongly recommended that client code NOT expose this value - via script-side features. The temptation to do so is strong, but - all of the potential side-effects of doing so are not yet fully - understood, and there are caveats vis-a-vis vacuum-safety. -*/ -cwal_value * cwal_scope_properties( cwal_scope * s ); - -/** - Returns the parent scope of s, NULL if !s or s has - no parent. -*/ -cwal_scope * cwal_scope_parent( cwal_scope * s ); - -/** - Returns the top scope in s's stack, NULL if !s. -*/ -cwal_scope * cwal_scope_top( cwal_scope * s ); - -/** - Searches s and optionally its parents for the given key. If - maxDepth is greater than 0 then only up to that many scope - levels is searched. If maxDepth is less than 0 then any number - of parent levels can be searched. A maxDepth of 0 means to - search only s. - - If foundIn is not NULL, it is assigned the scope in which - the property is found. - - Returns NULL if !s, !k, or no entry is found, else returns the - searched-for value. - - See cwal_prop_get_kvp_v() for import notes about the - lookup/property key comparisons. - - @see cwal_prop_get_kvp_v() - @see cwal_scope_search() -*/ -cwal_value * cwal_scope_search_v( cwal_scope * s, int maxDepth, - cwal_value const * k, - cwal_scope ** foundIn ); - -/** - Similar to cwal_prop_get_kvp_v(), but searches through a scope - (optionally recursively). upToDepth is interpreted as described - for cwal_scope_search_v(). If a match is found, the underlying - key-value pair is returned and foundIn (if not 0) is assigned to - the scope in which the match was found. - - Returns 0 if no mach is found, !s, or !key. - - This routine explicitly does not search for properties via - prototypes of the scope's property storage. i.e. only "declared" - variables can be found this way, not properties inherited by - the underlying property storage object/hash. - - ACHTUNG: the returned object is owned by an object (or hash) which - is owned either by the scope the key is found in or an older one, - and it may be invalidated on any modification of that object - (i.e. any changing of properties in that scope). i.e. don't hold on - to this, just grab its flags or whatever and let go of it. - - @see cwal_scope_search_kvp_v() - @see cwal_scope_search() -*/ -cwal_kvp * cwal_scope_search_kvp_v( cwal_scope * s, - int upToDepth, - cwal_value const * key, - cwal_scope ** foundIn ); - -/** - The C-string counterpart of cwal_scope_search_kvp_v(). The first - keyLen bytes of key are used as the search key. - - See cwal_prop_get() for details about the lookup key - comparison. - - @see cwal_scope_search_kvp_v() - @see cwal_scope_search() -*/ -cwal_kvp * cwal_scope_search_kvp( cwal_scope * s, - int upToDepth, - char const * key, - cwal_midsize_t keyLen, - cwal_scope ** foundIn ); - - -/** - Functionally equivalent to cwal_scope_search_v() except that it - takes a C-string key and can only match String-typed keys. The - first keyLen bytes of key are used as the search key. - - Returns as described for cwal_scope_search_v(), - and also returns NULL if (!key). - - See cwal_prop_get() for details about the lookup key - comparison. - - @see cwal_scope_search_kvp_v() - @see cwal_scope_search_kvp() -*/ -cwal_value * cwal_scope_search( cwal_scope * s, - int maxDepth, - char const * key, - cwal_midsize_t keyLen, - cwal_scope ** foundIn ); - -/** - Identical to cwal_scope_chain_set_with_flags_v(), passing - CWAL_VAR_F_PRESERVE as the final parameter. -*/ -int cwal_scope_chain_set_v( cwal_scope * s, int upToDepth, - cwal_value * key, cwal_value * val ); - -/** - Sets a property in s or one of its parent scopes. If upToDepth - is 0 then the property will be set in s, else s and up to - upToDepth parents will be searched for the key (e.g. a value of - 1 means to check this scope and its parent, but no higher). If upToDepth - is negative it means "arbitrarily high up in the stack." If - it is found then it is set in the scope it was found in, else - it is set in s. - - To unset a key, pass a val of NULL. - - Returns 0 on success. Its error codes and the flags are the same as - for cwal_prop_set_with_flags_v(), with one exception: if scopes are - configured to use hashes for property storage then this routine - will (as of 20191211) return CWAL_RC_IS_VISITING_LIST if the - property storage hash in which the property would be set is - currently being iterated over or is otherwise temporarily locked - against modification. -*/ -int cwal_scope_chain_set_with_flags_v( cwal_scope * s, int upToDepth, - cwal_value * k, cwal_value * v, - uint16_t flags ); - -/** - Identical to cwal_scope_chain_set_with_flags(), passing - CWAL_VAR_F_PRESERVE as the final parameter. -*/ -int cwal_scope_chain_set( cwal_scope * s, int upToDepth, - char const * k, cwal_midsize_t keyLen, - cwal_value * v ); - -/** - The C-string form of cwal_scope_chain_set_v(), except that only the - first keyLen bytes of k are considered as the search key. - - See cwal_prop_get() for details about the lookup key - comparison. -*/ -int cwal_scope_chain_set_with_flags( cwal_scope * s, int upToDepth, - char const * k, cwal_midsize_t keyLen, - cwal_value * v, uint16_t flags ); - -/** - Copies properties from src to dest, retaining any flags set for - those properties. If src is-a hashtable, its hash entries are - used, otherwise if src is a container, those properties are used. - - Returns 0 on success, CWAL_RC_MISUSE if either argument is null, - CWAL_RC_TYPE if dest is not properties-capable. Returns CWAL_RC_OOM - if allocation of any resource fails while copying properties. If a - client has made any properties of the scope "const" - (CWAL_VAR_F_CONST) then this function will fail with - CWAL_RC_CONST_VIOLATION if an attempt is made to import a symbol - with the same key. -*/ -int cwal_scope_import_props( cwal_scope * dest, cwal_value * src ); - -/** - "Declares" a variable in the given scope. Declaring is almost - identical to setting (cwal_scope_chain_set() and friends) but fails - with CWAL_RC_ALREADY_EXISTS if the given entry is already declared - (or set) in s. In addition, if v==NULL then cwal_value_undefined() - is used as the default. - - If s is NULL then e's current scope is used. If e is 0 then s's - engine is used. If both are NULL, CWAL_RC_MISUSE is returned. - - Returns CWAL_RC_MISUSE if key is NULL or empty (or otherwise - starts with a NUL byte). - - Returns CWAL_RC_OOM on allocation error. - - Returns (as of 20191211) CWAL_RC_IS_VISITING if s's property - storage *object* is currently being iterated over or is otherwise - locked, and CWAL_RC_IS_VISITING_LIST if s's propert storage *hash* - is in that state. -*/ -int cwal_var_decl_v( cwal_engine * e, cwal_scope * s, cwal_value * key, cwal_value * v, - uint16_t flags ); -/** - Functionally identical to cwal_var_decl_v(), but takes a - C-style string (key) which must be keyLen bytes long. -*/ -int cwal_var_decl( cwal_engine * e, cwal_scope * s, char const * key, - cwal_midsize_t keyLen, cwal_value * v, - uint16_t flags ); - -/** - cwal_value_unref() is THE function clients must use for - destroying values allocated via this framework. It decrements - the reference count of a cwal_value, cleaning up if needed. - - Whether or not clients must (or should) call this function - depends on how the values are used. Newly-created values have a - reference count of 0. This reference count is increased when - the value is added to a container (as a key _or_ value) or the - client calls cwal_value_ref(). If a client calls - cwal_value_ref() then he is obligated to call this function OR - allow the scope to clean up the value when the scope is - popped. If a client creates a new value, does not explicitly - reference, but adds it to a container (implicitly referencing - it) then he must _NOT_ call cwal_value_unref() (doing so will - leave a dangling pointer in the container). - - Caveat: unref'ing a STRING value without having explicitly - referenced it (cwal_value_ref()) can potentially be dangerous - if string interning is enabled and more than one (shared) - reference to that string is alive. This does not apply to - other value types. - - It is never _necessary_ to call this function: if it is not - called, then the value's owning scope will clean it up whenever - the scope is cleaned up OR (for newly-allocated values with no - refcount) during a sweep or vacuum operation (see - cwal_engine_sweep() and cwal_engine_vacuum()). - - The safest guideline for client usage, unless they really know - what they're doing and how to use/abuse this properly: - - - DO NOT EVER call this function UNLESS one of the following - applies: - - A) You have called cwal_value_ref() on that value. - - B) You created the value using cwal_new_TYPE() (or one of the - various convenience variants) AND it is NOT a CWAL_TYPE_STRING - value (x-strings and z-strings ARE safe here). - - All other uses, unless the caller is intricately familiar with - cwal's memory management, "might" be dangerous. - - String interning (if enabled) leaves open a corner case where it is - not safe to call this on a string (CWAL_TYPE_STRING) value unless - every instance of that string has been explicitly - cwal_value_ref()'d string or the client otherwise is very, very - (almost inconceivably VERY) much certain about its ownership. Note - that string interning does not apply to x-strings and z-strings, so - they are "safe" in this context. This does not mean string - interning is unsafe in general (in normal uses cases it works just - fine), just that it opens up a corner case involving shared strings - which does not apply to other types. Specifically: if clients - ref/unref interned strings 100% symmetrically (meaning always ref - and always unref), there will will/should be no problems. That is - not feasible to do in some client code, however, in particular - where temporary values are involved (which is where interning - causes the the headaches). - - Clients MUST treat this function as if it destroys v; it has - semantically the same role as free(3) and v must not be used by - the client after this function is called. Likewise, the return - values may, for essentially all purposes, be ignored by the - client, but this function returns a value to describe what it - actually does, the semantics of which are somewhat different - from the rest of the framework (i.e. non-0 is not necessarily - an error): - - CWAL_RC_MISUSE if !v. - - CWAL_RC_OK if this function does nothing but that's not an - error (e.g. if passed a handle to one of the built-in constant - values). - - CWAL_RC_DESTRUCTION_RUNNING if v is currently being destroyed. This - result should ONLY be returned while destructing a graph in which v - has cycles. Client code should never see this unless they are doing - manual cleanup of container values in the destructors of their own - custom cwal_native implementations. - - CWAL_RC_HAS_REFERENCES if v was not destroyed because it still has - pending references. - - CWAL_RC_FINALIZED if this function actually finalizes the value - (refcount drops to (or was) zero). - - Implementation notes: - - - This function might recycle v's memory for the next allocation of - the same value type. Some types are not recycled or are recycled - differently (namely strings because their allocation mechanism - limits how well we can recycle them). The interning mechanism, - however, (if enabled) ensures that we don't need to alloc/free - strings in many common usage patterns. - - - Note that built-in/constant values do not actually participate in - reference-counting (see cwal_value_is_builtin()) but, for reasons - of consistency, should be treated as if they do, and should be - passed to this function just like any other values (it is a - harmless no-op). - - @see cwal_value_ref() - @see cwal_value_unhand() - @see cwal_refunref() -*/ -int cwal_value_unref( cwal_value * v ); - -/** - A very close relative of cwal_value_unref(), this - variant behaves slightly differently: - - - If v is NULL or has no refcount, it is left - untouched. Builtin values are implicitly covered by this - condition. - - - If v has a positive refcount, its refcount is reduced by 1. - If the refcount is still above 0, there are no further - side-effects. If the refcount drops to 0, v is _reprobated_ in - its current owning scope. That means that it gets moved into - the list of values which which can be swept up by - cwal_engine_sweep() and friends. It will not, however, be - immediately destroyed by reaching a refcount of 0. - - Returns v with one exception (which will never happen in perfectly - well-behaved code): if v is not NULL and this function can - determine that v is no longer valid (e.g. it's officially been - destroyed, is awaiting destruction, or is sitting in a recycling - bin) then it will assert() and then return NULL. (The assert() is a - no-op in non-debug builds.) If it ever returns NULL when passed - non-NULL then something quite fatal has happened and the app should - treat it as a cwal-fatal error. That will only happen if the value - is involved in serious reference mismanagement. Continuing to use - such a corrupted Value from client code leads to Undefined - Behaviour. - - The intended use of this function is to "let go" of a value with a - clean conscience when propagating it, without outright destroying - it or having to be unduly uncertain about whether it will survive - the currently-evaluating expression. This allows them to take - advantage of scope lifetimes and sweep intervals (both of which - they control) to more closely manage the lifetimes of values which - are potentially temporary but will possibly be used as result - values (meaning that they need to survive a bit longer, despite - having no clear owner-via-reference-count). - - Example: - - @code - int myfunc( cwal_engine * e, cwal_value ** result ){ - int rc; - cwal_value * v = 0; - v = cwal_new_string_value(e, "hi, world...", 12); - if(!v) return CWAL_RC_OOM; - cwal_value_ref(v); - rc = ... do useful work ...; - if(rc){ // error! - // probably destroys x (if it's not gained another ref via - // whatever work we did; - cwal_value_unref(v); - }else{ - // remove our reference to v without the possibility of - // destroying it, so that it has a predictable refcount - // when the caller gets it... - *result = cwal_value_unhand(v); - } - return rc; - } - @endcode - - API change: before 2018-11-24 this function returned void. - - @see cwal_value_ref() - @see cwal_value_unref() - @see cwal_refunref() -*/ -cwal_value * cwal_value_unhand( cwal_value * v ); - -#if 0 -/** - A convenience alias for cwal_value_ref(). -*/ -#define cwal_ref(V) cwal_value_ref(V) -/*int cwal_unref( cwal_value * v );*/ - -/** - A convenience alias for cwal_value_unref(). -*/ -#define cwal_unref(V) cwal_value_unref(V) -/*int cwal_unref( cwal_value * v );*/ - -/** - A convenience alias for cwal_value_unhand() -*/ -#define cwal_unhand(V) cwal_value_unhand(V) -/*cwal_value * cwal_value_unhand( cwal_value * v );*/ -#endif - -/** - Increments v's reference count by 1 unless v is a built-in, in - which case this is a harmless no-op. - - Returns 0 on success. The error conditions include: - - - CWAL_RC_MISUSE: v is NULL. This is harmless and okay, and lots of - real client-side code passes a pointer to this routine without - checking whether it's NULL or not. - - - CWAL_RC_MISUSE or an assert(): v has no scope, which indicates an - internal error or memory mis-use/corruption (e.g. trying to ref a - value which is currently undergoing destruction). An assert() will - be triggered or CWAL_RC_MISUSE will be returned. - - - CWAL_RC_RANGE: incrementing would overflow past compile-time - boundaries. This limit is, for all but the most intentionally - malicious of purpsoses, unreachable. If this indeed ever happens - then an assert() is triggered (in debug builds) and the associated - cwal_engine is internally flagged as being "fatally dead", which - will cause some several other functions to fail with this same - result. - - In practice, the result value is ignored, as cwal will assert() - here if it detects any serious lifetime mismanagement problems and - there is no generic recovery strategy from what ammounts to memory - corruption. - - Note that some built-in/constant values do not actually participate - in reference-counting but, for reasons of consistency, should be - treated as if they do, and may be passed to this function just like - any other values. (See cwal_value_is_builtin() for the list of - shared/constant values.) - - Claiming a reference point never requires a new allocation. - - Calling this function obligates the client to eventually either - call cwal_value_unref() or cwal_value_unhand() to release the - reference OR be content to wait until the value's owning scope - (eventually) cleans up (at which point this value is freed - regardless of its reference count). - - @see cwal_value_unref() - @see cwal_value_unhand() -*/ -int cwal_value_ref( cwal_value * v ); - -/** - This is a obscure lifetime/cleanup related hack which can sometimes - be used to clean up temporaries (values with a refcount of 0) - without affecting non-temps (those with a positive refcount, or - built-in values). Specifically, it is for cleaning up values which - _might_ live in a higher scope but are temporaries. In some - contexts (during cleanup of lower scopes), such values must be - "kept around" by the engine, reinstated as temporaries (in their - owning scope) instead of cleaning them up, in order to be able to - handle value propagation through stack popping. - - Do not use this function without understanding exactly what it is - for and when it is safe to use. It is often NOT safe to use, but - only the higher-level environment can know for sure. - - This is functionally equivalent to: - - @code - cwal_value_ref(v); - cwal_value_unref(v); - @endcode - - but is conserably more efficient, in that it avoids any - intermediate movement of values those routines have to do on - temporaries. This is (like those functions) a no-op on - built-in/constant values (and clients should treat those just like - any other values, so they're safe to pass here). - - The return result should be ignored by clients - it is only - intended for use by cwal-level test code to ensure its proper - functioning. ITS RETURN SEMANTICS ARE NOT PART OF THE PUBLIC API, - but for the curious: it returns 0 (false) if this function has no - side effects, true (non-0) if v is immediately destroyed by this - call. **HOWEVER**: this info is of informational purposes only and - should be ignored outside of test code. It MUST NOT to be used for - any sort of application logic (e.g. "if not destroyed, unref it," - because that type of logic leads to all sorts of pain and suffering - in cwal). This function MAY change to a void return at some point, - so don't get used to actually checking the return value. - - Explaining when and why a client would want to do this would - require that i refine the two and half years of experience which - went into creating (well, stumbling upon) this (trivial) hack, - and i'm finding that difficult to do. In short... - - When dealing with values created from sources other than the local - function, it is, more often than not, not generically safe to unref - them. In some particular cases (specific to the client application) - it becomes, as a side effect of the other Value lifetime-related - machinery, possible to solve the problem of "kill the value or - not?" by simply adding a reference, then removing the - reference. The side effect is that temporary values (those with a - refcount of 0) will be destroyed by the call to cwal_value_unref(), - but it has no lasting effect on non-temporaries. This routine is - more efficient, however, in that it avoids the moving around of - temparies into and out of the this-is-a-temp list (values of - refcount 0 are internally kept in a separate list to facilitate - sweeping and re-temp'ing of values in some cases after their - refcount was formerly positive). While such movement of values is - O(1), this routine offers a notably faster O(1) and has the same - effect. - - cwal_value_unhand() is a close cousin of this routine. - - @see cwal_value_unhand() -*/ -bool cwal_refunref( cwal_value * v ); - -/** - Sets v as the (single) specially-propagating result value for - e. This is only to be used by keywords which toss a value up - the call stack, and use non-0 result codes to do so, but are not - necessarily errors. e.g. return, break, exit. - - If v is not 0, a reference is added to v, held by e. - - If v is 0, any propagating value is removed from the - propagating value slot. - - Regardless of whether or not v is 0, if e has a prior - propagating value, it gets unref'd after referencing v (see - cwal_value_unref()), possibly destroying it immediately. - - Returns v. - - @see cwal_propagating_get() - @see cwal_propagating_take() -*/ -cwal_value * cwal_propagating_set( cwal_engine * e, cwal_value * v ); - -/** - Returns the currently specially-propagating value from e, if - any. Ownership of the value is not modified by this call, and e - still holds a reference to it (if the value is is not 0). - - The intention is that this value will be set for an as-yet - unhandled RETURN or BREAK statements, as well as for an EXIT or - FATAL (which necessarily can't be handled until the app level, - or it loses its functionality). - - In practice, this routine is generally only used to check whether a - propagating value has been set up, and cwal_propagating_take() is - used for taking over ownership of that value. - - @see cwal_propagating_take() - @see cwal_propagating_set() -*/ -cwal_value * cwal_propagating_get( cwal_engine * e ); - -/** - Effectively the same as calling cwal_propagating_get() followed by - cwal_propagating_set(se,0), except that this keeps the pending - value alive even if its refcount drops to 0. Returns the result of - the first call. Note that the returned value may very well be a - temporary awaiting a reference before the next sweep-up. - - @see cwal_propagating_get() - @see cwal_propagating_set() -*/ -cwal_value * cwal_propagating_take( cwal_engine * e ); - -/** - This sets the given value to be e's one and only "exception" - value. This value is given special treatment in terms of - lifetime - it wanders up the call stack as scopes are popped, - until the client calls this again with x==NULL. - - x may be NULL, in which case any pending exception is cleared - and its handle gets unreferenced (see cwal_value_unref()) and - CWAL_RC_OK is returned. Otherwise... - - If x is not NULL then this function returns CWAL_RC_EXCEPTION - on success(!!!) or a different non-0 cwal_rc value on - error. The justification for this is so that it can be called - in as the return expression for a callback which is throwing - the exception. The exception value gets a reference held by the - engine, and any pending exception is cwal_value_unhand()ed. - - If x is NULL then 0 indicates success. - - Interpretation of the exception value is client-dependent, - and cwal's only special handling of it is ensuring that - it survives the ride up a popping scope stack. - - While the exception value may be of any cwal Value type, the - cwal_exception class is specifically intended for this purpose. - - Typical usage: - - @code - // assuming we want to keep an existing exception: - cwal_value * exc = cwal_exception_get(e); - cwal_value_ref(exc); - cwal_exception_set(e, 0); - cwal_value_unhand(exc); - - // Or, if we do not need to keep the old value and we KNOW that - // nobody is holding a pointer to the exception without also - // having added a reference, then simply: - cwal_exception_set(e, 0); - @endcode - - @see cwal_exception_get() -*/ -int cwal_exception_set( cwal_engine * e, cwal_value * x ); - -/** - Convenience form of cwal_exception_set() which uses - cwal_new_stringfv() to create an exception message string for - the new cwal_exception value (which can be fetched using - cwal_exception_get()). See cwal_printf() for the supported - formatting options. - - code is the exception's error code. fmt and following arguments - are the formatted error message. - - If !*fmt then this call creates an exception value with no - message part. - - As a special case, certain code values will skip the creation of an - exception and simply return that code. Currently CWAL_RC_OOM is the - only such case. -*/ -int cwal_exception_setfv(cwal_engine * e, int code, char const * fmt, va_list args); - -/** - Identical to cwal_exception_setfv() but takes its arguments in ellipsis form. -*/ -int cwal_exception_setf(cwal_engine * e, int code, char const * fmt, ...); - -/** - If e has a current exception value, it is returned to the - caller and (possibly) transfered into the calling scope (for - lifetime/ownership purposes). If not, NULL is returned. - - Note that the lifetime of the exception value is managed internally - by the engine to ensure that it survives as scopes are popped. If - the client wants to stop this from happening for a given exception - value, he should use cwal_exception_set() to set the current - exception state to 0 (and use cwal_value_ref() to get a reference, - if needed). That will, if the exception has a reference, keep the - (previous) current exception rooted in its current scope, from - which it will wander only if it is later referenced by/via an older - scope. -*/ -cwal_value * cwal_exception_get( cwal_engine * e ); - -/** - NOT IMPLEMENTED. - - Frees any message-related memory owned by err (or shared with it, - in the case of err->value). - - Returns 0 on success, or CWAL_RC_MISUSE if either paramter is 0. - - After calling this, err contains an empty state and must eventually - be deallocated using whatever mechanism complements its allocation - (e.g. do nothing more for stack-allocated objects or those embedded - in another struct). -*/ -/*int cwal_exception_info_clear( cwal_engine * e, cwal_exception_info * err );*/ - -/* NOT IMPLEMENTED. */ -/*int cwal_engine_err_set( cwal_engine * e, cwal_exception_info * err );*/ - -/** - Returns a pointer to e's current output handler. For purposes of - swapping them in and out, the returned value should be - bitwise-copied for later swapping in via - cwal_engine_outputer_set(). Do not store its pointer, as the - result's address is stable for a given cwal_engine instance, but - its contents may be swapped in and out (if done so carefully). - - This will never return NULL unless e is NULL. - - Remember that cwal_outputer::output may legally be NULL, so don't - just assume that the returned handler can actually output anything. - - @see cwal_engine_outputer_set() -*/ -cwal_outputer const * cwal_engine_outputer_get( cwal_engine const * e ); - -/** - If tgt is not NULL, this function bitwise-copies e's current output - handler to *tgt. Bit-wise copies the replacement, making it the - new output handler. - - @see cwal_engine_outputer_get() -*/ -void cwal_engine_outputer_set( cwal_engine * e, - cwal_outputer const * replacement, - cwal_outputer * tgt ); - -/** - Sends (src,n) through the engine-specified output mechanism - (specified via its vtab). See cwal_output_f() for the - semantics. Returns 0 on success: - - CWAL_RC_MISUSE: e or src are 0. - - Any other error code is propagated from the output routine. - - This function is a no-op if n==0. - - TODO? consider making (0==src, 0==n) a heuristic for signaling - a desire to flush the output. -*/ -int cwal_output( cwal_engine * e, void const * src, cwal_size_t n ); - -/** - If e's vtab is set up to be able to flush its output channel, - this calls that function and returns its result. Returns - CWAL_RC_MISUSE if !e or e is not initialized. Returns 0 on - success or if there is nothing to do. -*/ -int cwal_output_flush( cwal_engine * e ); - -/** - printf()-like variant of cwal_output(). See cwal_printf.h for - the format specifiers (they're pretty much standard, plus some - extensions inherited from sqlite). -*/ -int cwal_outputf( cwal_engine * e, char const * fmt, ... ); - -/** - va_list variant of cwal_outputf(). -*/ -int cwal_outputfv( cwal_engine * e, char const * fmt, va_list args ); - -/** - The cwal_new_VALUE() function does not really exist - it is - here for documentation purposes to consolidate the common - documentation for the large family of cwal_new_xxx() routines. - These routines typically come in some variation of these - three forms: - - 1) cwal_value * cwal_new_SOMETHING(); - 2) cwal_value * cwal_new_SOMETHING(cwal_engine*); - 3) cwal_SOMETHING * cwal_new_SOMETHING(cwal_engine*, ...); - - The first form is only for types which do not allocate memory, - meaning types with a known set of constant values (boolean, - undefined, null). - - The second form is only for types which need no initialization - parameters, e.g. Objects and Arrays. - - The third form is used by types which require more information for - their initialization. Most such types represent immutable data, - with values which cannot be changed for the lifetime of the - cwal_value handle. - - Ownership of the new returned value is initially held by the - scope which is active during creationg. A newly-created value - has a reference count of 0 (not 1, though it was in versions - prior to 20130522). A value with a refcount of 0 is considered - a "probationary" value, and has a special status in the - scope-sweep operations. In short, a sweep operation will free - up _all_ values with refcount 0 in the scope. If clients need - to ensure a specific lifetime, they must provide the value with - a reference. This can happen in one of several ways: - - - Insert the value into a container. e.g. set it as an Object - key or value, or insert it into an Array. - - - Call cwal_value_ref() to increase the refcount by 1. - - If a Value is ever referenced, perhaps indirectly, from an older - scope, it is automatically moved into that scope for - ownership/cleanup purposes. This ensures that Values live as long - as the last scope which references them, or until they are - otherwise cleaned up. - - Semantically speaking this function Returns NULL on allocation - error, but the non-allocating factories never actually allocate - (and so cannot fail). Nonetheless, all Values returned by - variations of this function must be treated as if they are an - allocated Value (this consistency is encouraged to avoid - clients special-casing code due to a cwal-internal - implementation detail). - - General rules for the cwal_new_XXX() family of functions (all of which - point the reader to this function) are: - - - Those which (might) allocate memory take a cwal_engine value as - their first argument. Non-allocating factories SHOULD be treated as - if they allocate, e.g. by assuming that they participate in the - normal reference-counting and de/allocation mechanism (which they - don't, actually). Note that cwal_new_string() and friends can - return a re-used pointer for an interned string, and it is - criticial that the client call cwal_value_ref() to increase the - reference count by 1 to avoid any problems vis-a-vis string - interning, and each must be followed by a call to - cwal_value_unref(). It is worth noting that string interning has - historically caused much Grief with regards to reference counts, - but it behaves properly if (and only if) all string-allocating - client code properly refs/unrefs each returned string (even if it's - a re-used/interened string). Similarly, cwal_new_string() and - friends may (depending on build-time options) return - static/shared/constant memory for length-1 ASCII strings, but those - should also be treated as if they were made up of - dynamically-allocated memory. - - - To re-iterate: certain built-in/constant values neither allocate - nor participate in reference-counting/scope-tracking, but that is - an internal implementation detail, and clients should treat all - values as equivalent for memory-management purposes except for - noted for specific APIs. - - - All newly-allocated values initially have a reference count of - 0. Clients must call cwal_value_ref() to claim a reference point, - and adding values to containers also manipulates their reference - count. Except for strings, client code may call cwal_value_unref() - to unreference a newly-created (not yet ref'd) value, possibly - cleaning it up (depending on other references to the value). For - strings, due to string interning, unref'ing is extremely unsafe - unless the client code called cwal_value_ref() on that instance. - - Additional notes and bits of wisdom earned via long-time use of - this library... - - When inserting new values into containers, the safe/proper thing to - do is to first reference the value, then perform the insertion, - then, regardless of whether the insertion worked, unref the - value. For example: - - @code - int rc; - cwal_value * v = cwal_new_integer(myEngine, 42); - if(!v) return CWAL_RC_OOM; - cwal_value_ref(v); // always obtain a reference - rc = cwal_array_append(myArray, v); - cwal_value_unref(v); // unref after the insertion - if(rc) return rc; - // On insertion success, the container now has the only reference - // to v, and v is still alive. On error, v was cleaned up by - // our call to cwal_value_unref(). - v = 0; - @endcode - - If the container insertion succeeds, that container will have - obtained a reference to the Value. If insertion fails, it will not. - Either way, our call to cwal_value_unref() removes our local - reference to the Value, making it _semantically_ illegal for us to - refer to the that Value again. - - Code like the following will "normally" work but has some - non-obvious pitfalls which may bite the client mightily somewhere - down the road: - - @code - // Expanding on the example above... - // DO NOT DO THIS... DO NOT DO THIS... DO NOT DO THIS... - v = cwal_new_integer(myEngine, 42); - // v has a refcount of 0 - rc = cwal_array_append(myArray, v); - // On success ^^^^ v has a refcount of 1, on error 0. - if(rc) cwal_value_unref(v); // cleans up v - v = 0; - @endcode - - That would actually work fine (most of the time) for most types, - but that approach breaks down in conjunction with cwal's - string-interning feature (and _potential_ future interning/re-use - features for other Value types (e.g. integers)). In the case of - interned/reused values, the cwal_new_XXX() call will return an - existing value but not increase its reference count, meaning that - any unref in the above block is strictly illegal (because it - doesn't obtain its own reference via cwal_value_ref()). In - practice, misuse like the above may not trigger a problem until - much later (especially when recycling is enabled, as that can - temporarily hide this type of misuse), and will eventually trigger - an assert() in cwal. - - Likewise, do not do the following: - - @code - // Expanding on the example above... - // DO NOT DO THIS... DO NOT DO THIS... DO NOT DO THIS... - rc = cwal_array_append(myArray, cwal_new_integer(myEngine, 42)); - @endcode - - The problem there is that if the container insertion fails, we've - "leaked" the newly-created Value and we have no way of referencing - that value ever again. That "leaked" value will be cleaned up when - the current cwal scope (see cwal_scope_push()) is popped. Likewise, - the "leaked" value, because it has no reference count, would be - cleaned up by a call to cwal_engine_sweep() or cwal_engine_vacuum() - if that call were made within the context of the current cwal - scope. So the value is not "really" leaked, in the classic sense of - a memory leak, but it is effectively leaked until cwal gets around - to cleaning it up (which, depending on client-specific usage, might - not be until the cwal_engine instance is finalized). -*/ -void cwal_new_VALUE(cwal_engine * e, ...); - -/** - Creates a new cwal_value from the given boolean value. - - Note that there are only two boolean values and they are singletons - - the engine never allocates memory for booleans. - - @see cwal_new_VALUE() -*/ -cwal_value * cwal_new_bool( int v ); - - -/** - Semantically the same as cwal_new_bool(), but for doubles. - - cwal's numeric-type Values (doubles and integers) are immutable - - they may never be modified after construction. - - The engine reserves the right to compile certain numeric Values as - built-in constants. Such values do not require any dynamic memory - but behave just like normal, dynamically-allocated Values except - that: - - - All refcount operations on such values are no-ops. - - - All flag-setting operations (e.g. vacuum-proofing) on such values - are no-ops. - - - All such values are immune to garbage-collection. - - Nonetheless, client code must behave exactly as if such values were - "normal" values, and all cwal APIs support this (by ignoring, where - appropriate, operations on built-in values). e.g. client code must - ref/unref these Values as if they were normal Values, even though - those operations are no-ops for built-in constants. - - Which, if any, numeric Values are built-in constants may differ in - any given build of this library. Client code must never attempt to - apply different logic to built-in values, as that logic may or may - not apply to any given build. - - @see cwal_new_VALUE() - @see cwal_new_bool() -*/ -cwal_value * cwal_new_double( cwal_engine * e, cwal_double_t v ); - -/** - Semantically the same as cwal_new_double(), but for integers. - - @see cwal_new_VALUE() - @see cwal_new_double() -*/ -cwal_value * cwal_new_integer( cwal_engine * e, cwal_int_t v ); - -/** - Returns a new "unique" value. Unique values are unusual in that: - - - Their identity is their value. - - - They may optionally wrap a single other value. They acquire a - reference to that value and unref it up when the Unique is cleaned - up. The value gets upscoped, if necessary, as the Unique gets - upscoped (but the reverse is not true: the binding is not - two-way!). - - - They always resolve to true in a boolean context (i.e. via - cwal_value_get_bool()). - - - They never compare as equivalent to any value other than - themselves (not even cwal_value_true(), even though it evaluates to - true in a boolean context). - - They are intended to be used as opaque sentry value or possibly as - a sort of enum entry substitute. - - Returns 0 if !e or on OOM, else it returns a new value instance - with a refcount of 0. - - Sidebar: unique values are not containers, per se, and cannot hold - any per-instance properties. Nonetheless, unlike most - non-containers, they may participate in cycles. Also, unlike most - other types, Uniques have no higher-level class representation - (i.e. there is no Unique-type counterpart of cwal_object or - cwal_string). - - @see cwal_unique_wrapped_set() - @see cwal_unique_wrapped_get() -*/ -cwal_value * cwal_new_unique( cwal_engine * e, cwal_value * wrapped ); - -/** - If uniqueVal is of type CWAL_TYPE_UNIQUE (was created using - cwal_new_unique()) and it has a wrapped value, that value is - returned, otherwise 0 is returned. Ownership of the returned - value is not modified. - - @see cwal_unique_wrapped_set() - @see cwal_new_unique() -*/ -cwal_value * cwal_unique_wrapped_get( cwal_value const * uniqueVal ); - -/** - If uniqueVal is of type CWAL_TYPE_UNIQUE then this function sets - uniqueVal's wrapped value to w. Any prior wrapped value is unref'd - and may be cleaned up immediately. - - Returns 0 on success, CWAL_RC_TYPE if uniqueVal is not a - Unique-type value, CWAL_RC_CYCLES_DETECTED if w==uniqueVal. (Note - that it does not catch nested cycles-to-self, as those are legal - (and would take arbitrarily long to find if they weren't).) - - w may be 0, but if the client wants to keep any current wrapped - value alive, he needs to ensure he's got a reference point for it - before passing 0 here, as this routine will destroy it if its - reference count drops to 0 during this call. - - This is a no-op, returning 0, if w is the same value uniqueVal - already wraps. - - @see cwal_unique_wrapped_get() - @see cwal_new_unique() - -*/ -int cwal_unique_wrapped_set( cwal_value * uniqueVal, cwal_value * w ); - - -/** - Returns the special "null" singleton value. - - See cwal_new_VALUE() for notes regarding the returned value's - memory. - - @see cwal_new_VALUE() -*/ -cwal_value * cwal_value_null(void); - -/** - Returns the special "undefined" singleton value. - - See cwal_new_VALUE() for notes regarding the returned value's - memory. - - @see cwal_new_VALUE() -*/ -cwal_value * cwal_value_undefined(void); - -/** - Equivalent to cwal_new_bool(0). - - @see cwal_new_VALUE() - @see cwal_new_bool() -*/ -cwal_value * cwal_value_false(void); - -/** - Equivalent to cwal_new_bool(1). - - @see cwal_new_VALUE() - @see cwal_new_bool() -*/ -cwal_value * cwal_value_true(void); - -/** - Converts the given value to a boolean, using JavaScript semantics depending - on the concrete type of val: - - undef or null: false - - boolean: same - - integer, double: 0 or 0.0 == false, else true - - object, array, hash, function, unique: true - - tuple: the empty tuple is false, all others are true. - - string: length-0 string is false, else true. - - Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1. - On error (val is NULL) then v is not modified. - - In practice this is never used by clients - see - cwal_value_get_bool(). -*/ -int cwal_value_fetch_bool( cwal_value const * val, char * v ); - -/** - Simplified form of cwal_value_fetch_bool() which returns 0 if val - is "false" and 1 if val is "truthy". Returns 0 if val is NULL. -*/ -bool cwal_value_get_bool( cwal_value const * val ); - -/** - Similar to cwal_value_fetch_bool(), but fetches an integer value. - - The conversion, if any, depends on the concrete type of val: - - NULL, null, undefined: *v is set to 0 and 0 is returned. - - string, object, array: *v is set to 0 and - CWAL_RC_TYPE is returned. The error may normally be safely - ignored, but it is provided for those wanted to know whether a direct - conversion was possible. - - integer: *v is set to the int value and 0 is returned. - - double: *v is set to the value truncated to int and 0 is returned. - - In practice this is never used by clients - see - cwal_value_get_integer(). -*/ -int cwal_value_fetch_integer( cwal_value const * val, cwal_int_t * v ); - -/** - Simplified form of cwal_value_fetch_integer(). Returns 0 if val - is NULL. -*/ -cwal_int_t cwal_value_get_integer( cwal_value const * val ); - -/** - The same conversions and return values as - cwal_value_fetch_integer(), except that the roles of int/double are - swapped. - - In practice this is never used by clients - see - cwal_value_get_double(). -*/ -int cwal_value_fetch_double( cwal_value const * val, cwal_double_t * v ); - -/** - Simplified form of cwal_value_fetch_double(). Returns 0.0 if val - is NULL. -*/ -cwal_double_t cwal_value_get_double( cwal_value const * val ); - -/** - Equivalent to cwal_string_value( cwal_new_string(e,str,len) ). - - @see cwal_new_string() - @see cwal_new_VALUE() -*/ -cwal_value * cwal_new_string_value(cwal_engine * e, char const * str, - cwal_midsize_t len); -/** - Returns a pointer to the NULL-terminated string bytes of str. - The bytes are owned by string and will be invalided when it - is cleaned up. - - If str is NULL then NULL is returned. If the string has a length - of 0 then "" is returned. - - @see cwal_string_length_bytes() - @see cwal_value_get_string() - @see cwal_string_cstr2() -*/ -char const * cwal_string_cstr(cwal_string const *v); - -/** - Equivalent to cwal_string_cstr(), but if the 2nd argument is not - NULL, *len is set to the string's length, in bytes. - - @see cwal_string_cstr() -*/ -char const * cwal_string_cstr2(cwal_string const *v, cwal_midsize_t * len); - -/** - Case-folds a UTF-8 C-string, placing the result in a new - cwal_string instance. - - cstr must point to at least cstrLen bytes of valid UTF-8 text. This - function converts the case of each character to upper or lower (as - specified by the 5th parameter). - - If the 5th parameter is true (non-0), it performs up-casing, else - it performs lower-casing. It supports all 1-to-1 case conversions - and none of the 1-to-N/special-case conversion (such characters are - left as-is). - - On success *rv is set to a new String value and 0 is returned. On - error, returns: - - - CWAL_RC_MISUSE if !e, !cstr, or !rv. - - - CWAL_RC_RANGE if folding results in invalid UTF-8 characters. - - - CWAL_RC_OOM on allocation error or if the 3rd parameter exceeds - cwal's string-length limits. In such cases, *rv may be set to 0 (or - may be unmodified). - - @see cwal_string_case_fold() -*/ -int cwal_utf8_case_fold( cwal_engine * e, char const * cstr, - cwal_midsize_t cstrLen, - cwal_value **rv, - bool doUpper ); - -/** - Works just like cwal_utf8_case_fold() with the following - differences: - - - Case-folded output is appended to buf, which may cause buf->mem - to get reallocated (and thus any prior pointer to it - invalidated). To send the output to the start of a re-used buffer, - rather than appending at the end, set buf->used=0 before calling - this. - - - If len is 0, this is a no-op: buf is not modified, not even to - add a NUL byte. (Note that the buffer APIs almost always ensure tha - buffers get a NUL byte added.) - - - On success, buf->mem gets is NUL-terminated unless len is 0, in - which case the memory is not modified. On error, no guarantees are - made as to its NUL-termination. - - - This updates buf->used as it goes. After completion, the "string" - part of buf is (unless buf->mem previously held non-string data) - the first buf->used bytes of buf->mem. (Note that that range does - not include the NUL terminator, but almost all buffer APIs add one - at buf->mem[buf->used].) - - Returns CWAL_RC_MISUSE if !e, !cstr, !buf, or if cstr is part of - buf's current memory range. Otherwise it returns as documented for - cwal_utf8_case_fold(). -*/ -int cwal_utf8_case_fold_to_buffer( cwal_engine * e, char const * cstr, - cwal_midsize_t len, cwal_buffer *buf, - bool doUpper ); - - -/** - Searches for the given "needle" in the given "haystack". - - The 1st and 2nd parameters delimit the area to search. The 4th and - 5th specify the thing to look for. - - The 3rd parameter specifies an optional UTF-8 character (not byte!) - offset within the haystack to start the search. If the offset is - negative, it is counted as the number of characters from the end of - the haystack, but does not change the search direction (because - counting UTF-8 lengths backwards sounds really hard ;). - - Returns a negative value if: - - - No match is found (or cannot be found because, e.g. the needle - is larger than the haystack) or the haystack appears to not be - UTF-8. - - - Either string is NULL or either length parameter is 0. - - If a match is found, its position is returned, but its - interpretation depends on the value of the 6th parameter: - - If returnAsByteOffset is true then the returned value is the byte - offset of the match, else it is the UTF-8 character offset of the - match. - - All inputs are assumed to be valid UTF-8 text. -*/ -cwal_int_t cwal_utf8_indexof( char const * haystack, cwal_size_t hayByteLen, - cwal_int_t charOffset, - char const * needle, cwal_size_t needleByteLen, - char returnAsByteOffset ); - -/** - Identical to cwal_utf8_case_fold() except that it takes its input - in the form of a cwal_string. - - As a special case, if str is an empty string (length of 0), - *rv is set to its value part. i.e. the output will be the - input, but in its alternate type pointer. - - On 20180515 a (cwal_engine*) parameter was added because this - function otherwise fails when used on built-in static strings - (length-1 ASCII might, depending on build options, be compiled - in). We don't internally special-case that because special-casing - is ugly and because that corner case is a compile-time option which - cwal_utf.c doesn't know about. - - @see cwal_utf8_case_fold() -*/ -int cwal_string_case_fold( cwal_engine * e, - cwal_string const * str, - cwal_value **rv, - bool doUpper ); - -/** - This creates a new cwal string Value which copies the first n bytes - of str. The new string will be NUL-terminated after n bytes, - regardless of whether the input string is. - - ACHTUNG: prior to 20171005, n=0 meant that this function should use - cwal_strlen() to determine str's length. That is no longer the - case, as it caused too much special-case code client-side. n=0 now - means an empty string (i.e. copying zero bytes from the source). - - If str is NULL or n is 0, this function still returns non-NULL - value representing the empty string. (The empty string is a - library-internal constant, shared across all invocations.) This - function may return shared/constant values for certain strings - (e.g. the empty string and _possibly_ length-1 ASCII strings). Such - strings do not actually partake in the lifetime management system - (e.g. their refcount is always 0) but should, from a client's - perspective, be treated as if they are normal Values - (e.g. adding/removing references, even though such is a no-op on - built-in Values). See cwal_new_double() for more details about - built-in Values. - - If len is larger than (2^(CWAL_SIZE_T_BITS-3)) then this function - returns NULL: cwal internally reserves 3 of the size's bits for - internal information, limiting string lengths to that number of - bits. (Design note: it's either that or increase the - sizeof(cwal_string) by another (padded) increment, increasing - memory costs for all strings.) This length limit is approximately - 8KiB on 16-bit builds (meaning CWAL_SIZE_T_BITS is 16), 512MiB on - 32-bit, and rediculously high on 64-bit. The library reserves the - right to strip a further bit or two (thereby making 16-bit builds - unfeasible, but... so what?). - - Returns NULL on allocation error or a range limit violation (see - above). - - See cwal_new_VALUE() for important information about the - returned memory. - - ACHTUNG: see cwal_value_unref() for important notes involving - string interning. - - In practice, it's rare for clients to use this function: normally - cwal_new_string_value() is a more convenient choice. - - @see cwal_string_value() - @see cwal_new_string_value() - @see cwal_string_cstr() - @see cwal_string_cstr2() -*/ -cwal_string * cwal_new_string(cwal_engine * e, char const * str, - cwal_midsize_t len); -/** - printf-like form of cwal_new_string(). See cwal_printf() for - the formatting specifiers. -*/ -cwal_string * cwal_new_stringf(cwal_engine * e, char const * fmt, ...); -/** - printf-like form of cwal_new_string(). See cwal_printfv() for - the formatting specifiers. -*/ -cwal_string * cwal_new_stringfv(cwal_engine * e, char const * fmt, va_list args); - -/** - Creates a new handle for an "x-string" (as in "external"). This - is different from cwal_new_string() in that it does not copy - str's bytes. The client must guaranty that len bytes of str are - valid for at least as long as the returned value is used. i.e. - this is safe to use on static strings or on buffers which the - client can guaranty outlive the returned string. - - ACHTUNG: prior to 20171121 this function treated len=0 as a hint to - use cwal_strlen() to determine the string's length. It now - requires len to be the byte length of the input string. - - Returns NULL on error. - - The returned string cannot be differentiated from a non-external - string using the public API, with the minor exception that calling - cwal_string_cstr() on the returned string will return the same - C-string pointer passed to this function unless the string matches - one of the built-in constant strings. - - Be aware that... - - - See cwal_new_string() for size limitation notes. - - - X-strings might not be NUL-terminated, so routines which - blindly display strings until the NUL might be surprised by the - results of doing so with cwal_string_cstr(anXString). - - - Strings shorter than sizeof(char *) are not going to get any - memory benefits compared to non-X-strings. Use normal strings - for those. - - - While technically this API allows strings to be non-NUL - terminated, in practice many C APIs which get bound to scripting - engines (not just cwal) do not take a length parameter and expect - their inputs to be NUL-terminated. Thus it is HIGHLY RECOMMENDED - that clients add a NUL terminator (but don't count the NUL in the - string's length). - - - X-strings do not partake in string internalization, because doing - so would potentially invalidate lifetime guarantees. Their "empty - shells" (all but the external string pointer) participate in - recycling. - - - X-strings DO partake in the length-0-string optimization, so - cwal_new_string(e,"",0) and cwal_new_xstring(e,"",0) will - return the same value (but that's an implementation detail - clients should not make code-level decisions based on). - -*/ -cwal_string * cwal_new_xstring(cwal_engine * e, char const * str, - cwal_midsize_t len); - -/** - Equivalent to passing the return value of - cwal_new_xstring(e,str,len) to cwal_string_value(). -*/ -cwal_value * cwal_new_xstring_value(cwal_engine * e, char const * str, - cwal_midsize_t len); - - -/** - A "z-string" is closely related to an "x-string" (see - cwal_new_xstring()) in that the caller allocates the string, but - (different from x-strings), the caller gives its memory over to a - new cwal_string value. This can avoid extra copies in some cases, - e.g. by using cwal_buffer_to_zstring() to construct a string using - a buffer, then transfering its memory to a Z-string. - - The caller transfers ownership of str to this function, regardless - of success or failure, and should treat it as if it were - _immediately_ freed, using the cwal_string APIs to access it - further. - - Note that transfer of str is only legal if str was allocated by - the same underlying allocator as the rest of the library - (i.e. cwal_free(str) must be legal or Undefined Behaviour may - ensue). - - On success the ownership of str is transfered to the returned - cwal_string value and it will be freed (via cwal_free()) when the - cwal_string is freed or even possibly by this function. Thus it is - critical that clients treat the str memory as invalid after calling - this, and (to repeat) only use the cwal_string APIs to get its - string value. - - To simplify usage, if allocation of the new cwal_string fails, - this function _still_ takes over ownership of the given string - memory and frees it before returning NULL from this - call. (Design note: if we did not do this, error checking would - become more complicated and the caller would have to decide to - add extra checks or leak.) - - ACHTUNG: - - - Prior to 20171121 this function treated len=0 as a hint to use - cwal_strlen() to determine the string's length. It now requires - len to be the byte length of the input string. - - - See cwal_new_string() for size limitation notes. - - - z-strings do not participate in string interning, but their - "empty shells" (and the client-supplied string bytes) - participate in recycling of some form or another. - - - While technically this API allows strings to be non-NUL - terminated, in practice many C APIs which get bound to scripting - engines (not just cwal) do not take a length parameter and expect - their inputs to be NUL-terminated. Thus it is HIGHLY RECOMMENDED - that clients add a NUL terminator (but don't count it in the - string's length). - - - str MUST have been allocated using the same allocator as - cwal_malloc(e,...) uses or results are undefined. e.g. memory from - a cwal_buffer would be safe but memory which can from strdup(), - malloc(), or similar "might" not be. - - - str's contents MUST NOT be modified after calling this. Doing so - can lead to very unpredictable behaviour in code using the string - (e.g. hashing of keys will break). The underlying laws of physics - cwal is based on assume that string bytes are always immutable. - - The term "z-string" refers to a coding convention seen in some - source tree (not this one) where pointers to strings for which the - client owns the memory are named with a "z" prefix, e.g. zMyString. - - @see cwal_new_zstring_value() - @see cwal_new_string() - @see cwal_new_xstring() -*/ -cwal_string * cwal_new_zstring(cwal_engine * e, char * str, - cwal_midsize_t len); - -/** - Equivalent to passing the result value of cwal_new_zstring(e,str,len) to - cwal_string_value(). -*/ -cwal_value * cwal_new_zstring_value(cwal_engine * e, char * str, - cwal_midsize_t len); - - - -/** - Creates a new cwal_string value by concatenating two string - values. Returns NULL if either argument is NULL or if - allocation of the new string fails. -*/ -cwal_value * cwal_string_concat( cwal_string const * s1, cwal_string const * s2 ); - -/** - An enum holding bitmasks for toggleable cwal_engine features. - See cwal_engine_feature_flags(). -*/ -enum cwal_engine_features_e { -/** For internal use. All feature flags must have all their bits - in this range. */ -CWAL_FEATURE_MASK = 0xFF00, -/** - Used in cwal_engine::flag to specify that auto-interning should - be enabled. - - Reminder to self: these must currently reside in the high byte. - Need to check/consolidate how the internal flags (low byte) - are being used. - - ACHTUNG: see cwal_value_unref() for notes involving string - interning. In short, DO NOT use string interning unless your app - _always_ refs/unrefs all values (in particular, string values) OR - doesn't unref any (leaving it to scope-level cleanup - - _hypothetically_ that's safe, too). Failing to do so can lead to - corrupting cwal state when interned copies of strings get more - unrefs than refs (and yet the same number of refs as calls to - cwal_new_string() and friends). - - When in doubt, leave interning disabled! -*/ -CWAL_FEATURE_INTERN_STRINGS = 0x0100, - -/** - Used in cwal_engine::flags to specify that the engine should - zero out string memory before freeing it. -*/ -CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP = 0x0200, - -/** - Possibly (as yet undecided) deprecated in favor of - CWAL_OBASE_ISA_HASH. With that build-time option enabled, this flag - becomes largely obsolete. - - Used in cwal_engine::flags to tell the engine that its scopes - should create hash tables for property storage, instead of - Objects. This theoretically costs more memory, but if recycling is - on then the difference is negligible (in the s2 unit test - suite). If recycling is off, the memory cost is higher. However, - hashes are much faster at property lookups when a scope contains - many variables. Historically, objects have suited this purpose just - fine. - - Changing this flag at runtime only affects future property storage - creation, and does not affect scopes which have already allocated - their scope properties (which they do lazily, only when needed). - - When the library is compiled with the CWAL_OBASE_ISA_HASH - configuration option then scopes will use a hash either way, but - the actual concrete type of the storage will differ: it will be - cwal_object without this flag and cwal_hash with it. Whether that's - really a feature or a bug is as yet unknown, but the latter enables - a capability not available to the former: scope-level properties - which are either exposed to or hidden from the scope lookup - mechanism, depending on whether they're stored as cwal_hash entries - or not. -*/ -CWAL_FEATURE_SCOPE_STORAGE_HASH = 0x0400 -}; - -/** - Sets the current set of feature flags and returns the old flags. - Pass a negative flags value to have it return the current flags - without setting them. flags is interpreted as a bitmask of - cwal_engine_features_e values. This may be called during engine - initialization (via cwal_engine_vtab::hook::on_init()). - - If !e or tracing is disabled at built-time, returns -1. - - Example: - - @code - // Get current flags: - uint32_t const flags = cwal_engine_feature_flags(e,-1); - // Disable string-interning: - cwal_engine_feature_flags(e, flags & ~CWAL_FEATURE_INTERN_STRINGS ); - @endcode - - Calling this might have side effects other than setting - the flags. Namely: - - If CWAL_FEATURE_INTERN_STRINGS is disabled by this call (and - was enabled before it) then the memory used for tracking - interned strings is released. The strings are left intact and - unaffected, but future strings with those same values will be - created anew instead of sharing the interned values. Note that - interning may be enabled or disabled at any time without any - adverse effects vis-a-vis string ownership, reference counting, - etc. - - ACHTUNG: - - One fine debugging session in early 2016 it was discovered that - string interning (via the CWAL_FEATURE_INTERN_STRINGS flag) has a - property which makes it dangerous to use if client code uses string - values without acquiring explicit references to them (e.g. they are - "temp strings"). It can happen that 2 such strings share an - interned instance concurrently and one of them is passed to - cwal_refunref(). That will nuke the shared instance (because it has - no refcount) but leave one of the 2 client code locations holding a - stale pointer to it (pointing to memory which might live in the - recycling bin or might have been deallocated or reallocated for - another purpose). The only solution for this is to explicitly take - references everywhere, and release them when done. That's not - always practical (or fun), however, and the workaround in such - cases is to disable string interning. For completeness, here is an - example scenario: - - 1) a function uses cwal_new_string[_value]() to create a local temp - string and does not grab a reference to it (because it's often - (seemingly) not strictly necessary to). This function calls - another, which calls another, which... - - 2) some downstream call, allocates the same string, which string - interning, as it's supposed to, doesn't allocate, but returns - from the interning table (which does _not_ modify its refcount - because that would make interned strings live forever). - - 3) that downstream code, being modern and safe, adds a reference - when it gets the string and unrefs it when done. - - 4) Blamo! When the function from step (1) tries to unref (or - cwal_refunref()) the string, it will be invoking undefined - behaviour because the state of the stale pointer is - indeterminate. By that time, it might have been free()'d, it might - live in cwal's recycling bin, or it might have been reallocated for - a different purpose altogether. - - The morale of the story is: always explicitly add/remove references - and your code will be safe. Failing to do so can lead to - difficult-to-track disasters. Optionally, disable/do not use string - interning, and "most" reasonable uses of temp/local strings are - fine and kosher (provided one does not use cwal_engine_sweep() or - cwal_engine_vacuum(), which will clean up those temps. - - All that being said: as of 20160111, cwal_new_string() will not - re-use an interned string which has a refcount. Instead it will go - through the normal allocation process (which might pull from the - recycler). -*/ -uint32_t cwal_engine_feature_flags( cwal_engine * e, int32_t flags); - -/** - Returns the Value associated with s, or NULL if !s. - - @see cwal_new_VALUE() -*/ -cwal_value * cwal_string_value(cwal_string const * s); - -/** - Returns the length of str in bytes, or 0 if !str. This is an - O(1) operation. -*/ -cwal_midsize_t cwal_string_length_bytes( cwal_string const * str ); - -/** - Returns the length of the first n bytes of str in UTF8 - characters, or 0 if !str. Results are undefined if str is not - legal UTF8. This is an O(N) operation. - - Note that an embedded NUL byte before (str+n) is counted as a - byte! -*/ -cwal_midsize_t cwal_strlen_utf8( char const * str, cwal_midsize_t n ); - -/** - Functionally equivalent to strlen(3) except that if !str it - returns 0 instead of crashing, and it returns cwal_size_t, - which very well may not be the same size as size_t. -*/ -cwal_midsize_t cwal_strlen( char const * str ); - -/** - Equivalent to: - - cwal_strlen_utf8(cwal_string_cstr(str),cwal_string_length_bytes(str)) - - Unless str is known to be an ASCII string, in which case it is an - O(1) operation. - - Returns 0 if !str. -*/ -cwal_midsize_t cwal_string_length_utf8( cwal_string const * str ); - -/** - If str is composed solely of ASCII characters (in the range - (0,127), this returns true, else false. While normally of little - significance, some common algorithms can be sped up notably if - their input is guaranteed to have only 1 byte per character. - - This immutable flag is set when a string is created. - - Returns 0 if str is NULL or if it is the built-in empty string - value. Most algorithms avoid all work if the length is zero, so its - ASCII-ness in that case is irrelevant (and debatable). -*/ -bool cwal_string_is_ascii( cwal_string const * str ); - -/** - Returns the upper-cased form of the given utf8 character, - or ch if it doesn't know what to do. - - The mappings cover all the one-to-one mappings defined by - Unicode: - - https://www.unicode.org/faq/casemap_charprop.html - ftp://ftp.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.html - ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt - - None of the "special cases" are covered. - - @see cwal_utf8_char_tolower() -*/ -int cwal_utf8_char_toupper( int ch ); - -/** - Returns the lower-cased form of the given utf8 character, - or ch if it doesn't know what to do. - - @see cwal_utf8_char_toupper() -*/ -int cwal_utf8_char_tolower( int ch ); - -/** - Reads a single UTF-8 character from an input string and returns the - unicode value. - - zBegin is the start of the string. zTerm points to the logical - EOF (one-after-the-end). zBegin _must_ be less than zTerm. - - It writes a pointer to the next unread byte back into *pzNext. - When looping, that value should be the next position passed to - this function (see the example below). - - Notes On Invalid UTF-8: - - - This routine never allows a 7-bit character (0x00 through - 0x7f) to be encoded as a multi-byte character. Any multi-byte - character that attempts to encode a value between 0x00 and 0x7f - is rendered as 0xfffd. - - - This routine never allows a UTF16 surrogate value to be - encoded. If a multi-byte character attempts to encode a value - between 0xd800 and 0xe000 then it is rendered as 0xfffd. - - - Bytes in the range of 0x80 through 0xbf which occur as the first - byte of a character are interpreted as single-byte characters and - rendered as themselves even though they are technically invalid - characters. (That can be considered a bug in the context of cwal!) - - - This routine accepts an infinite number of different UTF8 - encodings for unicode values 0x80 and greater. It does not change - over-length encodings to 0xfffd as some systems recommend. (That - can be considered a bug in the context of cwal!) - - - An embedded NUL byte before zTerm is counted as a length-1 - character. - - Credits: the implementation and most of the docs were stolen from - the public domain sqlite3 source tree. - - Example usage: - - @code - char unsigned const * pos = inputString; - char unsigned const * end = pos + inputStringLength; - char unsigned const * next = 0; - unsigned int ch; - for( ; (pos < end) - && (ch=cwal_utf8_read_char(pos, end, &next)); - pos = next ){ - // do something with ch... - // but note that 0 is technically a legal value - // for ch for generic purposes (but not for most practical - // purposes). The byte-length of the character is (next-pos). - } - @endcode - - If zBegin>=zTerm, it returns 0 and *pzNext is set to zEnd. Note - that that result is indistinguishable from a NUL-terminated, - zero-length string (i.e. containing only 1 byte: a NUL), but it's - as good as we can get for such inputs. The moral is: check the - input range before starting and do whatever is right for your use - case. - - @see cwal_utf8_read_char1() -*/ -unsigned int cwal_utf8_read_char( const unsigned char *zBegin, - const unsigned char *zTerm, - const unsigned char **pzNext); - -/** - A variant of cwal_utf8_read_char() which assumes that the input - string is NUL-terminated UTF8. It returns the unicode value of the - next UTF8 character and assigns *zIn to the first byte following - that character. For more information about the return value, see - cwal_utf8_read_char(). - - @see cwal_utf8_read_char() -*/ -unsigned int cwal_utf8_read_char1(const unsigned char **zIn); - -/** - Given UTF8 character value c, this calculates its length, in bytes, - writes that many bytes to output, and returns that length. If the - calculated size is >length then -1 is returned (which cannot happen - if length>=4). If !output then only the UTF8 length of c is - calculated and returned (and the length argument is ignored). - - Returns -1 if c is not a valid UTF8 character. The most bytes it - will ever write to *output is four, so an output buffer of four - bytes is sufficient for all encoding cases. - - Note that it does not NUL-terminate the output unless the character - is incidentally a NUL byte. That is the only case in which it - NUL-terminates the output. - - FIXME: return 1 if !c: a NUL byte has a length of 1. Make sure s2 - callers accommodate this first. Doh, we can't do that if (output) - is optional, or the caller would not know when to stop! - - FIXME: remove the length param and require at least 4 bytes. - - FIXME?: return -1 for the various "invalid" characters: 0xfffd - (?undocumented return value of cwal_utf8_read_char()?). - - @see cwal_utf8_char_at() -*/ -int cwal_utf8_char_to_cstr(unsigned int c, unsigned char *output, cwal_size_t length); - -/** - Searches the UTF8-encoded string starting at b for the index'th - UTF8 character. The string in the half-open range [b,e) must be - well-formed UTF8. If found, 0 is returned and *unicode (if unicode - is not NULL) is assigned to the code point. If index is out of - range or invalid UTF8 is traversed, CWAL_RC_RANGE is returned and - unicode is not modified. - - @see cwal_utf8_char_to_cstr() -*/ -int cwal_utf8_char_at( unsigned char const * b, - unsigned char const * e, - cwal_size_t index, - unsigned int * unicode ); - -#if 0 -/* do not use - fundamantally broken interface. */ -/** - A helper to loop over each UTF-8 character of an input string. - - The first two arguments define the current character and the - one-after-the-end address (pos+strlen(pos)). It is initially - passed the start of the string, but each iteration should pass - (previousStart + return value of previous call). See below for - an example. - - This routine only examines the first byte of *pos, so its - results depend on pos pointing to the start of a UTF-8 - character boundary. - - On success: - - - If unicode is not 0 then *unicode will holds the UTF-8 encoded - value of the character pointed to by pos. This increases the cost - of the calculation notably. Note that this value differs from its - code point! - - - The _byte_ length of the consumed character is returned. It tries - to return 0 for invalid characters, but there are plenty of invalid - UTF-8 characters which it does not currently treat as invalid (for - fear of breaking existing code), though this behaviour may change - someday. - - - For the second and subsequent iterations, the first parameter - must be incremented by the return value of the previous call to - this function. - - If pos>=end or the character read from pos extends past the - end, 0 is returned and *unicode is not modified. e.g. if - pos==(end-1) and it detects a two-byte character then it - returns 0 rather than setting up the user to dereference - any bytes past the end. - - It treats embedded NUL bytes (before the end position) as - length-1 bytes. - - Example intended usage: - - @code - char const * str = "hi world, in UTF-8"; - char const * pos = str; - char const * const end = str + cwal_strlen(str); // BYTE length! - unsigned int unicode = 0; - int len = 0; - for( ; (len = cwal_utf8_char_next(pos, end, &unicode))>0; - pos += len){ - ... use bytes in the range [pos,pos+len) ... - ... optionally use the unicode value ... - assert(len>=1 && len<=4); // a NUL byte has a len of 1! - } - @endcode - - @see cwal_utf8_read_char() - @see cwal_utf8_read_char1() -*/ -int cwal_utf8_char_next( char const * pos, char const * end, - unsigned int * unicode); -#endif - -/** - Equivalent to cwal_value_unref( e, cwal_string_value(v) ), - where e is s's owning cwal_engine. -*/ -int cwal_string_unref(cwal_string * s); - -/** - If cwal_value_is_string(val) then this function assigns *str to the - contents of the string. str may be NULL, in which case this function - functions like cwal_value_is_string() but returns 0 on success. - - Returns 0 if val is-a string, else non-0, in which case *str is not - modified. - - The bytes are owned by the given value and may be invalidated in any of - the following ways: - - - The value is cleaned up or freed. - - - An array or object containing the value peforms a re-allocation - (it shrinks or grows). - - And thus the bytes should be consumed before any further operations - on val or any container which holds it. - - Note that this routine does not convert non-String values to their - string representations. (Adding that ability would add more - overhead to every cwal_value instance.) - - In practice this is never used by clients - see - cwal_value_get_string(). -*/ -int cwal_value_fetch_string( cwal_value const * val, cwal_string ** dest ); - -/** - Simplified form of cwal_value_fetch_string(). Returns NULL if val - is-not-a string value. -*/ -cwal_string * cwal_value_get_string( cwal_value const * val ); - -/** - Convenience function which returns the string bytes of the - given value if it is-a string or a buffer, otherwise it returns - NULL. Note that this does no conversion of other types to - strings, and returns NULL for them. - - The second argument, if not NULL, is set to the length of the - string or buffer, in bytes (not UTF8 characters). For buffers, this - value corresponds to their "used" property. If the 2nd argument is - NULL, it is ignored. - - Using this for buffer values "might" (depending on the contents of - the buffer and the intended use of the returned bytes) lead to - undefined behaviour if the returned string is expected to contain - valid string data (buffers can contain anything). - - The returned bytes are owned by the underlying value. In the case - of strings their address and contents remain constant for the life - of the string value. For buffers the contents and address may - change at any time, so it is illegal to use the returned bytes if - there is any chance that the buffer which owns/owned them has been - modified _in any way_ since calling this. - - As a special case, a completely empty buffer value, with no - buffered memory, will return 0 here and len (if not NULL) will be - set to 0. An empty string, on the other hand, will return "" - and set len (if not NULL) to 0. -*/ -char const * cwal_value_get_cstr( cwal_value const * val, cwal_size_t * len ); - - -/** - Allocates a new "array" value and transfers ownership of it to the - caller. It must eventually be destroyed, by the caller or its - owning container, by passing it to cwal_value_unref(). - - Returns NULL on allocation error. - - Post-conditions: cwal_value_is_array(value) will return true. - - @see cwal_new_object_value() - @see cwal_new_VALUE() -*/ -cwal_value * cwal_new_array_value( cwal_engine * e ); - -/** - Convenience form of cwal_new_array_value() which returns its - result as an array handle. - - Postconditions: cwal_array_value(result) is the value which would - have been returned had the client called cwal_new_array_value() - instead of this function. -*/ -cwal_array * cwal_new_array(cwal_engine *e); - -/** - Equivalent to cwal_value_unref( cwal_array_value(v) ). -*/ -int cwal_array_unref(cwal_array *a); - -/** - Identical to cwal_value_fetch_object(), but works on array values. - - In practice this is never used by clients - see - cwal_value_get_array(). - - @see cwal_value_get_array() - @see cwal_value_array_part() -*/ -int cwal_value_fetch_array( cwal_value const * val, cwal_array ** ar); - -/** - Simplified form of cwal_value_fetch_array(). Returns NULL if val - is-not-a array value. -*/ -cwal_array * cwal_value_get_array( cwal_value const * v ); - -/** - The inverse of cwal_value_get_array(). -*/ -cwal_value * cwal_array_value(cwal_array const * s); - -/** - Sets the given index of the given array to the given value - (which may be NULL). - - If ar already has an item at that index then it is cleaned up and - freed before inserting the new item. - - ar is expanded, if needed, to be able to hold at least (ndx+1) - items, and any new entries created by that expansion are empty - (NULL values). - - On success, 0 is returned and ownership of v is transfered to ar. - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For example, the following code will introduce - a leak if this function fails: - - Fails with CWAL_RC_LOCKED if the array is currently locked against - modification (e.g. while the list is being sorted). - - @code - cwal_array_append( myArray, cwal_new_integer(42) ); - @endcode - - Because the value created by cwal_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cwal_value * v = cwal_new_integer(42); - int rc = cwal_array_append( myArray, v ); - if( 0 != rc ) { - cwal_value_unref( v ); - ... handle error ... - } - @endcode - -*/ -int cwal_array_set( cwal_array * const ar, cwal_midsize_t ndx, cwal_value * const v ); - -/** - Ensures that ar has allocated space for at least the given - number of entries. This never shrinks the array and never - changes its logical size, but may pre-allocate space in the - array for storing new (as-yet-unassigned) values. - - Returns 0 on success, or non-zero on error: - - - If ar is NULL: CWAL_RC_MISUSE - - - If allocation fails: CWAL_RC_OOM -*/ -int cwal_array_reserve( cwal_array * ar, cwal_midsize_t size ); - -/** - Sets the length of the given array to n, allocating space if - needed (as for cwal_array_reserve()), and unreferencing - truncated objects. New entries will have NULL values. - - It does not free the underlying array storage but may free - objects removed from the array via shrinking. i.e. this is not - guaranteed to free all memory associated with ar's storage. - - Fails (as of 20191211) with CWAL_RC_IS_VISITING_LIST if called - while traversing over the elements using one of the various - list-traversal/sort APIs. i.e. the length may not be modified - while a sort or list iteration is underway. (It "could" be made to - work for iteration, but resizing while sorting would be - catastrophic.) - - Fails with CWAL_RC_LOCKED if the list is currently locked (e.g. - being sorted). -*/ -int cwal_array_length_set( cwal_array * ar, cwal_midsize_t n ); - -/** - Simplified form of cwal_array_length_fetch() which returns 0 if ar - is NULL. -*/ -cwal_midsize_t cwal_array_length_get( cwal_array const * ar ); - -/** - If ar is not NULL, sets *v (if v is not NULL) to the length of the array - and returns 0. Returns CWAL_RC_MISUSE if ar is NULL. -*/ -int cwal_array_length_fetch( cwal_array const * ar, cwal_midsize_t* v ); - -/** - Simplified form of cwal_array_value_fetch() which returns NULL if - ar is NULL, pos is out of bounds or if ar has no element at that - position. -*/ -cwal_value * cwal_array_get( cwal_array const * ar, cwal_midsize_t pos ); - -/** - "Takes" the given index entry out of the array and transfers - ownership to the caller. Its refcount IS decremented by this, - but using cwal_value_unhand() instead of cwal_value_unref(), - so it won't be destroyed as a result of this call but may - once again be a temporary. - - Achtung: if the returned value is reprobated by this function - (it only does that if it holds the only reference) then as long - as it has a refcount of 0, a call to cwal_scope_sweep() _will_ - destroy it out from under the client. So take a reference (or - pass it to a container) if needed. -*/ -cwal_value * cwal_array_take( cwal_array * ar, cwal_size_t pos ); - -/** - If ar is-a array and is at least (pos+1) entries long then *v - (if v is not NULL) is assigned to the value at that position - (which may be NULL). - - Ownership of the *v return value is unchanged by this - call. (The containing array may share ownership of the value - with other containers.) - - If pos is out of range, non-0 is returned and *v is not - modified. - - If v is NULL then this function returns 0 if pos is in bounds, - but does not otherwise return a value to the caller. - - In practice this is never used by clients - see - cwal_array_get(). -*/ -int cwal_array_value_fetch( cwal_array const * ar, cwal_size_t pos, cwal_value ** v ); - -/** - Searches for a value in an Array. - - If ar contains v or a value which compares equivalent to v - using cwal_value_compare(), it returns 0 and sets *index to the - index the value is found at if index is not NULL. Returns - CWAL_RC_NOT_FOUND if no entry is found, CWAL_RC_MISUSE if ar is - NULL. v may be NULL, in which case it searches for the first - index in the array with no value in it (it never calls - cwal_value_compare() in that case). - - If strictComparison is true (non-0), values are only compared if - they have the same type. e.g. an integer will never match a double - if this flag is true, but they may match if it is false. If v is - NULL, this flag has no effect, as only NULL will compare equivalent - to NULL. - - The cwal_value_compare() (if any) is done with v as the - left-hand argument and the array's entry on the right. -*/ -int cwal_array_index_of( cwal_array const * ar, cwal_value const * v, - cwal_size_t * index, char strictComparison ); - -/** - Appends the given value to the given array. On error, ownership - of v is not modified. Ownership of ar is never changed by this - function. v may be NULL. - - This is functionally equivalent to - cwal_array_set(ar,cwal_array_length_get(ar),v), but this - implementation has slightly different array-preallocation policy - (it grows more eagerly). - - Returns 0 on success, non-zero on error. Error cases include: - - - ar is NULL: CWAL_RC_MISUSE - - - Array cannot be expanded to hold enough elements: CWAL_RC_OOM. - - - Appending would cause a numeric overlow in the array's size: - CWAL_RC_RANGE. (However, you'll get an CWAL_RC_OOM long before - that happens!) - - - CWAL_RC_LOCKED if the array is currently locked against - modification (e.g. while the list is being sorted). - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. See cwal_array_set() for the details. -*/ -int cwal_array_append( cwal_array * const ar, cwal_value * const v ); - -/** - The opposite of cwal_array_append(), this prepends a value - (which may be NULL) to the start of the array. - - This is a relatively expensive operations, as existing entries - in the array all have to be moved to the right. - - Returns 0 on success, non-0 on error (see cwal_array_append()). -*/ -int cwal_array_prepend( cwal_array * const ar, cwal_value * const v ); - -/** - "Shifts" the first item from the given array and assigns - it to *rv (if rv is not NULL). - - Returns 0 on success, CWAL_RC_MISUSE if !ar, and CWAL_RC_RANGE - if ar is empty. On error, *rv is not modified. - - The array's reference to *rv is removed but if rv is not NULL, - then then *rv is not immediately destroyed if its refcount goes - to zero. Instead, it is re-probated in its owning scope. If rv - is NULL then the shifted value may be reaped immediately - (before this function returns). i.e. the effect is as if it has - been cwal_value_unhand()'d, as opposed to cwal_value_unref()'d. -*/ -int cwal_array_shift( cwal_array * ar, cwal_value **rv ); - -/** - Copies a number of elements from ar into another array. If - !*dest then this function creates a new array and, on success, - updates *dest to point to that array. On error *dest's - ownership is not modified. - - Copies (at most) 'count' elements starting at the given - offset. If 0==count then it copies until the end of the array. - - If count is too large for the array, count is trimmed to fit - within bounds. - - If ar is empty or offset/count are out of range, it still - creates a new array on success, to simplify/unify client-side - usage. - - Returns 0 on success, CWAL_RC_MISUSE if !ar, !dest, or - (ar==*dest). Returns CWAL_RC_OOM for any number of potential malloc - errors. Returns (as of 20191212) CWAL_RC_LOCKED if either ar or - a non-NULL *dest are currently locked. -*/ -int cwal_array_copy_range( cwal_array * ar, cwal_size_t offset, - cwal_size_t count, - cwal_array **dest ); - - -/** - Clears the contents of the array, optionally releasing the - underlying list as well (if freeList is true). If the list is - not released it is available for re-use on subsequent - insertions. If freeProps is true then key/value properties are - also removed from ar, else they are kept intact. -*/ -void cwal_array_clear( cwal_array * ar, char freeList, char freeProps ); - -/** - A callback type for "value visitors," intended for use - with cwal_array_visit() and friends. - - The 1st parameter is the value being visited (it MAY BE NULL for - containers which may hold NULL values, e.g. arrays or tuples). The - 2nd is the state parameter passed to the visiting function - (e.g. cwal_array_visit()). - - Implementations MUST NOT unref v unless they explicitly ref it - first. The container which calls this callback holds at least - one reference to each value passe here. - - 20191211: since when do we pass on NULLs in cwal_array_visit()? -*/ -typedef int (*cwal_value_visitor_f)( cwal_value * v, void * state ); - -/** - A callback type for "array visitors," for use with cwal_array_visit2(). - - The first 3 parameters specify the array, value, and index of that - value in the array. The value may be NULL. - - The state parameter is that passed to cwal_array_visit2() (or - equivalent). - - Implementations MUST NOT unref v unless the also ref it first, but - may indirectly do so by re-assigning that entry in a. - - Note that changing a list's size while visiting is not allowed, and - attemping to do so will trigger an error. -*/ -typedef int (*cwal_array_visitor_f)( cwal_array * a, cwal_value * v, cwal_size_t index, void * state ); - -/** - For each entry in the given array, f(theValue,state) is called. If - it returns non-0, looping stops and that value is returned to the - caller. - - When traversing containers (this applies to Objects as well), they - have a flag set which marks them as "being visited" for the - moment. Prior to 20191211, concurrent visits were never allowed, - but they now are, with some restrictions. Some APIs temporarily - lock lists against traversal, which triggers the result code - CWAL_RC_LOCKED from other APIs (such as this one). - - Returns 0 on success (note that having no entries is not an - error). - - Minor achtung: entries in the array which have no Value in them are - passed to the visitor as NULL. Script-side visitors may need to - check for that and pass on cwal_value_undefined() or - cwal_value_null() in its place (or skip them altogether). They are - passed to the visitor so that the number of visits matches the - indexes of the array, which simplifies some script code compared to - this routine skipping NULL entries. (BTW, it _did_ skip over NULLs - until 20160225, at which point the older, arguable behaviour was - noticed and changed. It was likely an artifact of keeping th1ish - happy.) -*/ -int cwal_array_visit( cwal_array * const a, cwal_value_visitor_f const f, void * const state ); - -/** - An alternative form of cwal_array_visit() which takes a - different kind of callback. See cwal_array_visit() and - cwal_array_visitor_f() for the semantics. -*/ -int cwal_array_visit2( cwal_array * const a, cwal_array_visitor_f const f, void * const state ); - -/** - Runs a qsort(2) on ar, using the given comparison function. The - values passed to the comp routine will be a pointer to either - (cwal_value const *) or NULL (empty array elements are NULL - unless the client populates them with something else). - - Returns 0 on success, and the error conditions are quite - limited: - - This function will fail with CWAL_RC_MISUSE if either argument - is NULL. - - Fails with CWAL_RC_IS_VISITING_LIST if called while traversing over the - elements using one of the various traversal APIs. (This was result - code CWAL_RC_IS_VISITING prior to 20191211.) - - Fails with CWAL_RC_LOCKED if the list is currently locked. - - @see cwal_compare_value_void() - @see cwal_compare_value_reverse_void() - @see cwal_array_reverse() -*/ -int cwal_array_sort( cwal_array * const ar, int(*comp)(void const *, void const *) ); - -/** - Reverses the order of all elements in the array. Returns 0 on success, - non-zero on error: - - - CWAL_RC_MISUSE if !ar. - - While this is technically legal during traversal of a list, the - results may cause undue confusion. - - @see cwal_array_sort() -*/ -int cwal_array_reverse( cwal_array * ar ); - -/** - A comparison function for use with cwal_array_sort() which - requires that lhs and rhs be either NULL or valid cwal_value - pointers. It simply casts the arguments and returns - the result of passing them to cwal_value_compare(). - - @see cwal_array_sort() -*/ -int cwal_compare_value_void( void const * lhs, void const * rhs ); - -/** - A comparison function for use with cwal_array_sort() which - requires that lhs and rhs be either NULL or valid cwal_value - pointers. It simply casts the arguments, calls - cwal_value_compare(), and returns that result, negated if it is - not 0. -*/ -int cwal_compare_value_reverse_void( void const * lhs, void const * rhs ); - -/** - A cwal_value comparison function intended for use with - cwal_array_sort_stateful(). Implementations must compare the given - lhs/rhs values and return an integer using memcmp() semantics. - - The lhs and rhs value are the left/right values to compare (either - or both _MAY_ be NULL). These arguments "should" be const but for - the intended uses of this callback it would be impossible for the - client to ensure that the constness is not (by necessity) cast - away. Results are undefined if any sort-relevant state of lhs or - rhs is modified during the sorting process. - - The state argument is provided by the caller of - cwal_array_sort_stateful(). - - Implementations must set *errCode to 0 on success and non-0 - (preferably a CWAL_RC_xxx value) on error. This can be used to - propagate non-exception errors back through the sorting process - (e.g. script-engine syntax errors or interrupt handling errors). - The cwal API guarantees that errCode will not be NULL if this - callback is called from within the cwal API. -*/ -typedef int (*cwal_value_stateful_compare_f)( cwal_value * lhs, cwal_value * rhs, - void * state, int * errCode ); - -/** - An array sort variant which allows the client to provide a - stateful comparison operation. The intended use for this is in - providing script-side callback functions for the sorting, where - cmp would be a native wrapper around a cwal_function (the state - param) and would call that function to perform the comparison. - - The exact semantics of the state parameter depend entirely on - the cmp implementation - this function simply passes the state - on to the comparison function. - - Returns 0 on success, or CWAL_RC_MISUSE if either !ar or !cmp. If - any sort-internal call to cmp() sets its final parameter (error - code pointer) to a non-0 value, sorting is aborted and that error - code is returned. This function returns 0 without side-effects if - the length of the given array (see cwal_array_length_get()) is 0 or - 1. - - Fails with CWAL_RC_IS_VISITING_LIST if called while traversing over - the elements using one of the various traversal APIs: it is illegal - to sort an array while it is being iterated over. Likewise, it is - illegal to iterate over an array from a comparison function while - sorting is underway (because the results would be highly - unpredictable). (This was code CWAL_RC_IS_VISITING prior to - 20191211.) - - Fails with CWAL_RC_LOCKED if the list is currently locked. - - If the sorting process does not return an error but the underlying - cwal engine has, after sorting is complete, an exception awaiting - propagation, CWAL_RC_EXCEPTION is returned. This is largely - historical behaviour, from before the time when - cwal_value_stateful_compare_f was capable of returning error codes. - It may, in some unusual cases, be necessary for the client to - ensure that no exception is propagating before calling this (by - calling cwal_exception_set() with a NULL exception). In practice, - sorting cannot be triggered during exception propagation, so this - is really a non-issue unless the client is doing some truly odd - error handling. -*/ -int cwal_array_sort_stateful( cwal_array * const ar, - cwal_value_stateful_compare_f cmp, - void * state ); - -/** - A wrapper around cwal_array_sort_stateful() which calls the - given comparison function to perform the sorting comparisons. - The function must accept two (cwal_value*) arguments, compare - them using whatever heuristic it prefers, and "return" (to - script-space) an integer value with memcmp() semantics. Note - that cwal_new_integer() does not allocate for the values (-1, - 0, 1), so implementations should use those specific values for - their returns. - - The self parameter specifies the "this" object for the function - call. It may be NULL, in which case cwal_function_value(cmp) - is used. - - Fails with CWAL_RC_IS_VISITING_LIST if called while traversing over - the elements using one of the various traversal APIs. (This was - result code CWAL_RC_IS_VISITING prior to 20191211.) - - Fails with CWAL_RC_LOCKED if the list is currently locked. -*/ -int cwal_array_sort_func( cwal_array * ar, cwal_value * self, cwal_function * cmp ); - - -/** - Identical to cwal_new_array_value() except that it creates - an Object. - - @see cwal_new_VALUE() -*/ -cwal_value * cwal_new_object_value( cwal_engine * e ); - -/** - Identical to cwal_new_object_value() except that it returns - the object handle which can converted back to its value - handle using cwal_value_get_object(). -*/ -cwal_object * cwal_new_object(cwal_engine *e); - -/** - Equivalent to cwal_value_unref( e, cwal_object_value(v) ). -*/ -int cwal_object_unref(cwal_object *v); - -/** - If cwal_value_is_object(val) then this function assigns *obj to the underlying - object value and returns 0, otherwise non-0 is returned and *obj is not modified. - - obj may be NULL, in which case this function works like cwal_value_is_object() - but with inverse return value semantics (0==success) (and it's a few - CPU cycles slower). - - The *obj pointer is owned by val, and will be invalidated when val - is cleaned up. - - Achtung: for best results, ALWAYS pass a pointer to NULL as the - second argument, e.g.: - - @code - cwal_object * obj = NULL; - int rc = cwal_value_fetch_object( val, &obj ); - - // Or, more simply: - obj = cwal_value_get_object( val ); - @endcode - - In practice this is never used by clients - see - cwal_value_get_object(). - - @see cwal_value_get_object() -*/ -int cwal_value_fetch_object( cwal_value const * val, cwal_object ** ar); - -/** - Simplified form of cwal_value_fetch_object(). Returns NULL if val - is-not-a object value. -*/ -cwal_object * cwal_value_get_object( cwal_value const * v ); - -/** - The Object form of cwal_string_value(). See that function - for full details. -*/ -cwal_value * cwal_object_value(cwal_object const * s); - -/** - Fetches a property from a child (or [great-]*grand-child) object. - - obj is the object to search. - - path is a delimited string, where the delimiter is the given - separator character. - - This function searches for the given path, starting at the given object - and traversing its properties as the path specifies. If a given part of the - path is not found, then this function fails with CWAL_RC_NOT_FOUND. - - If it finds the given path, it returns the value by assiging *tgt - to it. If tgt is NULL then this function has no side-effects but - will return 0 if the given path is found within the object, so it can be used - to test for existence without fetching it. - - Returns 0 if it finds an entry, CWAL_RC_NOT_FOUND if it finds - no item, and any other non-zero error code on a "real" error. Errors include: - - - obj or path are NULL: CWAL_RC_MISUSE. - - - separator is 0, or path is an empty string or contains only - separator characters: CWAL_RC_RANGE. - - - There is an upper limit on how long a single path component may - be (some "reasonable" internal size), and CWAL_RC_RANGE is - returned if that length is violated. - - - Limitations: - - - It has no way to fetch data from arrays this way. i could - imagine, e.g. a path of "subobj.subArray.0" for - subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too - lazy/tired to add this. - - Example usage: - - - Assume we have a JSON structure which abstractly looks like: - - @code - {"subobj":{"subsubobj":{"myValue":[1,2,3]}}} - @endcode - - Out goal is to get the value of myValue. We can do that with: - - @code - cwal_value * v = NULL; - int rc = cwal_prop_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' ); - @endcode - - Note that because keys in JSON may legally contain a '.', the - separator must be specified by the caller. e.g. the path - "subobj/subsubobj/myValue" with separator='/' is equivalent the - path "subobj.subsubobj.myValue" with separator='.'. The value of 0 - is not legal as a separator character because we cannot - distinguish that use from the real end-of-string without requiring - the caller to also pass in the length of the string. - - Multiple successive separators in the list are collapsed into a - single separator for parsing purposes. e.g. the path "a...b...c" - (separator='.') is equivalent to "a.b.c". - - TODO: change last parameter to an int to support non-ASCII - separators. s2's string.split() code is close to what we need here. - - @see cwal_prop_get_sub() - @see cwal_prop_get_sub2() -*/ -int cwal_prop_fetch_sub( cwal_value * obj, cwal_value ** tgt, char const * path, - char separator ); - -/** - Similar to cwal_prop_fetch_sub(), but derives the path separator - character from the first byte of the path argument. e.g. the - following arg equivalent: - - @code - cwal_prop_fetch_sub( obj, &tgt, "foo.bar.baz", '.' ); - cwal_prop_fetch_sub2( obj, &tgt, ".foo.bar.baz" ); - @endcode -*/ -int cwal_prop_fetch_sub2( cwal_value * obj, cwal_value ** tgt, char const * path ); - -/** - Convenience form of cwal_prop_fetch_sub() which returns NULL if the given - item is not found. -*/ -cwal_value * cwal_prop_get_sub( cwal_value * obj, char const * path, char sep ); - -/** - Convenience form of cwal_prop_fetch_sub2() which returns NULL if the given - item is not found. -*/ -cwal_value * cwal_prop_get_sub2( cwal_value * obj, char const * path ); - - -/** - Returns v's reference count, or 0 if !v. -*/ -cwal_refcount_t cwal_value_refcount( cwal_value const * v ); - -/** - Typedef for generic visitor functions for traversing Objects. The - first argument holds they key/value pair and the second holds any - state passed to cwal_props_visit_kvp() (and friends). - - If it returns non-0 the visit loop stops and that code is returned - to the caller. - - Implementations MUST NOT unref the key/value parts of kvp. They are - owned by (or shared with) the kvp object. - - TODO (20160205): consider making kvp non-const, as clients could - hypothetically safely use cwal_kvp_value_set() on them during - visitation. -*/ -typedef int (*cwal_kvp_visitor_f)( cwal_kvp const * kvp, void * state ); - -/** - Returns the key associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cwal_value * cwal_kvp_key( cwal_kvp const * kvp ); - -/** - Returns the value associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cwal_value * cwal_kvp_value( cwal_kvp const * kvp ); - -/** - Re-assigns the given kvp's value part. - - On success this adds a ref point to v and unrefs the old value, - which may destroy the old value immediately. - - Returns 0 on success, non-0: - - - CWAL_RC_MISUSE if either argument is NULL. Values may not - be unset by passing a NULL 2nd argument. - - Because keys are used in hashing and sorting, they may not be - modified, so there is no cwal_kvp_key_set() routine. - - Achtung: this does not honor any CWAL_VAR_F_CONST flag set on kvp, - but cwal_value_kvp_set2() does. - - ACHTUNG: because this routine is not guaranteed to be able to know - which scope is correct for v, clients should, on success, - cwal_value_rescope() v to the scope which owns the container which - kvp came from (which will move it up only if needed). Do not - rescope it if this fails or it might end up being stranded in a - higher-up scope. - - @see cwal_value_kvp_set2() -*/ -int cwal_kvp_value_set( cwal_kvp * const kvp, cwal_value * const v ); - -/** - Similar to cwal_kvp_value_set() but honors CWAL_VAR_F_xxx flags set - on kvp, such that it can return additional error codes: - - - CWAL_RC_CONST_VIOLATION if the CWAL_VAR_F_CONST flag is set. - As a special case (to keep th1ish working :/), if the const flag - is set but kvp's value == v (as in, the same C pointer, not cwal_value - equivalence), 0 is returned. - - Unfortuntately, as this routine does not know which container kvp - belongs to, it cannot enforce - CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES and - CWAL_CONTAINER_DISALLOW_PROP_SET. Likewise, it cannot ensure that v - is in the scope it needs to be in, so the caller should, for - safety's sake, rescope v to the underlying container's scope (see - cwal_value_rescope()). - - @see cwal_value_kvp_set() -*/ -int cwal_kvp_value_set2( cwal_kvp * const kvp, cwal_value * const v ); - -/** - Returns kvp's flags, or 0 if !kvp. Flags are typically - set via cwal_var_decl() and friends. - - @see cwal_var_flags -*/ -cwal_flags16_t cwal_kvp_flags( cwal_kvp const * kvp ); - -/** - Sets kvp's flags to the given flags and returns the old - flags. Only do this if you are absolutely certain of what - you are doing and the side-effects it might have. - - As a special case, if flags is CWAL_VAR_F_PRESERVE then - the old value is retained. - - @see cwal_var_flags -*/ -cwal_flags16_t cwal_kvp_flags_set( cwal_kvp * kvp, cwal_flags16_t flags ); - - -/** - Clears all properties (set via the cwal_prop_set() family of - functions) from the given container value. Returns 0 on - success, or: - - - CWAL_RC_MISUSE if either c is NULL or its associated - engine cannot be found (indicative of an internal error). - - - If c is not a type capable of holding properties, CWAL_RC_TYPE is - returned. - - - CWAL_RC_DISALLOW_PROP_SET if the CWAL_CONTAINER_DISALLOW_PROP_SET - flag is set on the value (via cwal_container_flags_set()). -*/ -int cwal_props_clear( cwal_value * c ); - -#if 0 -/* This one needs better definition... */ - -/** - If v is of a type which can contain mutable state - (e.g. properties or children of any sort, e.g. Array, Object, - etc.) then all of that mutable state is cleaned up. For other - types this is a no-op. - - This does not clear the prototype value, but clears everything - else. - - Note that this effectively destroys Buffers, Natives, and - Functions. - - Returns 0 on success. Errors include: - - - CWAL_RC_MISUSE: if c is NULL or its engine cannot be found. - - - CWAL_RC_TYPE: if c is not a type capable of holding - properties. - -*/ -int cwal_value_clear_mutable_state( cwal_value * c ); -#endif - -/** - For each property in container value c which does not have the - CWAL_VAR_F_HIDDEN flag, f(property,state) is called. If it returns - non-0 looping stops and that value is returned. - - Returns CWAL_RC_MISUSE if !o or !f, and 0 on success. - - Prior to 20200118, this routine returned CWAL_RC_CYCLES_DETECTED if - concurrent iteration would have been triggered, but that is, as of - 20191211, permitted by the API, so that condition was removed from - this function. -*/ -int cwal_props_visit_kvp( cwal_value * c, cwal_kvp_visitor_f f, void * state ); - -/** - Convenience wrapper around cwal_props_visit_kvp() which visits only the - keys. f() is passed the (cwal_value*) for each property key. -*/ -int cwal_props_visit_keys( cwal_value * c, cwal_value_visitor_f f, void * state ); - -/** - Convenience variant of cwal_props_visit_kvp() which visits only the - values. f() is passed the (cwal_value*) for each property value. -*/ -int cwal_props_visit_values( cwal_value * o, cwal_value_visitor_f f, void * state ); - -/** - If c is a property container type and its object-level properties - may safely be iterated over without danger of recursion then this - function returns true (non-0), else false (0). - - As of 20191211, objects and lists may iterate multiple times - concurrently so long as no "locking" operation which calls back - into client-side code is underway. As of this writing, there are no - such operations, so this function always returns true for any - values of types which are capable of holding properties. - - @see cwal_value_is_iterating_list() - @see cwal_value_may_iterate_list() -*/ -bool cwal_value_may_iterate( cwal_value const * const c ); - -/** - If c is a value type with a list (array, tuple, hashtable (kinda)) - and that list is currently being iterated over, or is otherwise - locked, this function returns true (non-0), else false (0). - - Certain list operations, e.g. resizing, sorting, or hash table - manipulation, are disallowed when a list is being iterated over. - (Note that a hashtable is a list, of sorts.) - - @see cwal_value_may_iterate() - @see cwal_value_may_iterate_list() - @see cwal_value_is_iterating_list() -*/ -bool cwal_value_is_iterating_props( cwal_value const * const c ); - -/** - If c is a value type with a list (array, tuple, hashtable (kinda)) - and that list is currently being iterated over, or is otherwise - locked, this function returns true (non-0), else false (0). - - Certain list operations, e.g. resizing, sorting, or hash table - manipulation, are disallowed when a list is being iterated over. - (Note that a hashtable is a list, of sorts.) - - @see cwal_value_may_iterate() - @see cwal_value_may_iterate_list() -*/ -bool cwal_value_is_iterating_list( cwal_value const * const c ); - -/** - If c is a value type with a list (array, tuple, hashtable (kinda)) - and that list is currently capable of being iterated over, this - function returns true (non-0), else false (0). - - Certain list operations, e.g. resizing, sorting, or hash table - manipulation, are disallowed when a list is being iterated over. - (Note that a hashtable is a list, of sorts.) A stateful sort - operation (cwal_array_sort_stateful()) locks a list from - being iterated while it is running. - - @see cwal_value_is_iterating_list() - @see cwal_value_may_iterate() -*/ -bool cwal_value_may_iterate_list( cwal_value const * const c ); - -/** - Copies all non-hidden properties from src to dest. - - Returns 0 on success. Returns CWAL_RC_MISUSE if either pointer is - NULL and CWAL_RC_TYPE if cwal_props_can() returns false for either - src or dest. - - It returns CWAL_RC_IS_VISITING if dest is already being traversed - (the data model does not support modifying properties during - iteration). - - All property flags, e.g. CWAL_VAR_F_CONST, *EXCEPT* for - CWAL_VAR_F_HIDDEN, are carried over from the source to the - destination. Properties marked as CWAL_VAR_F_HIDDEN are "hidden" - from iteration routines and therefore are not copied by this - operation. - - Note that this does not actually _copy_ the properties - only - references are copied. This function must, however, allocate - internals to store the new properties, and can fail with - CWAL_RC_OOM. -*/ -int cwal_props_copy( cwal_value * src, cwal_value * dest ); - -/** - Removes a property from container-type value c. - - If c contains the given key (which must be keyLen bytes long), it - is removed and 0 is returned. If it is not found, CWAL_RC_NOT_FOUND - is returned (which can normally be ignored by client code). - - Returns 0 if the given key is found and removed. - - CWAL_RC_MISUSE is returned if obj or key are NULL or key has - a length of 0. - - Fails with CWAL_RC_IS_VISITING if c is currently being iterated - over. - - This is functionally equivalent calling - cwal_prop_set(obj,key,keyLen,NULL). - -*/ -int cwal_prop_unset( cwal_value * c, char const * key, - cwal_midsize_t keyLen ); - -/** - Searches the given container value for a string-keyed property - matching the first keyLen bytes of the given key. If found, it is - returned. If no match is found, or any arguments are NULL, NULL is - returned. The returned object is owned by c, and may be invalidated - by ANY operations which change c's property list (i.e. add or - remove properties). - - This routine will only ever match property keys for which - cwal_value_get_cstr() returns non-NULL (i.e. property keys of type - cwal_string or cwal_buffer). It never compares the key to - non-string property keys (even though they might compare equivalent - if the search key was a "real" cwal_string). - - @see cwal_prop_get_kvp() - @see cwal_prop_get_v() - @see cwal_prop_get_kvp_v() -*/ -cwal_value * cwal_prop_get( cwal_value const * c, char const * key, - cwal_midsize_t keyLen ); - -/** - Similar to cwal_prop_get() but takes a cwal_value key and may - compare keys of different types for equivalence. e.g. the lookup - key (integer 1) will match a property with a key of (double 1) or - (string "1"). - - See cwal_prop_get_kvp_v() for more details, in particular about the - special handling of boolean-type lookup- and property keys. - - @see cwal_prop_get_kvp() - @see cwal_prop_get_v() - @see cwal_prop_get_kvp_v() -*/ -cwal_value * cwal_prop_get_v( cwal_value const * c, cwal_value const * key ); - -/** - UNTESTED! EXPERIMENTAL! DO NOT USE! - - An alternate form of cwal_prop_get() which returns any found value - via its final parameter. Returns 0 if a match is found, - CWAL_RC_NOT_FOUND if no match is found, and CWAL_RC_MISUSE if c or - key are NULL or c is not a properties-capable type. If rv is not - NULL, then if a match is found (and thus 0 is returned), *rv is set - to that value. If any value other than 0 is returned, *rv is not - modified. - - Achtung: if/when property interceptors are added to the cwal core, - this function will support them (whereas cwal_prop_get() and - friends will not). Thus a non-0 return value may hypothetically - come from a property interceptor function. -*/ -int cwal_prop_getX( cwal_value * c, bool processInterceptors, - char const * key, cwal_midsize_t keyLen, - cwal_value ** rv); - -/** - UNTESTED! EXPERIMENTAL! DO NOT USE! - - Like cwal_prop_getX() but takes a cwal_value key. -*/ -int cwal_prop_getX_v( cwal_value * c, bool processInterceptors, - cwal_value const * key, cwal_value ** rv ); - -/** - cwal_prop_get_kvp() is similar to cwal_prop_get(), but returns - its result in a more complex form (useful mostly in - interpreter-level code so that it can get at their flags to - check for constness and such). - - The container value c is searched as described for cwal_prop_get(), - but prototypes are only searched if searchProtos is true, in which - case they are searched recursively if need. If foundIn is not NULL - then if a match is found then *foundIn is set to the Value in which - the key is found (it will be c or a prototype of c). - - If a match is found, it is returned. - - ACHTUNG: the returned object is owned by c (or *foundIn) and may be - invalidated on any modification (or cleanup) of c (or *foundIn). - - Returns 0 if no match is found, !c, or !key. -*/ -cwal_kvp * cwal_prop_get_kvp( cwal_value * c, char const * key, - cwal_midsize_t keyLen, bool searchProtos, - cwal_value ** foundIn ); - - -/** - cwal_prop_get_kvp_v() works identically to cwal_prop_get_kvp(), but - takes its key in the form of a cwal_value. Except in the case of a - boolean lookup key or property key (see below), it performs a - type-loose comparison for equivalence, not strict equality. - e.g. (string "1") and (integer 1) will match. In other words, it - uses the equivalent of cwal_value_compare() for comparisons (and - the lookup key is always the logical left-hand-side of that - comparison). - - Returns 0 if no match is found, !c, or !key. - - Prior to 20190706 this routine incorrectly handled lookup/property - keys of type CWAL_TYPE_BOOL, in that its equivalence comparison - would, for a lookup key of cwal_value_true(), match the first - "truthy" property key. Likewise, a property key of - cwal_value_true() would match any truthy lookup key. Complementary - comparisons applied for cwal_value_false(). This went unnoticed - because boolean-type keys are apparently never used. As of - 20190706, when either the lookup key or a property key are of type - boolean, this routine performs a type-strict comparison, so will - never match anything but an exact match for those cases. This change - applies to all of the various property lookup routines, not - just this function. - - @see cwal_prop_get() - @see cwal_prop_get_v() - @see cwal_prop_get_kvp() - @see cwal_prop_take() - @see cwal_prop_take_v() -*/ -cwal_kvp * cwal_prop_get_kvp_v( cwal_value * c, cwal_value const * key, - bool searchProtos, - cwal_value ** foundIn ); - -/** - Similar to cwal_prop_get(), but removes the value from the parent - container's ownership. This removes the owning container's - reference point but does not destroy the value if its refcount - reaches 0. If no item is found then NULL is returned, else the - object (now owned by the caller or possibly shared with other - containers) is returned. - - This is functionally similar to adding a ref to the property value, - removing it from/unsetting it in the container (which removes the - container's ref), and then cwal_value_unhand()ing it. - - Returns NULL if either c or key are NULL, key has a length of 0, or - c is-not-a container. - - Note that this does not search through prototypes for a property - - it only takes properties from the given value. - - See cwal_prop_get() for important details about the lookup/property - key comparisons. - - If c is currently undergoing traversal, NULL is returned (a limitation - of the data model). - - FIXME: #1: add a keyLen parameter, for symmetry with the rest of - the API. - - @see cwal_prop_take_v() - @see cwal_prop_get() - @see cwal_prop_get_v() -*/ -cwal_value * cwal_prop_take( cwal_value * c, char const * key ); - -/** - Similar to cwal_prop_take() but also optionally takes over - ownership of the key as well. If takeKeyAsWell is not NULL then - ownership of the key is taken away, eactly as for the result value, - and assigned to *takeKeyAsWell, otherwise the key is discarded - (unreferenced). - - Note that they key passed to this function might be equivalent to - (cwal_value_compare()), but not be a pointer match for, the key - found in the the container, thus the passed-in key and - *takeKeyAsWell may be different pointers on success. To deal with - that, make sure to pass key to cwal_value_ref() before calling - this, and to one of cwal_value_unhand() or cwal_value_unref() - (depending on key's current lifetime requirements) after calling - this. - - If no entry is found, NULL is returned and *takeKeyAsWell is not - modified. - - @see cwal_prop_take() - @see cwal_prop_get() - @see cwal_prop_get_v() -*/ -cwal_value * cwal_prop_take_v( cwal_value * c, cwal_value * key, - cwal_value ** takeKeyAsWell ); - -/** - Functionally similar to cwal_array_set(), but uses a string key - as an index. Like arrays, if a value already exists for the given key, - it is destroyed by this function before inserting the new value. - - c must be a "container type" (capable of holding key/value - pairs). For the list of types capable of having properties, see - cwal_props_can(). - - If v is NULL then this call is equivalent to - cwal_prop_unset(c,key,keyLen). Note that (v==NULL) is treated - differently from v having the special null value - (cwal_value_null()). In the latter case, the key is set to the - special null value. - - The key may be encoded as ASCII or UTF8. Results are undefined - with other encodings, and the errors won't show up here, but may - show up later, e.g. during output. - - The flags argument may be a mask of cwal_var_flags values. When - in doubt about whether the property is already set and might - have other flags set, use CWAL_VAR_F_PRESERVE. If it makes no - difference for your use case, feel free to pass 0. - - Returns 0 on success, non-0 on error. It has the following error - cases: - - - CWAL_RC_MISUSE: e, c, or key are NULL or !*key. - - - CWAL_RC_TYPE: c is not of a type capable of holding - properties. - - - CWAL_RC_OOM: an out-of-memory error - - - CWAL_RC_IS_VISITING: The library does not support modifying a - container which is being visited/iterated over. - - - CWAL_RC_CONST_VIOLATION: cannot (re)set a const property. - - - CWAL_RC_DISALLOW_NEW_PROPERTIES: the container has been flaged - with the CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES flag and key refers - to a property which is not in the container. - - - CWAL_RC_DISALLOW_PROP_SET: the container has been flaged with the - CWAL_CONTAINER_DISALLOW_PROP_SET flag. - - - CWAL_RC_NOT_FOUND if !v and the entry is not found. This can - normally be ignored as a non-error, but is provided for - completeness. - - - CWAL_RC_DESTRUCTION_RUNNING if c is currently being finalized. - This "should" only ever be returned in conjunction with cwal_native - types if they have a finalizer and that finalizer manipulates the - cwal_native/cwal_value part it has held on to in parallel. - - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For the variants of this function - taking a (cwal_value*) key, ownership of the key is also not - changed on error. - - For example, the following code will introduce a "leak" (insofar as - cwal really leaks) if this function fails: - - @code - cwal_prop_set( myObj, "foo", 3, cwal_new_integer(e, 42) ); - @endcode - - Because the value created by cwal_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cwal_value * v = cwal_new_integer(e, 42); - cwal_value_ref(v); - int rc = cwal_prop_set_with_flags( myObj, "foo", 3, v, CWAL_VAR_F_HIDDEN ); - cwal_value_unref(v); - // if prop_set worked, v has a reference, else it is cleaned up - if( 0 != rc ) { - ... handle error. - } - @endcode - - However, because the value in that example is owned by the active - scope, it will be cleaned up when the scope exits if the user does - not unref it manually. i.e. it is still "safe" vis-a-vis not - leaking memory, to use the first (simpler) insertion - option. Likewise, as cwal_scope_sweep() can also clean up the - errant integer within the current scope (but often has other - side-effects, so be careful with that). - - See cwal_prop_get() for important details about the lookup/property - key comparisons. - - @see cwal_prop_set() - @see cwal_prop_set_v() - @see cwal_prop_set_with_flags_v() -*/ -int cwal_prop_set_with_flags( cwal_value * c, char const * key, - cwal_midsize_t keyLen, cwal_value * v, - uint16_t flags ); - -/** - Equivalent to calling cwal_prop_set_with_flags() using the same - parameters, and a flags value of CWAL_VAR_F_PRESERVE. -*/ -int cwal_prop_set( cwal_value * c, char const * key, - cwal_midsize_t keyLen, cwal_value * v ); - -/** - UNTESTED! EXPERIMENTAL! DO NOT USE! -*/ -int cwal_prop_setX_with_flags( cwal_value * c, - bool processInterceptors, - char const * key, - cwal_midsize_t keyLen, - cwal_value * v, - uint16_t flags ); - -/** - UNTESTED! EXPERIMENTAL! DO NOT USE! -*/ -int cwal_prop_setX_with_flags_v( cwal_value * c, bool processInterceptors, - cwal_value * key, - cwal_value * v, uint16_t flags ); - -/** - Returns non-0 (true) if c is of a type capable of containing - per-instance properties, else 0 (false). - - The following value types will return true from this function: - - CWAL_TYPE_ARRAY, CWAL_TYPE_OBJECT, CWAL_TYPE_FUNCTION, - CWAL_TYPE_EXCEPTION, CWAL_TYPE_NATIVE, CWAL_TYPE_HASH, - CWAL_TYPE_BUFFER (as of 20141217). - - All others return false. - - Note that properties referred to by this function are independent - of an array's indexed entries or a hashtable's entries. -*/ -bool cwal_props_can( cwal_value const * c ); - -/** - Returns true if cwal_props_can() returns true and if the value - actually has any properties (not including those in prototype - values). This is an O(1) operation. -*/ -bool cwal_props_has_any( cwal_value const * c ); - -/** - Returns true if c is a value type which is suitable for use as a - key for object properties or hashtable entries. As of 2021-07-09, - types whose equivalence comparison takes their mutable state into - account are disallowed as property keys. That includes - CWAL_TYPE_BUFFER and CWAL_TYPE_TUPLE. Prior to that, any data type - could be used as a property key, but that was a fundamentally - flawed design decision. -*/ -bool cwal_prop_key_can( cwal_value const * c ); - -/** - Returns the number of properties in c (not including prototypes), - or 0 if c is not a properties-capable type. This is an O(1) - operation of built with CWAL_OBASE_ISA_HASH, else it's an O(N) - operation. - - Note that this count includes properties flagged with - CWAL_VAR_F_HIDDEN. -*/ -cwal_midsize_t cwal_props_count( cwal_value const * c ); - -/** - Like cwal_prop_set_with_flags() but takes a cwal_value key. - - Note that this routine does a type-loose comparison for the lookup - key and property keys, with the exception of boolean-type keys - (which only compare type-strictly: see cwal_prop_get_kvp_v() for - details). That is, the same comparison as cwal_value_compare() is - performed. - - If cwal_prop_key_can() returns false for the given key, - CWAL_RC_TYPE is returned. Prior to 2021-07-09, any data type could - be used as a property key, but that was a fundamentally flawed - design decision. -*/ -int cwal_prop_set_with_flags_v( cwal_value * c, cwal_value * key, - cwal_value * v, - uint16_t flags ); - -/** - Equivalent to calling cwal_prop_set_with_flags_v() using the same - parameters, and a flags value of CWAL_VAR_F_PRESERVE. -*/ -int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ); - -/** - Like cwal_prop_unset() but takes a cwal_value key. - - Fails with CWAL_RC_IS_VISITING if c is currently being iterated - over. -*/ -int cwal_prop_unset_v( cwal_value * c, cwal_value * key ); - -/** - Returns true (non-zero) if the value v contains the given - property key. If searchPrototype is true then the search - continues up the prototype chain if the property is not found, - otherwise only v is checked. - - See cwal_prop_get_kvp_v() for details about the lookup/property key - comparisons. -*/ -bool cwal_prop_has( cwal_value const * v, char const * key, - cwal_midsize_t keyLen, - bool searchPrototype ); - -/** - Like cwal_prop_has() but takes a cwal_value key. - - See cwal_prop_get_kvp_v() for details about the lookup/property key - comparisons. -*/ -bool cwal_prop_has_v( cwal_value const * v, cwal_value const * key, - bool searchPrototype ); - -/** - Returns the virtual type of v, or CWAL_TYPE_UNDEF if !v. -*/ -cwal_type_id cwal_value_type_id( cwal_value const * v ); - -/** - If v is not NULL, returns the internal string name of v's - concrete type, else it returns NULL. If no type name proxy is - installed (see cwal_engine_type_name_proxy()) then the returned - bytes are guaranteed to be static, else they are gauranteed (by - the proxy implementation) to live at least as long as v. - - If it returns non-NULL and len is not NULL then *len will hold - the length of the returned string. - - LIMITATION: if v is a builtin value then it has no engine - associated with it, meaning we cannot get proxied name. We can - fix that by adding an engine parameter to this function. -*/ -char const * cwal_value_type_name2( cwal_value const * v, - cwal_size_t * len); - - -/** - Equivalent to cwal_value_type_name2(v, NULL). -*/ -char const * cwal_value_type_name( cwal_value const * v ); - -/** - For the given cwal_type_id value, if that value represents - a client-instantiable type, this function returns the same - as cwal_value_type_name() would for an instance of that type, - else returns NULL. -*/ -char const * cwal_type_id_name( cwal_type_id id ); - -/** - For the given type ID, returns its "base sizeof()," which - has a slightly different meaning for different types: - - CWAL_TYPE_BOOL, UNDEF, NULL: are never allocated and return 0. - - CWAL_TYPE_LISTMEM: are internal metrics-counting markers and have - no sizes, so return 0. - - CWAL_TYPE_STRING/XSTRING/ZSTREAM: returns the base size of string - values (which may differ for each string sub-type), not including - their string bytes or trailing NUL. - - CWAL_TYPE_INTEGER, DOUBLE, ARRAY, OBJECT, NATIVE, BUFFER, FUNCTION, - EXCEPTION, HASH: returns the allocated size of the Value part plus - its high-level type representation (e.g. cwal_object), not - including any dynamic data such as hash table/array memory or - properties. - - CWAL_TYPE_WEAKREF, KVP: returns sizeof(cwal_weak_ref) - resp. sizeof(cwal_kvp). - - CWAL_TYPE_SCOPE: sizeof(cwal_scope). Though the API technically - supports dynamically-allocated scopes, in practice their usage fits - perfectly with stack allocation. - - Returns 0 if passed an unknown value. -*/ -cwal_size_t cwal_type_id_sizeof( cwal_type_id id ); - -/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */ -bool cwal_value_is_undef( cwal_value const * v ); -/** Returns true if v contains a null value. */ -bool cwal_value_is_null( cwal_value const * v ); -/** Returns true if v contains a bool value. */ -bool cwal_value_is_bool( cwal_value const * v ); -/** Returns true if v contains an integer value. */ -bool cwal_value_is_integer( cwal_value const * v ); -/** Returns true if v contains a double value. */ -bool cwal_value_is_double( cwal_value const * v ); -/** Returns true if v contains a number (double, integer, bool) value. */ -bool cwal_value_is_number( cwal_value const * v ); -/** Returns true if v contains a cwal_string value. */ -bool cwal_value_is_string( cwal_value const * v ); -/** Returns true if v contains an cwal_array value. */ -bool cwal_value_is_array( cwal_value const * v ); -/** Returns true if v contains an cwal_object value. */ -bool cwal_value_is_object( cwal_value const * v ); -/** Returns true if v contains a cwal_native value. */ -bool cwal_value_is_native( cwal_value const * v ); -/** Returns true if v contains a cwal_buffer value. */ -bool cwal_value_is_buffer( cwal_value const * v ); -/** Returns true if v contains a cwal_exception value. */ -bool cwal_value_is_exception( cwal_value const * v ); -/** Returns true if v contains a cwal_hash value. */ -bool cwal_value_is_hash( cwal_value const * v ); -/** Returns true if v contains a "unique" value - (of type CWAL_TYPE_UNIQUE). */ -bool cwal_value_is_unique( cwal_value const * v ); -/** Returns true if v contains a cwal_tuple value. */ -bool cwal_value_is_tuple( cwal_value const * v ); - -/** - A special-purpose function which upscopes v into s if v is owned by - a lower (newer) scope. If v is owned by s, or a higher scope, then - this function has no side-effects. This is necessary when clients - create values which they need to survive the current scope. In such - cases they should pass the scope they want to (potentially) - reparent the value into. - - Note that this reparenting is only for lifetime management - purposes, and has nothing at all to do with "scope variables". It - does not affect the script-side visibility of v. - - Returns 0 on success, CWAL_RC_MISUSE if any argument is 0, and - is believed to be infalible (hah!) as long as the arguments are - legal and their underlying cwal_engine is in a legal state. - - This is a no-op (returning 0) if v's current owning scope is older - than s or if cwal_value_is_builtin(v). It may assert() that neither - argument is NULL. -*/ -int cwal_value_rescope( cwal_scope * s, cwal_value * v ); - -/** - Given a pointer, returns true (non-0) if m lives in the memory - region used for built-in/constant/shared (cwal_value*) instances, - else returns 0. Is tolerant of NULL (returns 0). This - determination is O(1) or 2x O(1) (only pointer range comparisons), - depending on whether we have to check both sets of builtins. - - If this returns true (non-0), m MUST NOT EVER be cwal_free()d - because it refers to stack memory! If m refers to a (cwal_value*) - instance, that value is a built-in and it MAY be (harmlessly) - passed to any public Value-lifetime-management routines (e.g. - cwal_value_ref(), cwal_value_unref(), and cwal_value_unhand()), all - of which are no-ops for built-in values. Note that this returns - true for any address in a range which covers more than just - cwal_value instances, but in practice it is used only to check - whether a cwal_value is a built-in. For example, if cwal is - compiled with length-1 ASCII strings as built-ins then: - - @code - cwal_value_is_builtin( - cwal_value_get_cstr( - cwal_new_string_value(e,"a",1), 0 - ) - ) - @endcode - - will return true, as those string bytes are in the built-in block - of memory. -*/ -bool cwal_value_is_builtin( void const * m ); - -/** - Creates a new value which refers to a client-provided "native" - object of an arbitrary type. N is the native object to bind. - dtor is the optional finalizer function to call when the new - value is finalized. typeID is an arbitrary pointer which is - used later to verify that a given cwal_native refers to a - native of a specific type. - - A stack-allocated native pointer is only legal if it can be - _guaranteed_ to out-live the wrapping value handle. - - This function returns NULL if (e, N, typeID) are NULL or on - allocation error.On success it returns a new value, initially - owned by the current scope. - - Clients can fetch the native value later using - cwal_native_get(). The typeID passed here can be passed to - cwal_native_get() to allow cwal to confirm that the caller is - getting the type of pointer he wants. In practice the typeID is - a pointer to an app/library-wide value of any type. Its - contents are irrelevant, only its _address_ is relevant. While - it might seem intutive to use a string as the type ID, - compilers may (or may not) combine identical string constants - into a single string instance, which may or may not foul up - such usage. If one needs/wants to use a string, set it in 1 - place, e.g. via a file-scope variable, and expose its address - to any client code which needs it (as opposed to them each - inlining the string, which might or might not work at runtime, - depending on whether the strings get compacted into a single - instance). - - When the returned value is finalized (at a time determined by - the cwal engine), if dtor is not NULL then dtor(e,N) is called - to free the value after any Object-level properties are - destructed. If N was allocated using cwal_malloc() or - cwal_realloc() and it has no special cleanup requirements then - cwal_free can be passed as the dtor value. Finalizer functions - "might currently be prohibited" from performing "certain - operations" with the cwal API during cleanup, but which ones - those are (or should be) are not yet known. - - Note that it is perfectly legal to use a static value for N, - assuming the finalizer function (if any) does not actually try - to free() it. In the case of a static, the value could be used - as its own typeID (but since the client has that pointer, and - it's static, there doesn't seem to be much use for having a - cwal_native for that case!). - - See cwal_new_VALUE() for more details about the return value. - - @see cwal_new_VALUE() - @see cwal_new_native() - @see cwal_value_get_native() -*/ -cwal_value * cwal_new_native_value( cwal_engine * e, void * N, - cwal_finalizer_f dtor, - void const * typeID ); - -/** - Equvalent to passing the return value of - cwal_new_native_value() to cwal_value_get_native(). -*/ -cwal_native * cwal_new_native( cwal_engine * e, void * n, - cwal_finalizer_f dtor, - void const * typeID ); - -/** - A special-purpose component for binding native state to - cwal_natives and cwal_functions, necessary when native bindings - hold Value pointers whose lifetimes are not managed via - Object-level properties of the native. - - This callback is called whenever the Value part of the - function/native is moved to an older scope (for lifetime management - purposes, not script visibility purposes) and is passed the - following arguments: - - s: the scope to potentially be rescoped to. - - v: the cwal_value part of the Native or Function. Use - cwal_value_get_native() or cwal_value_get_function(), as - appropriate, to get the higher-level part. DO NOT use - cwal_value_native_part() resp. cwal_value_function_part(), - as those may return parts of _other_ values higher up - in v's prototype chain! - - Rescopers MUST do the following: - - a) For each "unmanaged" Value, call cwal_value_rescope(s, - theValue). They MUST NOT pass a NULL value to cwal_value_rescope(), - or an assertion may be triggered. - - d) It must not rescope the Native/Function passed to it, as that - value is in the process of rescoping when this is called. - - Implementations must return 0 on success and any error is - tantamount to an assertion, leading to undefined results - in cwal from here on out. - - Implementations MUST NOT perform any work which might allocate - values. (Potential TODO: disable the allocator during this - operation, to enforce that requirements.) - - Note that it is often necessary to make such hidden/internal values - vacuum-proof by using cwal_value_make_vacuum_proof() on it (for - containers) or adding them to a hidden/internal container which is - itself vacuum-proofed. Alternately, such refs can be held as hidden - properties of the cwal_native, perhaps using unique keys (via - cwal_new_unique()) to keep client code from every being able to - address them. When stored in the cwal_native's properties, no extra - Value rescoping/ownership management is necessary on the client - side. - - @see cwal_native_set_rescoper() - @see cwal_function_set_rescoper() -*/ -typedef int (*cwal_value_rescoper_f)( cwal_scope * s, cwal_value * v ); - -/** - A special-case function which is necessary when client-side - natives manage Values which are not visible to the native's - Object parent (i.e. they are not tracked as properties). When - creating such natives, after calling cwal_new_native() or - cwal_new_native_value(), call this function and pass it your - rescoper implementation. - - This function returns 0 unless nv is NULL, in which case it - returns CWAL_RC_MISUSE. - - When the given rescoper is called, cwal_value_get_native() can be - called on its argument to get the nv pointer which was passed to - this function (it will never be NULL when the rescoper is called - from cwal). DO NOT use cwal_value_native_part()! - - @see cwal_value_rescoper_f() -*/ -int cwal_native_set_rescoper( cwal_native * nv, - cwal_value_rescoper_f rescoper ); - -/** - A special-case function which is necessary when client-side - function state Values which are not visible to the function's - Object parent (i.e. they are not tracked as properties). When - creating such natives, after calling cwal_new_function() (or - equivalent), call this function and pass it your rescoper - implementation. - - This function returns 0 unless f is NULL, in which case it - returns CWAL_RC_MISUSE. - - When the given rescoper is called, cwal_value_get_function() can be - called on its argument to get the nv pointer which was passed to - this function (it will never be NULL when the rescoper is called - from cwal). DO NOT use cwal_value_function_part()! - - @see cwal_value_rescoper_f() -*/ -int cwal_function_set_rescoper( cwal_function * f, - cwal_value_rescoper_f rescoper ); - - -/** - Returns the cwal_value form of n, or 0 if !n. -*/ -cwal_value * cwal_native_value( cwal_native const * n ); - -/** - If val is of type CWAL_TYPE_NATIVE then this function - assigns *n (if n is not NULL) to its cwal_native handle - and returns 0, else it returns CWAL_RC_TYPE and does not - modify *n. - - In practice this is never used by clients - see - cwal_value_get_native(). - - @see cwal_value_get_native() - @see cwal_value_native_part() -*/ -int cwal_value_fetch_native( cwal_value const * val, cwal_native ** n); - -/** - If v is of type CWAL_TYPE_NATIVE then this function returns its - native handle, else it returns 0. This is a simplified form of - cwal_value_fetch_native(). -*/ -cwal_native * cwal_value_get_native( cwal_value const * v ); - -/** - Fetches the a raw "native" value (void pointer) associated with - n. - - If (0==typeID) or n's type ID is the same as typeID then *dest (if dest is not NULL) - is assigned to n's raw native value and 0 is returned, else... - - CWAL_RC_TYPE: typeID does not match. - - CWAL_RC_MISUSE: n is NULL. - - Note that clients SHOULD pass a value for typeID to ensure that - they are getting back the type of value they expect. The API - recognizes, however, that the type ID might not be available or - might be irrelevant to a particular piece of code, and - therefore allows (but only grudgingly) typeID to be NULL to - signify that the client knows WTF he is doing and is getting a - non-type-checked pointer back (via *dest). -*/ -int cwal_native_fetch( cwal_native const * n, void const * typeID, void ** dest); - -/** - Convenience form of cwal_native_fetch() which returns NULL if - n's type ID does not match typeID. -*/ -void * cwal_native_get( cwal_native const * n, void const * typeID); - -/** - Clears the underlying native part of n, such that future calls - to cwal_native_get() will return NULL. If callFinalizer is true - (non-0) then the native's finalizer, if not NULL, is called, - otherwise we assume the caller knows more about the lifetime of - the value than we do and the finalizer is not called. As a - general rule, clients should pass a true value for the second - parameter. -*/ -void cwal_native_clear( cwal_native * n, char callFinalizer ); - - -/** - Creates a new "buffer" value. startingSize is the amount of - memory to reserve in the buffer by default (0 means not to - reserve any, of course). If reservation of the buffer fails - then this function returns NULL. - - See cwal_new_VALUE() for details on the ownership. - - See the cwal_buffer API for how to use buffers. -*/ -cwal_value * cwal_new_buffer_value(cwal_engine *e, cwal_size_t startingSize); - -/** - Equvalent to passing the return value of - cwal_new_buffer_value() to cwal_value_get_buffer(). -*/ -cwal_buffer * cwal_new_buffer(cwal_engine *e, cwal_size_t startingSize); - -/** - Equivalent to cwal_value_unref( e, cwal_buffer_value(v) ). -*/ -int cwal_buffer_unref(cwal_engine *e, cwal_buffer *v); - -/** - This convenience routine takes b's buffered memory and - transfers it to a new Z-string value (see - cwal_new_zstring()). - - b may either have been created using cwal_new_buffer() or be a - "non-value" buffer which the client happens to be using. - - See cwal_new_string() for size limitation notes. - - The new string will have a string byte length of b->used. - - If !b or !e, if b->used is too big, or on allocation error, NULL is - returned. If b has no memory, the empty string value is - returned. The returned value is owned by e and (unless it is the - empty string) will initially be owned by e's current scope. If NULL - is returned, b's memory is not modified, otherwise after calling - this b will be an empty buffer (but its lifetime is otherwise - unaffected). - - After returning, if b->mem is not NULL then b still owns its - managed buffer (and there was an error, so NULL will have been - returned). For most use cases, clients should unconditionally pass - b to cwal_buffer_clear() after calling this, as they presumably had - no interest in managing b's memory. On success, b->mem's ownership - will have been transfered to the returned string and b->mem will be - 0. - - For metrics-counting purposes, b->mem's memory is counted by whoever - allocated it first, and not by z-string metrics. -*/ -cwal_string * cwal_buffer_to_zstring(cwal_engine * e, cwal_buffer * b); - -/** - Equivalent to cwal_string_value(cwal_buffer_to_zstring(e,b)). - - @see cwal_buffer_to_zstring() -*/ -cwal_value * cwal_buffer_to_zstring_value(cwal_engine * e, cwal_buffer * b); - - -/** - Equivalent to cwal_value_fetch_object() and friends, but for - buffer values. - - In practice this is never used by clients - see - cwal_value_get_buffer(). -*/ -int cwal_value_fetch_buffer( cwal_value const * val, cwal_buffer ** ar); - -/** - If value is-a Buffer then this returns the cwal_buffer form of the - value, else it returns 0. -*/ -cwal_buffer * cwal_value_get_buffer( cwal_value const * v ); - -/** - Returns the cwal_value handle associated with the given buffer, - or NULL if !s. - - WARNING OH MY GOD SUCH AN IMPORTANT WARNING: NEVER EVER EVER - pass a cwal_buffer which was NOT created via - cwal_new_buffer_value() to this function!!! It WILL cause - invalid memory access if passed e.g. a cwal_buffer which was - allocated on the stack (or by ANY means other than the - functions listed above) and might (depending on the state of - the random memory we're reading) cause the client to get - invalid memory back (as opposed to NULL). -*/ -cwal_value * cwal_buffer_value(cwal_buffer const * s); - -/** - Creates a new "exception" value. - - See cwal_new_VALUE() for details on the ownership of the return - value. - - code is a client-interpreted error code. (Clients are free to - use the cwal_rc values.) msg is an optional (may be NULL) value - which stores some form of error message (of an arbitrary value - type). Exception values may hold key/value pairs, so they may - be "enriched" with client-specific information like a stack - trace or source line/column information. - - On success the returned Exception value will contain the - properties "code" and "message", reflecting the values passed - here. - - @see cwal_new_exception(). - @see cwal_new_exceptionf() - @see cwal_new_exceptionfv() -*/ -cwal_value * cwal_new_exception_value(cwal_engine *e, int code, cwal_value * msg ); - -/** - Equivalent to passing the return value of - cwal_new_exception_value() to cwal_value_get_exception(). -*/ -cwal_exception * cwal_new_exception(cwal_engine *e, int code, cwal_value * msg ); - -/** - A printf-like form of cwal_new_exception() which uses - cwal_new_stringf() to create a formatted message to pass to - cwal_new_exception(). Returns the new Exception value on - success, NULL on allocation error or if either e is NULL. A - format string of NULL or "" are treated equivalently as NULL. - - @see cwal_new_exceptionf() - @see cwal_new_exception() -*/ -cwal_exception * cwal_new_exceptionfv(cwal_engine * e, int code, char const * fmt, va_list args ); - -/** - Identical to cwal_new_exceptionv() but takes its arguments in ellipsis form. -*/ -cwal_exception * cwal_new_exceptionf(cwal_engine * e, int code, char const * fmt, ... ); - -/** - Returns true if v is-a Exception, else false. -*/ -bool cwal_value_is_exception(cwal_value const *v); - - -/** - Equivalent to cwal_value_unref( e, cwal_exception_value(v) ). -*/ -int cwal_exception_unref(cwal_engine *e, cwal_exception *v); - -/** - Equivalent to cwal_value_fetch_object() and friends, but for - error values. - - In practice this is never used by clients - see - cwal_value_get_exception(). -*/ -int cwal_value_fetch_exception( cwal_value const * val, cwal_exception ** ar); - -/** - If value is-a Exception then this returns the cwal_exception - form of the value, else it returns 0. -*/ -cwal_exception * cwal_value_get_exception( cwal_value const * v ); - -/** - Returns the cwal_value handle associated with the given error - value, or NULL if !r. -*/ -cwal_value * cwal_exception_value(cwal_exception const * r); - -/** - Returns r's current result code, or some unspecified non-0 - value if !r. -*/ -int cwal_exception_code_get( cwal_exception const * r ); - -/** - Sets r's result code. Returns 0 on success, CWAL_RC_MISUSE - if !r. -*/ -int cwal_exception_code_set( cwal_exception * r, int code ); - -/** - Returns the "message" part of the given error value, NULL if !r - or r has no message part. The returned value is owned by/shared - with r via reference counting, and it must not be unref'd by - the client unless he explicitly references himself. -*/ -cwal_value * cwal_exception_message_get( cwal_exception const * r ); - -/** - Sets the given msg value to be r's "message" component. Interpretation - of the message is up to the client. - - Returns 0 on success, CWAL_RC_MISUSE if either e or r are - NULL. msg may be NULL. - - This function adds a reference to msg and removes a reference - from its previous message (if any). -*/ -int cwal_exception_message_set( cwal_engine * e, cwal_exception * r, cwal_value * msg ); - - -/** - Creates a new value wrapping a function. - - e is the owning engine, callback is the native function to wrap - (it may not be NULL). state is optional state for the callback - and may (assuming client application conditions allow for it) - be NULL. - - The stateTypeID parameter is not directly used by the framework - but can be used when the callback is called (via - cwal_function_call() and friends) to determine whether the - state parameter passed into the function is of a type expected - by the client (which avoids mis-casting pointers when script - code criss-crosses methods between object instances and - classes). See cwal_args_state() for more details. - - See cwal_new_VALUE() for details regarding ownership and lifetime - of the returned value. - - Returns NULL if preconditions are not met (e and callback may - not be NULL) or on allocation error. - - When the callback is called via cwal_function_call() and - friends, state->data will be available via the - cwal_callback_args instance passed to the callback. - - When the returned value is destroyed, if stateDtor is not NULL - then stateDtor(state) is called at destruction time to clean up - the state value. -*/ -cwal_value * cwal_new_function_value( cwal_engine * e, - cwal_callback_f callback, - void * state, - cwal_finalizer_f stateDtor, - void const * stateTypeID ); -/** - Equvalent to passing the return value of - cwal_new_function_value() to cwal_value_get_function(). -*/ -cwal_function * cwal_new_function( cwal_engine * e, cwal_callback_f, - void * state, cwal_finalizer_f stateDtor, - void const * stateTypeID ); -/** - Returns true if v is-a Function, else false. -*/ -bool cwal_value_is_function(cwal_value const *v); -/** - If v is-a Function then this returns that Function handle, - else it returns 0. -*/ -cwal_function * cwal_value_get_function(cwal_value const *v); -/** - Returns the Value handle part of f, or 0 if !f. -*/ -cwal_value * cwal_function_value(cwal_function const *f); -/** - Equivalent to cwal_value_unref(cwal_function_value(f)). -*/ -int cwal_function_unref(cwal_function *f); - -/** - Calls the given function, passing it the given arguments and other - state via its single cwal_callback_args parameter. - - The given scope is used as the execution context for purposes of - ownership of new values. - - self may technically be 0, but f may require it to be of a specific - type. Its intention is to be interpreted as the "this" object/value - for the call (the semantics of which are client-dependent). - - argv must point at at least argc values. Both argv and argc may be - 0. This function takes a reference to each value in the list to - protect them from being swept up by cwal_engine_sweep() (or - similar) during the function call, but it does not make them - vacuum-proof. After the call completes, each reference is released - using cwal_value_unhand(), as opposed to cwal_value_unref(), so - that the values survive the return trip to the caller. - - Returns the result from f or CWAL_RC_MISUSE if any arguments are - invalid. Callback implementors should keep in mind that returning a - value other than 0 (CWAL_RC_OK) will "usually" (but not always) be - interpreted as an error condition. The exact details depend on the - client's use of cwal. - - If resultVal is not NULL then on success *resultVal holds the - value-level result from the function call (which may be 0, but - clients typically interpret that as cwal_value_undefined()). - Clients are recommended to explicitly initialize *resultVal to 0 - before calling this, to avoid potential confusion afterwards. On - error *resultVal is not modified. - - It is strictly illegal to pop the current scope from within (or - via) the f->callback(). Subscopes may of course be pushed (and must - be popped before returning to this function, or an assertion may be - triggered!). - - If s is not the interpreter's current scope, this function - artificially changes the current scope, which comes with a - _potential_ caveat: during the life of the f->callback() call, up - until the next scope is pushed (if that happens), s is the current - scope for all intents and purposes. But that's the point of this - routine. That said: no small amount of practice implies that - s should always be the engine's current scope. - - If callback hooks have been installed via cwal_callback_hook_set() - then they are triggered in this function as described in the - cwal_callback_hook documentation. The "pre" callback is only - triggered after it is certain that f will be called (i.e. after - basic argument validation). If the pre-callback returns non-0 then - neither f nor the post-callback are triggered. If the pre-callback - returns 0 then both f and the post-callback are guaranteed to be - called. - - f and self will be made sweep-proof (via a ref) during the life of - the call. Both will be made vacuum-proof as well, if they weren't - already. - - As a special case, if f is an interceptor function which is - concurrently being called, CWAL_RC_CYCLES_DETECTED is returned to - avoid endless loops in interceptor invocation. (That said: - interceptors are an incomplete feature - do not use them.) -*/ -int cwal_function_call_in_scope( cwal_scope * s, - cwal_function * f, - cwal_value * self, - cwal_value ** resultVal, - uint16_t argc, - cwal_value * const * argv ); - -/** - Identical to cwal_function_call_in_scope() except that it sets the - cwal_callback_args::propertyHolder value to the value passed as the - 3rd argument to this function (which may be 0). - - The propertyHolder member is intended to be used by container - member functions which may need to distinguish between their "this" - and the "owner" of the property (insofar as any container owns a - value it contains). Specifically, it was added to support certain - property interceptor usages. The vast, vast majority of functions - have no need for it. - - @see cwal_function_call() -*/ -int cwal_function_call_in_scope2( cwal_scope * s, - cwal_function * f, - cwal_value * propertyHolder, - cwal_value * self, - cwal_value ** resultVal, - uint16_t argc, - cwal_value * const * argv ); - -/** - Convenience form of cwal_function_call_in_scope() which pushes a - new scope onto the stack before calling that function. - - Note that the value-level result of the function call might be - owned by the pushed scope or a subscope, and therefore be cleaned - up when the function call returns. If resultValue is not NULL then - the result value of the call() is moved into the scope which was - active before the call, such that it is guaranteed to survive when - the scope created for the call() is closed. If resultValue is null, - scope ownership of the call()'s result is not modified, and it - "may" be cleaned up as soon as the scope expires. - - Returns the result of cwal_function_call_in_scope(), or some non-0 - CWAL_RC_xxx code if pushing a new scope fails (which can only - happen if the client has installed a scope-push hook into the - cwal_engine and that hook fails). -*/ -int cwal_function_call( cwal_function * f, - cwal_value * self, - cwal_value ** resultVal, - uint16_t argc, - cwal_value * const * argv ); - -/** - Identical to cwal_function_call_in_scope() except that it sets the - cwal_callback_args::propertyHolder value to the value passed as the - 2nd parameter to this function (which may be 0). - - @see cwal_function_call_in_scope2() -*/ -int cwal_function_call2( cwal_function * f, - cwal_value * propertyHolder, - cwal_value * self, - cwal_value ** resultVal, - uint16_t argc, - cwal_value * const * argv ); - -/** - A form of cwal_function_call() which takes its arguments in - the form of a cwal_array (which may be NULL). - - If s is NULL then this acts as a proxy for cwal_function_call(), - otherwise it behaves like cwal_function_call_in_scope(), using - s as the call scope. - - Returns 0 on success, non-0 on error. - - Results are undefined if args is NULL. - - This routine makes the args array safe from sweep-up and - vacuuming (if it was not already so) for the duration of the - call. (The f parameter will be made so via the proxied - function.) - - ACHTUNG: any empty entries in the array will be passed to the - callback as literal NULLs, and experience has shown that most - callbacks do not generally expect any literal NULLs (because - script code cannot generate them). So... be careful with that. -*/ -int cwal_function_call_array( cwal_scope * s, cwal_function * f, - cwal_value * self, cwal_value ** rv, - cwal_array * args); - - -/** - Works like cwal_function_call() but has very specific requirements - on the variadic arguments: the list must contain 0 or more - (cwal_value*) arguments and MUST ALWAYS be terminated by a 0/NULL - value (NOT a cwal_value, e.g. cwal_value_null(), but a literal 0). - - This function places some "reasonable upper limit" on the number of - arguments to avoid having to allocate non-stack space for them (the - limit is defined by CWAL_OPT_MAX_FUNC_CALL_ARGS). It returns - CWAL_RC_RANGE if that limit is exceeded. -*/ -int cwal_function_callv( cwal_function * f, - cwal_value * self, - cwal_value ** resultVal, - va_list args ); - -/** - Equivalent to cwal_function_callv() but takes its arguments - in ellipsis form. BE SURE to read the docs for that function - regarding the arguments! -*/ -int cwal_function_callf( cwal_function * f, - cwal_value * self, - cwal_value ** resultValue, - ... ); - -/** - The cwal_function_call_in_scope() counterpart of - cwal_function_callv(). See those functions for more details. -*/ -int cwal_function_call_in_scopef( cwal_scope * s, - cwal_function * f, - cwal_value * self, - cwal_value ** resultValue, - ... ); - -/** - If args is NULL, returns NULL, else it returns the same as passing - (args->callee, stateTypeID) to cwal_function_state_get(). - - @see cwal_function_state_get() -*/ -void * cwal_args_state( cwal_callback_args const * args, - void const * stateTypeID ); - -/** - If f is not NULL and either stateTypeID is NULL or f was created - with the same stateTypeID as provided in the 2nd argument, then f's - native state is returned, else NULL is returned. - - Passing a NULL as the 2nd parameter is not recommended, as cwal cannot - verify the type of the returned pointer. Clients passing NULL are - assumed to know what they're doing. - - Example usage: - - @code - // From within a cwal_callback_f implementation... - MyType * my = (MyType *)cwal_function_state_get(args->callee, MyTypeID); - if(!my) { ...args->callee was not created with MyTypeID... } - @endcode - - @see cwal_args_state() -*/ -void * cwal_function_state_get( cwal_function * f, - void const * stateTypeID ); - -/** - Returns a static array of 1001 integers containing the first - 1000 prime numbers (up to and including 7919) followed by a - trailing 0 (so clients can use that as an iteration controller - instead of the length). - - Intended to be use for initializing hash tables. -*/ -int const * cwal_first_1000_primes(void); - -/** - Creates a new hash table object. These tables can store - arbitrary cwal_value keys and values and have amortized O(1) - search, insertion, and removal performance. - - hashSize is the number of elements in the hash, ideally be a - prime number. It can be changed after creation using - cwal_hash_resize(), but doing so will require another - allocation (tisk tisk) to resize the - table. cwal_first_1000_primes() can be used to get prime - numbers if you have not got any handy. - - Returns the new hash table on success, NULL on error. It is - an error if hashSize is 0. -*/ -cwal_hash * cwal_new_hash( cwal_engine * e, cwal_size_t hashSize ); - -/** - Equivalent to: - - cwal_hash_value(cwal_new_hash(e,hashSize)) -*/ -cwal_value * cwal_new_hash_value( cwal_engine * e, cwal_size_t hashSize); - -/** - Searches the given hashtable for a key, returning it if found, - NULL if not found. - - - @see cwal_hash_search() -*/ -cwal_value * cwal_hash_search_v( cwal_hash * h, cwal_value const * key ); - -/** - Equivalent to cwal_hash_search_v() but returns (on a match) a - cwal_kvp holding the key/value pair. The returned object is owned - by h and might be invalidated or modified on any change to h, so - clients must not hold returned kvp wrapper for long. -*/ -cwal_kvp * cwal_hash_search_kvp_v( cwal_hash * h, cwal_value const * key ); - -/** - Like cwal_hash_search_v() but takes its key in the form of the - first keyLen bytes of the given key. It will only ever match true - string keys, not non-string keys which might otherwise compare (via - cwal_value_compare()) to equivalent. - - Returns NULL if !h or !key. If keyLen is 0 and *key is not then - the equivalent of strlen(key) is used to find its length. - - @see cwal_hash_search_v() - @see cwal_hash_search_kvp() -*/ -cwal_value * cwal_hash_search( cwal_hash * h, char const * key, - cwal_midsize_t keyLen ); - - -/** - Equivalent to cwal_hash_search() but returns (on a match) a - cwal_kvp holding the key/value pair, using the first keyLen bytes - of key as the search key. It only matches String-typed keys. The - returned object is owned by h and might be invalidated or modified - on any change to h, so clients must not hold returned kvp wrapper - for long. NEVER, EVER change the internals of the returned cwal_kvp - value, e.g. changing the key or value instances, as that Will Break - Things. - - @see cwal_hash_search() -*/ -cwal_kvp * cwal_hash_search_kvp( cwal_hash * h, char const * key, - cwal_midsize_t keyLen ); - -/** - Returns true (non-0) if v is of the concrete type cwal_hash. -*/ -bool cwal_value_is_hash( cwal_value const * v ); - -/** - If cwal_value_is_hash(v) then this returns the value's - cwal_hash representation, else it returns NULL. -*/ -cwal_hash * cwal_value_get_hash( cwal_value * v ); - -/** - Returns the cwal_value part of a cwal_hash value, - or NULL if !h. -*/ -cwal_value * cwal_hash_value( cwal_hash * h ); - -/** - Removes all entries from the hashtable. If freeProps is true then - non-hash properties (those belonging to the object base type) are - also cleared. After calling this, cwal_hash_entry_count() will be 0 - until new entries are added. The hashtable size is not modified by - this routine. -*/ -void cwal_hash_clear( cwal_hash * ar, bool freeProps ); - - -/** - Inserts a value into the given hash. - - Returns 0 on success. If the given key already exists then - insertion fails and CWAL_RC_ALREADY_EXISTS is returned unless - allowOverwrite is true (in which case the entry is replaced). If - allowOverwrite is true but an existing entry is marked with the - CWAL_VAR_F_CONST flag, CWAL_RC_CONST_VIOLATION is returned. - - On error the key and value acquire no new references. - - This function returns CWAL_RC_IS_VISITING_LIST if called while h's - hash properties are being iterated over (e.g. via - cwal_hash_visit_kvp() and friends), as the data structures do not - support modification during iteration. (Prior to 20191211, - CWAL_RC_IS_VISITING was returned for this case.) - - The kvpFlags parameter is treated as described for - cwal_prop_set_with_flags(). When in doubt, pass - CWAL_VAR_F_PRESERVE. - - ACHTUNG: when overwriting an existing equivalent key, the older key - is replaced by the newer one. This is to avoid uncomfortable - questions about the lifetime management of newly-created keys (the - management of the older/longer-lived ones is simpler!). This means - that when replacing an entry, the original copy may (depending on - references) get freed immediately. The moral of this story is: - don't hold (cwal_value*) to such keys without holding a reference - to them (and releasing it when done), or they could be invalidated - (or their memory reused) in the mean time. - - Other error codes: - - - CWAL_RC_TYPE if cwal_prop_key_can() returns false for the given - key. - - - CWAL_RC_MISUSE if any arguments are invalid. - - - CWAL_RC_DISALLOW_NEW_PROPERTIES: the container has been flaged - with the CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES flag and key refers - to a property which is not in the container. - - - CWAL_RC_DISALLOW_PROP_SET: the container has been flaged with the - CWAL_CONTAINER_DISALLOW_PROP_SET flag. TODO: this is arguable, as - that flag was initially intended to apply only to object-level - properties. We might need another flag/code for this, but cannot do - so without making the same change in s2 (which uses a mix of - objects and hashes to implement enums with this flag). - - - CWAL_RC_DESTRUCTION_RUNNING: the container is currently being - destructed. It "should" be impossible for clients to ever trigger - this. - - -*/ -int cwal_hash_insert_with_flags_v( cwal_hash * h, cwal_value * key, cwal_value * v, - bool allowOverwrite, cwal_flags16_t kvpFlags ); - -/** - Equivalent to calling cwal_hash_insert_with_flags_v() with the same arguments, - passing CWAL_VAR_F_PRESERVE as the final argument. -*/ -int cwal_hash_insert_v( cwal_hash * h, cwal_value * key, cwal_value * v, - bool allowOverwrite ); - -/** - Like cwal_hash_insert_with_flags_v() but takes its key in the form - of the first keyLen bytes of the given key. - - This routine allocates a new String value for the key (just in - case there was any doubt about that). -*/ -int cwal_hash_insert_with_flags( cwal_hash * h, char const * key, - cwal_midsize_t keyLen, - cwal_value * v, bool allowOverwrite, - cwal_flags16_t kvpFlags ); - -/** - Equivalent to calling cwal_hash_insert_with_flags() with the same arguments, - passing CWAL_VAR_F_PRESERVE as the final argument. -*/ -int cwal_hash_insert( cwal_hash * h, char const * key, cwal_midsize_t keyLen, - cwal_value * v, bool allowOverwrite ); -/** - Removes the given key from the given hashtable, potentially - freeing the value (and possibly even the passed-in key, - depening on ownership conditions). - - Returns 0 on success, CWAL_RC_MISUSE if either argument is - NULL, or CWAL_RC_NOT_FOUND if the entry is not found. - - This function returns CWAL_RC_IS_VISITING_LIST if called while h's - hashtable part is being iterated over (e.g. via - cwal_hash_visit_kvp() and friends), as modifying the hash during - iteration could potentially lead the memory-related problems. (This - case was reported as CWAL_RC_IS_VISITING prior to 20191211.) - - Returns CWAL_RC_DISALLOW_PROP_SET if the container has been flagged - with the CWAL_CONTAINER_DISALLOW_PROP_SET flag. - - Returns CWAL_RC_DESTRUCTION_RUNNING: the container is currently - being destructed. It "should" be impossible for clients to ever - trigger this. -*/ -int cwal_hash_remove_v( cwal_hash * h, cwal_value * key ); - -/** - Like cwal_hash_remove_v(), with the same result codes, but takes - its key in the form of the first keyLen bytes of the given key. It - can only match String-type keys, not non-String-type keys which - might happen to have similar representations. e.g. passing "1" as a - key will not match an Integer-typed key with the numeric value 1. -*/ -int cwal_hash_remove( cwal_hash * h, char const * key, cwal_midsize_t keyLen ); - -/** - Returns the number of entries in the given hash, - or 0 if !h. This is an O(1) operation. -*/ -cwal_midsize_t cwal_hash_entry_count(cwal_hash const * h); - -/** - Returns the table size of h, or 0 if !h. -*/ -cwal_midsize_t cwal_hash_size( cwal_hash const * h ); - -/** - Resizes the table used by h for key/value storage to the new - size (which most definitely should be a prime number!). On - success it returns 0 and cwal_hash_size() will return the size - passed here. This is a no-op (returning 0) if newSize == - cwal_hash_size(h). - - On error it returns... - - - CWAL_RC_MISUSE if !h. - - - CWAL_RC_RANGE if !newSize. - - - CWAL_RC_IS_VISITING_LIST if h's hashtable part is currently being - "visited" (iterated over), as resizing is not legal while that is - happening. (This case was reported as CWAL_RC_IS_VISITING prior to - 20191211.) - - - CWAL_RC_OOM on an allocation error. If this happens, h is - left in its previous state (it is not modified). If allocation - of the new table succeeds, no other ops performed by this call - which modify h can fail (exception in debug builds: - lifetime-related assertions are in place which could be - triggered if any key/value lifetimes appear to have been - corrupted before this call). -*/ -int cwal_hash_resize( cwal_hash * h, cwal_size_t newSize ); - - -/** - If h's entry count is at least the 2nd value (load*100) percent of - its table size, the table is resized to be (roughly) within that - load size. load must be a value greater than 0, and ideally less an - 1. e.g. 0.80 means an 80% load factor. Unusually large or small - values may be trimmed to within some min/max range. A value of 0 or - less will use the library's built-in default. - - Returns: - - - 0 on success (which may mean it did nothing at all). - - - CWAL_RC_OOM if allocation of a larger hash table size fails. - - - CWAL_RC_IS_VISITING_LIST if called while h's hashtable part is - currently being iterated over or is otherwise locked against - concurrent modification. - - Results are undefined if h is NULL or otherwise not a valid - pointer. -*/ -int cwal_hash_grow_if_loaded( cwal_hash * h, double load ); - -/** - Returns the "next higher" prime number starting at (n+1), where - "next" is really the next one in a short list of rather arbitrary - prime numbers. If n is larger than some value which we internally - (and almost arbitrarily) define in this code, a value smaller than - n will be returned. That maximum value is guaranteed to be at least - as large as the 1000th prime number (7919 resp. - cwal_first_1000_primes()[999]). - - This function makes no performance guarantees. -*/ -cwal_midsize_t cwal_next_prime( cwal_midsize_t n ); - -/** - _Moves_ properties from the containter-type value src to the hash - table dest. overwritePolicy determines how to handle keys which - dest already contains: - - overwritePolicy<0: keep existing entries and leave the colliding - key from src in place. - - overwritePolicy==0: trigger CWAL_RC_ALREADY_EXISTS error on key - collision. - - overwritePolicy>0: overwrite any keys already existing in the hash - and removes the property from src. - - If a given property, due to the overwrite policy, is not taken then - it is kept in src, so src need not be empty when this returns unless - overwritePolicy>0. - - Property-level key/value pair flags, e.g. constness, are not - retained. - - Sidebar 1: ideally, this routine moves properties in such a way - that does not require new allocations, but that's currently only - the case when the build-time CWAL_OBASE_ISA_HASH option is - disabled. - - Sidebar 2: it is legal for src and dest to be the same cwal_value, - in which case its object-level properties are moved into its - hashtable. - - Returns 0 on success or: - - - CWAL_RC_OOM on allocation error. - - - CWAL_RC_ALREADY_EXISTS if overwritePolicy is 0 and a collision is - found. - - - CWAL_RC_MISUSE if !src or !dest - - - CWAL_RC_TYPE if src is not a container type. - - - CWAL_RC_IS_VISITING if src is currently being visited (the model - does not support modification during visitation). - - - CWAL_RC_IS_VISITING_LIST if dest's hash properties (as distinct - from its base object-level properties) are currently being visited - (the model does not support modification during visitation). (This - case was reported as CWAL_RC_IS_VISITING prior to 20191211.) - - On error, src's property list may well have been modified so may be - in an undefined state, with a subset of the properties moved and a - subset not. -*/ -int cwal_hash_take_props( cwal_hash * const dest, cwal_value * const src, int overwritePolicy ); - - -/** - Similar to cwal_props_visit_kvp() except that it operates on - the hash table entries of h. See cwal_props_visit_kvp() for the - semantics of the visitor and its return value. -*/ -int cwal_hash_visit_kvp( cwal_hash * h, cwal_kvp_visitor_f f, void * state ); - -/** - Equivalent to cwal_props_visit_keys() except that it operates - on the hash table entries of h, passing each key in the hashtable - to f (in an indeterminate order). -*/ -int cwal_hash_visit_keys( cwal_hash * h, cwal_value_visitor_f f, void * state ); - -/** - Equivalent to cwal_props_visit_keys() except that it operates - on the hash table entries of h, passing each value in the table - to f (in an indeterminate order). -*/ -int cwal_hash_visit_values( cwal_hash * h, cwal_value_visitor_f f, void * state ); - -/** - Hashes n bytes of m and returns its hash. It uses an unspecified - hash algorithm which is not guaranteed to be stable across - platforms or compilations of this code. It is guaranteed to be - deterministic within a single compilation of this code. m must not - be NULL. -*/ -cwal_hash_t cwal_hash_bytes( void const * m, cwal_size_t n ); - -/** - Converts v to a string representation and copies it to dest. dest - must be valid memory at least *nDest bytes long. On success (*nDest - is long enough to hold the number and trailing NUL) then *nDest is - set to the size of the string (minus the trailing NUL) and dest is - updated with its contents. - - Returns CWAL_RC_OK on success, else: - - CWAL_RC_MISUSE: dest or nDest are NULL. - - CWAL_RC_RANGE: *nDest is not enough to hold the resulting string - (including terminating NUL). dest is not modified in this case, but - *nDest is updated to contain the size which would be needed to - write the full value. - - For normal use cases, a memory length of 30 or less is more - than sufficient. -*/ -int cwal_int_to_cstr( cwal_int_t v, char * dest, cwal_size_t * nDest ); - -/** - Functionally identical to cwal_int_to_cstr() but works on a - double value. - - For normal use cases, a memory length of 128 or less is more - than sufficient. The largest result i've ever witnessed was - about 80 bytes long. - - Prior to 20181127, this routine would output "scientific notation" - for large/precise-enough numbers, but that was an oversight. It now - always writes in normal decimal form. If it ever tries to write - -*/ -int cwal_double_to_cstr( cwal_double_t v, char * dest, cwal_size_t * nDest ); - -/** - Tries to interpret slen bytes of cstr as an integer value, - optionally prefixed by a '+' or '-' character. On success 0 is - returned and *dest (if dest is not NULL) will contain the - parsed value. On error one of the following is returned: - - - CWAL_RC_MISUSE if !slen, !cstr, or !*cstr. - - - CWAL_RC_TYPE if cstr contains any non-numeric characters. - - - CWAL_RC_RANGE if the numeric string is too large for - cwal_int_t. - - Potential TODOs: hex with leading 0x or 0X, and octal with - leading 0o. -*/ -int cwal_cstr_to_int( char const * cstr, cwal_size_t slen, cwal_int_t * dest ); - -/** - Equivalent to cwal_cstr_to_int() but takes a cwal_string value. - Returns CWAL_RC_MISUSE if !s, else returns as for - cwal_cstr_to_int(). -*/ -int cwal_string_to_int( cwal_string const * s, cwal_int_t * dest ); - -/** - Behaves as for cwal_cstr_to_int(), but parses an integer or - literal double (in decimal form) with an optional leading sign. -*/ -int cwal_cstr_to_double( char const * cstr, cwal_size_t slen, cwal_double_t * dest ); - -/** - The cwal_string counterpart of cwal_cstr_to_double(). -*/ -int cwal_string_to_double( cwal_string const * s, cwal_double_t * dest ); - -/** - Compares the two given strings using memcmp() semantics with - these exceptions: - - if either of len1 or len2 are 0 then the longer of the two - strings compares de facto (without a string comparison) to - greater than the other. If both are 0 they are compared - as equal. - - len1 and len2 MUST point to their respective number of bytes of - live memory. If they are 0 their corresponding string is not - touched. i.e. s1 may be NULL only if len1 is 0, and likewise - for (s2,len2). -*/ -int cwal_compare_cstr( char const * s1, cwal_size_t len1, - char const * s2, cwal_size_t len2 ); - -/** - A cwal_compare_cstr() proxy which compares the given cwal_string - to the given c-style string. -*/ -int cwal_compare_str_cstr( cwal_string const * s1, - char const * s2, cwal_size_t len2 ); - -/** - Configures e to recycle, at most, n elements for the given - type. If the recycle list already contains more than that then - any extra elements in it are freed by this call. Set it to 0 to - disable recycling for the given type. - - typeID must be one of: - - CWAL_TYPE_INTEGER, CWAL_TYPE_DOUBLE (see notes below!), - CWAL_TYPE_OBJECT, CWAL_TYPE_ARRAY, CWAL_TYPE_NATIVE, - CWAL_TYPE_BUFFER, CWAL_TYPE_KVP, CWAL_TYPE_WEAK_REF, - CWAL_TYPE_STRING (but see below regarding strings). - - Or, as a special case, CWAL_TYPE_UNDEF means to apply this - change to all of the above-listed types. - - Also note that any built-in constant values are never - allocated, and so are not recycled via this mechanism. - - Returns 0 on succes, CWAL_RC_MISUSE if !e, and CWAL_RC_TYPE if - typeID is not refer to one of the recyclable types. - - Notes: - - ACHTUNG: As of 20141129, cwal groups the recycling bins by the size - of the Value type, and that sizing is platform-dependent and - determined at runtime. It is not possible for clients to determine - which types are grouped together, which means that this approach to - configuring the bin sizes is not as useful as it was before that - change. e.g. it may well be that Hashes and Buffers share the same - bin as Arrays, making it impossible to size the bins for exactly - per type (but giving us better recycling overall). THEREFORE... - the (probably) best approach to using this function is to call it - in the order of your preferred priority (highest sizes last), so - that any shared bins will get the highest size. e.g. passing it - CWAL_TYPE_INTEGER after CWAL_TYPE_DOUBLE will ensure that the - INTEGER recycle bin size is used on platforms where those types - have the same size. - - CWAL_TYPE_KVP is an internal type with no cwal_value - representation. Each key/value pair in an Object requires one - instance of cwal_kvp, and clients can control that recycling - level here. - - CWAL_TYPE_WEAK_REF is an internal type with no cwal_value - representation. We do, however, recycle them, if they are - configured for it. - - CWAL_TYPE_XSTRING and CWAL_TYPE_ZSTRING are equivalent here, - as those types use the same recycling bin. - - CWAL_TYPE_STRING recycling is comparatively limited because a - string's size plays a factor in its reusability. When choosing - strings from the recycling pool, only strings with the same - approximate length will be considered. This means it is - possible, depending on usage, to fill up the recycle pool with - strings of sizes we won't ever recycle. Internally, the library - pads new string sizes up to some common boundary because doing - so saves memory (somewhat ironically) by improving recylability - of strings from exact-fit-only to a close-fit. -*/ -int cwal_engine_recycle_max( cwal_engine * e, cwal_type_id type, cwal_size_t n ); - -/** - For the given cwal value type ID, this function returns the - maximum number of values of that type which e is configured to - keep in its recycle bin. Returns 0 if !e or recycling is - disabled or not allowed for the given type. - - Example: - - @code - cwal_size_t const x = cwal_engine_recyle_max_get(e, CWAL_TYPE_OBJECT); - @endcode -*/ -cwal_size_t cwal_engine_recycle_max_get( cwal_engine * e, cwal_type_id type ); - -/** - Sets up e's memory chunk recycler. It must currently be called - before any memory has been placed in that recycler (i.e. during - engine initialization). config's contents are copied into e, so the - object need not live longer than this call. - - Returns: - - - 0 on success - - - CWAL_RC_MISUSE if !e or !config. - - - CWAL_RC_OOM if growing the table fails. - - The ability to resize it at runtime is on the TODO list. It's - actually implemented but completely untested. -*/ -int cwal_engine_memchunk_config( cwal_engine * e, - cwal_memchunk_config const * config); - -/** - Runs the type-specific equivalence comparison operation for lhs - and rhs, using memcmp() semantics: returns 0 if lhs and rhs are - equivalent, less than 0 if lhs is "less than" rhs, and greater - than 0 if lhs is "greater than" rhs. Note that many types do - not have any sort of sensible orderings. This API attempts to - do something close to ECMAScript, but it does not exactly match - that. - - Note that this function does not guaranty return values of - exactly -1, 0, or 1, but may return any (perhaps varying) - negative resp. positive values. - - TODO: find the appropriate place to document the cross-type - comparisons and weird cases like undefined/null. - - Notes: - - - CWAL_TYPE_NULL and CWAL_TYPE_UNDEF compare equivalently to - any falsy value. (This was not true before 20140614, but no - known current code was broken by that change.) -*/ -int cwal_value_compare( cwal_value const * lhs, cwal_value const * rhs ); - -#if 0 -/* th1 has something like this... */ -int cwal_engine_call_scoped( cwal_engine * e, - int (*callback)(cwal_engine *e, void * state1, void * state2) ); -#endif -/** - A generic interface for callback functions which act as a - streaming input source for... well, for whatever. - - The arguments are: - - - state: implementation-specific state needed by the function. - - - n: when called, *n will be the number of bytes the function - should read and copy to dest. The function MUST NOT copy more than - *n bytes to dest. Before returning, *n must be set to the number of - bytes actually copied to dest. If that number is smaller than the - original *n value, the input is assumed to be completed (thus this - is not useful with non-blocking readers). - - - dest: the destination memory to copy the data to. - - Must return 0 on success, non-0 on error (preferably a value from - cwal_rc). - - There may be specific limitations imposed upon implementations - or extra effort required by clients. e.g. a text input parser - may need to take care to accommodate that this routine might - fetch a partial character from a UTF multi-byte character. -*/ -typedef int (*cwal_input_f)( void * state, void * dest, cwal_size_t * n ); - -/** - A cwal_input_f() implementation which requires the state argument - to be a readable (FILE*) handle. -*/ -int cwal_input_f_FILE( void * state, void * dest, cwal_size_t * n ); - -/** - A generic streaming routine which copies data from a - cwal_input_f() to a cwal_outpuf_f(). - - Reads all data from inF() in chunks of an unspecified size and - passes them on to outF(). It reads until inF() returns fewer - bytes than requested. Returns the result of the last call to - outF() or (only if reading fails) inF(). Returns CWAL_RC_MISUSE - if inF or ouF are NULL. - - Here is an example which basically does the same thing as the - cat(1) command on Unix systems: - - @code - cwal_stream( cwal_input_f_FILE, stdin, cwal_output_f_FILE, stdout ); - @endcode - - Or copy a FILE to a buffer: - - @code - cwal_buffer myBuf = cwal_buffer_empty; - cwal_output_buffer_state outState; - outState.b = &myBuf; - outState.e = myCwalEngine; - rc = cwal_stream( cwal_input_f_FILE, stdin, cwal_output_f_buffer, &outState ); - // Note that on error myBuf might be partially populated. - // Eventually clean up the buffer: - cwal_buffer_clear(&myBuf); - @endcode -*/ -int cwal_stream( cwal_input_f inF, void * inState, - cwal_output_f outF, void * outState ); - - -/** - Reserves the given amount of memory for the given buffer object. - - If n is 0 then buf->mem is freed and its state is set to - NULL/0 values. - - If buf->capacity is less than or equal to n then 0 is returned and - buf is not modified. - - If n is larger than buf->capacity then buf->mem is (re)allocated - and buf->capacity contains the new length. Newly-allocated bytes - are filled with zeroes. - - On success 0 is returned. On error non-0 is returned and buf is not - modified. - - buf->mem is owned by buf and must eventually be freed by passing an - n value of 0 to this function. - - buf->used is never modified by this function unless n is 0, in which case - it is reset. - - Example: - - @code - cwal_buffer buf = cwal_buffer_empty; // VERY IMPORTANT: copy initialization! - int rc = cwal_buffer_reserve( e, &buf, 1234 ); - ... - cwal_buffer_reserve( e, &buf, 0 ); // frees the memory - @endcode -*/ -int cwal_buffer_reserve( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ); - -/** - Fills all bytes of the given buffer with the given character. - Returns the number of bytes set (buf->capacity), or 0 if - !buf or buf has no memory allocated to it. -*/ -cwal_size_t cwal_buffer_fill( cwal_buffer * buf, unsigned char c ); - -/** - Uses a cwal_input_f() function to buffer input into a - cwal_buffer. - - dest must be a non-NULL, initialized (though possibly empty) - cwal_buffer object. Its contents, if any, will be overwritten by - this function, and any memory it holds might be re-used. - - The src function is called, and passed the state parameter, to - fetch the input. If it returns non-0, this function returns that - error code. src() is called, possibly repeatedly, until it reports - that there is no more data. - - Whether or not this function succeeds, dest still owns any memory - pointed to by dest->mem, and the client must eventually free it by - calling cwal_buffer_reserve(dest,0). - - dest->mem might (and possibly will) be (re)allocated by this - function, so any pointers to it held from before this call might be - invalidated by this call. - - On error non-0 is returned and dest has almost certainly been - modified but its state must be considered incomplete. - - Errors include: - - - dest or src are NULL (CWAL_RC_MISUSE) - - - Allocation error (CWAL_RC_OOM) - - - src() returns an error code (that code is returned). - - Whether or not the state parameter may be NULL depends on - the src implementation requirements. - - On success dest will contain the contents read from the input - source. dest->used will be the length of the read-in data, and - dest->mem will point to the memory. dest->mem is automatically - NUL-terminated if this function succeeds, but dest->used does not - count that terminator. On error the state of dest->mem must be - considered incomplete, and is not guaranteed to be NUL-terminated. - - Example usage: - - @code - cwal_buffer buf = cwal_buffer_empty; - int rc = cwal_buffer_fill_from( engine, &buf, cwal_input_f_FILE, - stdin ); - if( rc ){ - fprintf(stderr,"Error %d (%s) while filling buffer.\n", - rc, cwal_rc_cstr(rc)); - cwal_buffer_reserve( engine, &buf, 0 ); // might be partially populated - return ...; - } - ... use the contents via buf->mem ... - ... clean up the buffer ... - cwal_buffer_reserve( engine, &buf, 0 ); - @endcode - - To take over ownership of the buffer's memory, do: - - @code - void * mem = buf.mem; - buf = cwal_buffer_empty; - @endcode - - In which case the memory must eventually be passed to cwal_free() - to free it. - - TODO: add a flag which tells it whether to append or overwrite the - contents, or add a second form of this function which appends - rather than overwrites. -*/ -int cwal_buffer_fill_from( cwal_engine * e, cwal_buffer * dest, cwal_input_f src, void * state ); - -/** - A cwal_buffer_fill_from() proxy which overwrite's dest->mem - with the contents of the given FILE handler (which must be - opened for read access). Returns 0 on success, after which - dest->mem contains dest->used bytes of content from the input - source. On error dest may be partially filled. -*/ -int cwal_buffer_fill_from_FILE( cwal_engine * e, cwal_buffer * dest, FILE * src ); - -/** - Wrapper for cwal_buffer_fill_from_FILE() which gets its input - from the given file name. As a special case it interprets the - name "-" as stdin. -*/ -int cwal_buffer_fill_from_filename( cwal_engine * e, cwal_buffer * dest, char const * filename ); - -/** - Works just like cwal_buffer_fill_from_filename() except that it - takes a required length for the filename. This routine uses an - internal buffer to copy (on the stack) the given name and - NUL-terminate it at the nameLen'th byte. This is intended to - help protect against potentially non-NUL-terminated input - strings, e.g. from X- or Z-strings. - - Returns 0 on success, CWAL_RC_MISUSE if any pointer argument is - 0, and CWAL_RC_RANGE if nameLen is larger than the internal - name buffer (of "some reasonable size"). -*/ -int cwal_buffer_fill_from_filename2( cwal_engine * e, cwal_buffer * dest, - char const * filename, - cwal_size_t nameLen); - - -/** - Sets the "used" size of b to 0 and NULs the first byte of - b->mem if b->capacity is greater than 0. DOES NOT deallocate - any memory. - - Returns 0 on success and the only error case is if !b - (CWAL_RC_MISUSE). - - @see cwal_buffer_reserve() -*/ -int cwal_buffer_reset( cwal_buffer * b ); - -/** - Similar to cwal_buffer_reserve() except that... - - - It does not free all memory when n==0. Instead it essentially - makes the memory a length-0, NUL-terminated string. - - - It will try to shrink (realloc) buf's memory if (ncapacity). - - - It sets buf->capacity to (n+1) and buf->used to n. This routine - allocates one extra byte to ensure that buf is always - NUL-terminated. - - - On success it always NUL-terminates the buffer at - offset buf->used. - - Returns 0 on success, CWAL_RC_MISUSE if !buf, CWAL_RC_OOM if - (re)allocation fails. - - @see cwal_buffer_reserve() - @see cwal_buffer_clear() -*/ -int cwal_buffer_resize( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ); - - -/** - Convenience equivalent to cwal_buffer_reserve(e, b, 0). -*/ -int cwal_buffer_clear( cwal_engine * e, cwal_buffer * b ); - -/** - Appends the first n bytes of data to b->mem at position - b->used, expanding b if necessary. Returns 0 on success. If - !data then CWAL_RC_MISUSE is returned. This function - NUL-terminates b on success. -*/ -int cwal_buffer_append( cwal_engine * e, cwal_buffer * b, void const * data, cwal_size_t n ); - -/** - Appends printf-style formatted bytes to b using - cwal_printf(). Returns 0 on success. Always NUL-terminates the - buffer on success, but that NUL byte does not count against - b->used's length. - - If it detects an error while appending to the buffer, it resets - b->used to the length it had before calling this, and - NUL-terminates b->mem (if not NULL) at that position. i.e. the - visible effect on the buffer is as if this has not been called. -*/ -int cwal_buffer_printf( cwal_engine * e, cwal_buffer * b, char const * fmt, ... ); - -/** - Equivalent to cwal_buffer_printf() but takes a va_list instead - of ellipsis. -*/ -int cwal_buffer_printfv( cwal_engine * e, cwal_buffer * b, char const * fmt, va_list ); - - -/** - A string formatting function similar to Java's - java.lang.String.format(), with similar formatting rules. It - uses a formatting string to describe how to convert its - arguments to a formatted string, and appends the output to a - cwal_buffer instance. - - Overview of formatting rules: - - A format specifier has this syntax: - - %N$[flags][[-]width][.precision][type] - - "%%" is interpreted as a single "%" character, not a format - specifier. - - N = the 1-based argument (argv) index. It is 1-based because - that is how java.lang.String.format() does it. The argv value - at that index is expected to be of the type(s) specified by the - format specifier, or convertible to that type. - - How the width and precision are handled varies by type. TODO: - document the various behaviours and ensure semantic - compatibility (or close) with java.lang.String.format(). - - [type] must one of the following: - - - b: treat the argument as a boolean, evaluate to "true" or - "false". Width and precision are ignored. (TODO: treat - width/precision as padding/truncation, as for strings.) - - - B: "blobifies" the argument (which must be a Buffer or - String), encoding it as a series of hex values, two hex - characters per byte of length. The precision specifies the - maximum number of byte pairs to output (so the formatted length - will be twice the precision). - - - d, o, x, X: means interpret the result as an integer in - decimal, octal, hex (lower-case), or hex (upper-case), - respectively. If a width is specified and starts with a '0' - then '0' (instead of ' ') is used for left-side padding if the - number is shorter than the specified width. Precision is - ignored(?). - - - f: double value. Width and precision work like cwal_outputf() - and friends. - - - J: runs the value through cwal_json_output() to convert it to - a JSON string. The width can be used to specify - indentation. Positive values indent by that many spaces per - level, negative values indicate that many hard tabs per - level. The precision is ignored. - - - N, U: interpret the value as "null" or "undefined", - respectively. Width and precision are ignored. - - - p: evaluates to a string in the form TYPE_NAME\@ADDRESS, using - the hex notation form of the value's address. Width and - precision are ignored. - - - q: expects a string or NULL value. Replaces single-quote - characters with two single-quote characters and interpets NULL - values as "(NULL)" (without the quotes). - - - Q: like 'q' but surrounds string ouput with single quotes and - interprets NULL values as "NULL" (without the quotes). - - - s: string or buffer value. The precision determines the - maximum length. The width determines the minimum length. If - the string is shorter (in bytes!) than the absolute value of - the width then a positive width will left-pad the string with - spaces and a negative width will left-pad the string with - spaces. FIXME: USE UTF8 CHARS for precision and width! - - - y: evaluates to cwal_value_type_name(argv[theIndex]). Width - and precision are ignored. - - The flags field may currently only be a '+', which forces - numeric conversions to include a sign character. This sign - character does not count against the width/precision. - - Anything which is not a format specifier is appended as-is to - tgt. - - Note that tgt is appended to by this function, so when re-using - a buffer one may either need to set tgt->used=0 before calling - this or the caller should copy tgt->used before calling this - function and treating (tgt->mem + preCallUsed) as the start of the - output and (tgt->used - preCallUsed) as its length. - - Note that this function might reallocate tgt->mem, so any - pointers to it may be invalidated. - - Returns 0 on success. On error it returns non-0 and may replace - the contents of tgt->mem with an error string. It will do this - for all cases exception invalid arguments being passed to this - function (CWAL_RC_MISUSE) or an allocation error - (CWAL_RC_OOM). For all others, on error it writes an error - message (NUL-terminated) to (tgt->mem + (tgt->used when this - function was called)). - - - TODO: refactor this to take a cwal_output_f() instead of a - buffer then reimplement this function on top of that one. -*/ -int cwal_buffer_format( cwal_engine * e, cwal_buffer * tgt, - char const * fmt, cwal_size_t fmtLen, - uint16_t argc, cwal_value * const * const argv); - -/** - Searches the give buffer for byte sequences matching the first - needleLen bytes of needle with the first replLen bytes of repl. - needle may not be NULL and needleLen must be greater than 0. - replLen may be 0 and repl may not be NULL unless replLen is 0. - - needle is expected to be valid UTF8, but repl is not strictly - required to be. Results are undefined if needle is not valid UTF8 - (e.g. if needle starts part-way through a multi-byte character or - if needleLen truncates needle part-way through one). - - If limit>0 then that specifies the maximum number of replacements - to make. If limit is 0 then all matches are replaced. - - If changeCount is not NULL then *changeCount it is set to the - number of changes made (regardless of success or failure). - - ACHTUNG: this function needs to create a temporary buffer to work - on and it will (on success) swap out buf's contents with those of - the working buffer. Thus any pointers to buf->mem held before this - call will almost certainly (except in a couple rare corner cases) - be invalidated by this call. - - On success, returns 0 and replaces any matches in the buffer (up to - the specified limit, if any). On error, buf's contents/state are - not modified and non-0 is returned: - - - CWAL_RC_OOM if a memory allocation fails. - - - CWAL_RC_MISUSE if any of (e, buf, needle) are NULL or (!repl && - replLen>0). - - - CWAL_RC_RANGE if needleLen==0 (replLen may be 0). - - - @see cwal_buffer_replace_byte() -*/ -int cwal_buffer_replace_str( cwal_engine * e, cwal_buffer * buf, - unsigned char const * needle, cwal_size_t needleLen, - unsigned char const * repl, cwal_size_t replLen, - cwal_size_t limit, - cwal_size_t * changeCount); - -/** - Replaces instances of the given needle byte in the given buffer - with the given repl byte. - - If limit is 0, all matching bytes are replaced, else only the first - limit matches are replaced. - - If changeCount is not NULL then *changeCount is assigned to the - number of changes made (regardless of success or failure). As a - special case, if needle==repl then no changes are made. - - Returns 0 on success or CWAL_RC_MISUSE if either of (e, buf) are - NULL. - - Unlike cwal_buffer_replace_str(), this function modifies the input - buffer in-place and does not risk modifying (via reallocation) the - buf->mem address. - - @see cwal_buffer_replace_str() -*/ -int cwal_buffer_replace_byte( cwal_engine * e, cwal_buffer * buf, - unsigned char needle, unsigned char repl, - cwal_size_t limit, - cwal_size_t * changeCount); - - -/** - Client-configurable options for the cwal_json_output() family of - functions. -*/ -struct cwal_json_output_opt{ - /** - Specifies how to indent (or not) output. The values - are: - - (0) == no extra indentation. - - (-N) == -N TAB character for each level. - - (N) == N SPACES for each level. - - TODO: replace or supplement this with a ((char const *), - length) pair. - */ - int indent; - - /** - indentString offers a more flexible indentation method over the - (older) indent member. cwal_json_output() will use indentString - instead of indent if indentString.str is not NULL. - */ - struct { - /** - String to use for each level of indentation. The client - must ensure that these bytes outlive this object or - behaviour is undefined. - */ - char const * str; - /** - Number of bytes of this.str to use for each level of - indentation. - */ - cwal_size_t len; - } indentString; - - /** - Maximum object/array depth to traverse. Traversing deeply can - be indicative of cycles in the containers, and this value is - used to figure out when to abort the traversal. If JSON output - is triggered by this constraint, the result code will be - CWAL_RC_RANGE. - */ - unsigned short maxDepth; - - /** - If true, a newline will be added to the end of the generated - output, else not. - */ - char addNewline; - - /** - If true, a space will be added after the colon operator - in objects' key/value pairs. - */ - char addSpaceAfterColon; - - /** - If true, a space will be appended after commas in array/object - lists, else no space will be appended. - */ - char addSpaceAfterComma; - - /** - If set to 1 then objects/arrays containing only a single value - will not indent an extra level for that value (but will indent - on subsequent levels if that value contains multiple values). - */ - char indentSingleMemberValues; - - /** - The JSON format allows, but does not require, JSON generators - to backslash-escape forward slashes. This option enables/disables - that feature. According to JSON's inventor, Douglas Crockford: - - (quote) - It is allowed, not required. It is allowed so that JSON can be - safely embedded in HTML, which can freak out when seeing - strings containing "errorCode is not 0, then - the failure was either during parsing or an allocation failed - during parsing. - - On success, 0 is returned and *tgt is assigned to the root - object/array of the tree (it is initially owned by the - currently active scope). On success *tgt is guaranteed to be - either of type Object or Array (i.e. _one_ of - cwal_value_get_object() or cwal_value_get_array() will return - non-NULL). - - On error non-0 is returned and *tgt is not modified. pInfo - will, if not NULL, contain the location of the parse error (if - any). - - - ACHTUNG: if the build-time configuration option - CWAL_ENABLE_JSON_PARSER is set to 0 then the whole family of - cwal_json_parse() functions returns CWAL_RC_UNSUPPORTED when - called, but they will do so after doing any normal argument - validation, so those codes are still valid in such builds. -*/ -int cwal_json_parse( cwal_engine * e, cwal_input_f src, - void * state, cwal_value ** tgt, - cwal_json_parse_info * pInfo ); - -/** - Convenience form of cwal_json_parse() which reads its contents - from the given opened/readable file handle. -*/ -int cwal_json_parse_FILE( cwal_engine * e, FILE * src, - cwal_value ** tgt, - cwal_json_parse_info * pInfo ); - -/** - Convenience form of cwal_json_parse() which reads its contents from - the given file name. - - The file name "-" is interpreted as stdin. -*/ -int cwal_json_parse_filename( cwal_engine * e, char const * src, - cwal_value ** tgt, - cwal_json_parse_info * pInfo ); - -/** - Convenience form of cwal_json_parse() which reads its contents - from (at most) the first len bytes of the given string. -*/ -int cwal_json_parse_cstr( cwal_engine * e, char const * src, - cwal_size_t len, cwal_value ** tgt, - cwal_json_parse_info * pInfo ); - -/** - Sets the current trace mask and returns the old mask. mask is - interpreted as a bitmask of cwal_trace_flags_e values. If mask == - -1 then it returns the current mask without setting it, otherwise - it sets the trace mask to the given value and returns the previous - value. - - If !e or tracing is disabled at built-time, returns -1. -*/ -int32_t cwal_engine_trace_flags( cwal_engine * e, int32_t mask ); - -/** - Sets v's prototype value. Both v and prototype must be - container types (those compatible with cwal_prop_set() and - friends), and prototype may be NULL. - - If either v or prototype are not a container type, or v is a - built-in value, CWAL_RC_TYPE is returned. - - If (v==prototype), CWAL_RC_MISUSE is returned. - - If v has the CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flag, - CWAL_RC_DISALLOW_PROTOTYPE_SET is returned. (Added 20191210.) - - Returns CWAL_RC_CYCLES_DETECTED if v appears anywhere in the - given prototype's prototype chain, with the special allowance - of prototype already being v's prototype (see above). - - If none of the above-listed conditions apply and if prototype is - already v's prototype then this is a harmless no-op. - - If v already has a different prototype, it is un-ref'd during - replacement. - - On success, v adds a reference to the prototype object. - - On success, 0 is returned. -*/ -int cwal_value_prototype_set( cwal_value * v, cwal_value * prototpe ); - -/** - If v is a type capable of having a prototype, its prototype - (possibly NULL) is returned, otherwise it is equivalent to - cwal_value_prototype_base_get(e,cwal_value_type_id(v)) is - returned. - - Reminder to self: the engine argument is only required so that - this can integrate with cwal_prototype_base_get(). -*/ -cwal_value * cwal_value_prototype_get( cwal_engine * e, cwal_value const * v ); - -/** - Maps the given client-specified prototype value to be the - prototype for new values of type t. This adds a reference to - proto and moves it to e's top-most scope so that it will live - as long as e has scopes. - - Returns 0 on success, CWAL_RC_MISUSE if !e, and CWAL_RC_OOM - if insertion of the prototype mapping could not allocate - memory. - - All instances of the given type created after this is called - will, if they are container types (meaning, by extension, - capable of having a prototype) have proto assigned as their - prototype as part of their construction process. - - Note that cwal does not assign prototypes by default - this is - reserved solely for client-side use. - - Results are of course undefined if t is not a valid type ID (e.g. - cast from an out-of-range integer). - - Potential uses: - - - Mapping common functions, e.g. toString() implementations, - for types which cannot normally have prototypes (meaning - non-container types). - - - A central place to plug in client-defined prototypes, such - that new instances will inherit their prototypes (having had - this feature would have saved th1ish a bit of code). - - @see cwal_prototype_base_get() -*/ -int cwal_prototype_base_set( cwal_engine * e, cwal_type_id t, cwal_value * proto ); - -/** - Returns a prototype value set via cwal_prototype_base_set(), - or NULL if !e or no entry has been set by the client. -*/ -cwal_value * cwal_prototype_base_get( cwal_engine * e, cwal_type_id t ); - -/** - Returns true (non-0) if v==proto or v has proto in its - prototype chain. Returns 0 if any argument is NULL. - - Reminder to self: the engine argument is only necessary so that - this can integrate with cwal_prototype_base_get(). -*/ -bool cwal_value_derives_from( cwal_engine * e, - cwal_value const * v, - cwal_value const * proto ); - - -/** - Reparents v into one scope up from e's current scope, if possible - and necessary in order to keep v alive (it is not moved if it - already belongs in an older scope. Returns CWAL_RC_MISUSE if !v or - if v has no associated cwal_engine, 0 if v is already in a - top-level scope. This is a no-op for built-in constant values - (which do not participate in lifetime tracking). -*/ -int cwal_value_upscope( cwal_value * v ); - -/** - Returns a handle to v's originating cwal_engine, or NULL - if !v. -*/ -cwal_engine * cwal_value_engine( cwal_value const * v ); - -/** - Returns the current owning scope of v, or NULL if !v. - - Note that this is always 0 for values for which - cwal_value_is_builtin() returns true. -*/ -cwal_scope * cwal_value_scope( cwal_value const * v ); - - -/** - Possibly reallocates self->list, changing its size. This - function ensures that self->list has at least n entries. If n - is 0 then the list is deallocated (but the self object is not), - BUT THIS DOES NOT DO ANY TYPE-SPECIFIC CLEANUP of the items. If - n is less than or equal to self->alloced then there are no side - effects. If n is greater than self->alloced, self->list is - reallocated and self->alloced is adjusted to be at least n (it - might be bigger - this function may pre-allocate a larger - value). - - Passing an n of 0 when self->alloced is 0 is a no-op. - - Newly-allocated slots will be initialized with NUL bytes. - - Returns the total number of items allocated for self->list. On - success, the value will be equal to or greater than n (in the - special case of n==0, 0 is returned). Thus a return value smaller - than n is an error. Note that if n is 0 or self is NULL then 0 is - returned. - - The return value should be used like this: - - @code - cwal_size_t const n = number of bytes to allocate; - if( n > cwal_list_reserve( e, myList, n ) ) { ... error ... } - // Or the other way around: - if( cwal_list_reserve( e, myList, n ) < n ) { ... error ... } - @endcode -*/ -cwal_size_t cwal_list_reserve( cwal_engine * e, cwal_list * self, cwal_size_t n ); - -/** - Appends a bitwise copy of cp to self->list, expanding the list as - necessary and adjusting self->count. - - Ownership of cp is unchanged by this call. cp may not be NULL. - - Returns 0 on success, CWAL_RC_MISUSE if any argument is NULL, - or CWAL_RC_OOM on allocation error. -*/ -int cwal_list_append( cwal_engine * e, cwal_list * self, void * cp ); - -/** @typedef typedef int (*cwal_list_visitor_f)(void * p, void * visitorState ) - - Generic visitor interface for cwal_list lists. Used by - cwal_list_visit(). p is the pointer held by that list entry - and visitorState is the 4th argument passed to - cwal_list_visit(). - - Implementations must return 0 on success. Any other value - causes looping to stop and that value to be returned, but - interpration of the value is up to the caller (it might or - might not be an error, depending on the context). Note that - client code may use custom values, and is not restricted to - CWAL_RC_xxx values. -*/ -typedef int (*cwal_list_visitor_f)(void * obj, void * visitorState ); - -/** - For each item in self->list, visitor(item,visitorState) is called. - The item is owned by self. The visitor function MUST NOT free the - item, but may manipulate its contents if application rules do not - specify otherwise. - - If order is 0 or greater then the list is traversed from start to - finish, else it is traverse from end to begin. - - Returns 0 on success, non-0 on error. - - If visitor() returns non-0 then looping stops and that code is - returned. -*/ -int cwal_list_visit( cwal_list * self, int order, - cwal_list_visitor_f visitor, void * visitorState ); - -/** - Works similarly to the visit operation without the _p suffix except - that the pointer the visitor function gets is a (**) pointing back - to the entry within this list. That means that callers can assign - the entry in the list to another value during the traversal process - (e.g. set it to 0). If shiftIfNulled is true then if the callback - sets the list's value to 0 then it is removed from the list and - self->count is adjusted (self->alloced is not changed). -*/ -int cwal_list_visit_p( cwal_list * self, int order, char shiftIfNulled, - cwal_list_visitor_f visitor, void * visitorState ); - - -/** - Parses command-line-style arguments into a cwal object tree. - - argc and argv are expected to be values from main() (or - similar, possibly adjusted to remove argv[0]). - - It expects arguments to be in any of these forms, and any - number of leading dashes are treated identically: - - -key : Treats key as a boolean with a true value. - - +key : Treats key as a boolean with a false value. - - -key=VAL : Treats VAL as a boolean, double, integer, string, null, - or undefined (see cwal_value_from_arg()). - - -key= : Treats key as a cwal null (not literal NULL) value. - - +key=val : identical to -key=val. This "should" produce and - error, but this routine is intentionally lax. - - All such properties are accumulated in the (*tgt).flags Object - property. The flags object has no prototype, to avoid any - unintentional property lookups. - - Arguments not starting with a dash or '+' are treated as - "non-flags" and are accumulated in the (*tgt).nonFlags array - property. - - Each key/value pair is inserted into the (*tgt).flags object. If a - given key appears more than once then only the final entry is - actually stored. - - Any NULL entries in the argument list are skipped over. - - tgt must be either a pointer to NULL or a pointer to a - client-provided container value. If (NULL==*tgt) then this function - allocates a new object and on success it stores the new object in - *tgt (it is owned by the caller). If (NULL!=*tgt) then it is - assumed to be a properly allocated object. DO NOT pass a pointer to - unitialized memory, as that will fool this function into thinking - it is a valid object and Undefined Behaviour will ensue. If *tgt is - not NULL (i.e. the caller passes in their own target) then this - routine does not modify the refcount of the passed-in object. If - this routine allocates *tgt then (on success) the new object is - given to the caller with a refcount of 0. - - If *tgt is provided by the caller and is an array - (cwal_value_get_array() returns non-NULL), then each argument in - the provided argument list is appended as-is to that array. - - On success: - - - 0 is returned. - - - If (*tgt==NULL) then *tgt is assigned to a newly-allocated - object, owned by the caller, with a refcount of 0. Note that even - if no arguments are parsed, the object is still created. - - On error: - - - non-0 is returned: CWAL_RC_MISUSE if e or tgt are - NULL. CWAL_RC_MISUSE if *tgt is not NULL but cwal_props_can(*tgt) - returns false (i.e. if *tgt is not a property container type). - CWAL_RC_RANGE if argc is negative. - - - If (*tgt==NULL) then it is not modified. - - - If (*tgt!=NULL) (i.e. the caller provides his own object) then - it might contain partial results. - - @see cwal_value_from_arg() -*/ -int cwal_parse_argv_flags( cwal_engine * e, - int argc, char const * const * argv, - cwal_value ** tgt ); - -/** - A helper function intended for use in implementing utilities - like cwal_parse_argv_flags(). This function tries to evaluate - arg as follows: - - - If it looks like a number, return a numeric value. If the - number-looking value is too large (would overflow during - conversion), it is treated like a string instead. (Reminder - to self: that seems to work properly for integers but there - are likely ranges of doubles for which overflow is not - properly noticed, in which case the conversion may truncate - the resuling double-typed value.) - - - If it is "true" or "false", return the equivalent boolean value. - - - If it is NULL or "null", return the special null value. - - - If it is "undefined", return the special undefined value. - - - Else treat it like a string. If it starts and ends in matching - quotes (single or double), those are removed from the resulting - string. - - It is up to the caller to cwal_value_ref() the returned value. - - Returns NULL only on allocation error or if !e. - - Prior to 20181107, this function did not attempt to parse args - which started with leading '+' or '-' as numbers, but it now - does. If the input represents a massive number that is too large - for cwal_int, the internal attempt to convert it to an integer will - fail and this routine will fall back to treating it like a string. - - @see cwal_parse_argv_flags() -*/ -cwal_value * cwal_value_from_arg(cwal_engine * e, char const *arg); - -/** - Creates a new weak reference for the given value. The return - value can be passed to cwal_weak_ref_value() to find out if the - value referenced by the cwal_weak_ref is still valid. - - Returns NULL if !v or on allocation error. If recycling - is enabled for the CWAL_TYPE_WEAK_REF type then this will - re-use recyclable memory if any is available. - - Results are strictly undefined if v is not valid at the time - this is called (e.g. if it has already been destroyed and is a - dangling pointer). - - The caller must eventually pass the returned instance to - cwal_weak_ref_free() to clean it up. Note that cwal_weak_refs - are not owned by scopes, like values are, so they will not be - pulled out from under the client if a weak ref survives past - the cwal_scope under which it is created. - - Minor achtung: weak refs are themselves reference-counted, and - all weak refs to the same value (assuming it really _is_ the - same value when all weak refs are created) will be the same - weak ref instance. However, UNLIKE VALUES, they start life with - a refcount of 1 instead of 0 (a currently-necessary side-effect - of the sharing). That, however, is an implementation detail - which clients must not rely on. i.e. the must pass each - returned value from this function to cwal_weak_ref_free(), even - though this function may return the same value multiple times. - - If v is one of the built-in values then this function might return - a shared cwal_weak_ref instance, but this is an optimization and - implementation detail, and clients should not rely on it. - - The above refcounting and sharing is mentioned here primarily - in case someone happens to notice this function returning - duplicate pointers and thinks its a bug. It's not a bug, it - just means that v is one of the special built-in constants or a - multiply-weak-ref'd value. For built-ins, the weak reference - will never become invalidated because the built-in values are - neither allocated nor freed (and thus valid for the life of the - program). - - @see cwal_weak_ref_free() - @see cwal_weak_ref_value() - @see cwal_weak_ref_custom_new() -*/ -cwal_weak_ref * cwal_weak_ref_new( cwal_value * v ); - -/** - If r was created by cwal_weak_ref_new() and r's value is - still alive then this function returns it, else it returns - NULL. Will return NULL after the referenced value has been - destroyed via the normal value lifetime processes. - - Returns NULL if !r. - - @see cwal_weak_ref_new() - @see cwal_weak_ref_free() -*/ -cwal_value * cwal_weak_ref_value( cwal_weak_ref * r ); - -/** - Frees (or recycles) the memory associated with a weak - reference created by cwal_weak_ref_new() or - cwal_weak_ref_custom_new(). If the client fails to do so, the - reference will effectively leak until the engine is cleaned - up, at which point it will reap the memory of all dangling - weak references (at which point it becomes illegal for the - client to try to do so because both the cwal_engine and the - weak reference are invalid!). - - cwal_engine_recycle_max() can be used to configure the size of - the weak reference recycling pool by passing CWAL_TYPE_WEAK_REF - as its second parameter. - - @see cwal_weak_ref_new() -*/ -void cwal_weak_ref_free( cwal_engine * e, cwal_weak_ref * r ); - -/** - Creates a weak reference which "monitors" p. A call to - cwal_weak_ref_custom_invalidate(e,p) will "invalidate" any - weak references pointing to, such that - cwal_weak_ref_custom_check() and cwal_weak_ref_custom_ptr() - for references to that memory will return NULL. - - Note that this function recycles cwal_weak_ref instances for - any given value of p, meaning that this function may return - the same instance multiple times when passed the same - parameters. However, it reference counts them and each - instance should still be treated as unique and passed to - cwal_weak_ref_free() when the client is done with it. - - Clients must at some point call - cwal_weak_ref_custom_invalidate() to remove any entries they - "map" via weak references. Ideally they should do this in the - moment before their native memory is being finalized or - otherwise unassociated with script-space. If clients do not do - so then weak references to that memory will (incorrectly) - still think it is alive because cwal still holds a copy of - that pointer. - - @see cwal_weak_ref_custom_invalidate() - @see cwal_weak_ref_custom_check() - @see cwal_weak_ref_custom_ptr() -*/ -cwal_weak_ref * cwal_weak_ref_custom_new( cwal_engine * e, void * p ); - -/** - "Invalidates" p, in that future calls to - cwal_weak_ref_custom_check(e,p) or cwal_weak_ref_custom_ptr() - will return NULL. - - Returns 0 (false) if it does not find p in e's weak ref - mapping or non-0 (true) if it does (and thereby invalidates - existing weak refs to it). - - @see cwal_weak_ref_custom_new() -*/ -bool cwal_weak_ref_custom_invalidate( cwal_engine * e, void * p ); - -/** - Searches e to see if p is being monitored by weak references - created via cwal_weak_ref_custom_new(e,p). If one is found - then then p is returned, else NULL is returned. Note that a - call to cwal_weak_ref_custom_invalidate() "erases" monitored - pointers, and if p has been passed to it then this function - will return NULL. This is essentially an O(1) operation (a - hashtable lookup). -*/ -void * cwal_weak_ref_custom_check( cwal_engine * e, void * p ); - -/** - If r was created by cwal_weak_ref_custom_new() and has not - been invalidated then this function returns r's native memory - pointer (of a type known only to whoever created r, if at - all). Otherwise it returns NULL. This is faster than - cwal_weak_ref_custom_check() (O(1) vs. a slower O(1)). -*/ -void * cwal_weak_ref_custom_ptr( cwal_weak_ref * r ); - -/** - Returns true (non-0) if p has been registered as - weakly-referenced memory with e, else false (0). Note that p - is intended to be a client-side native memory address or - cwal_value pointer, and NOT one of the concrete higher-level types - like cwal_object, nor a cwal_weak_ref instance. - - p "should" be a const pointer, but some internals disallow - that (we don't do anything non-consty with it, though). In - script bindings, however, const pointers are fairly rare - because bound data are rarely const. -*/ -bool cwal_is_weak_referenced( cwal_engine * e, void * p ); - - -/** - Tokenizes an input string on a given separator. Inputs are: - - - (inp) = is a pointer to the pointer to the start of the input. - - - (separator) = the separator character - - - (end) = a pointer to NULL. i.e. (*end == NULL) - - This function scans *inp for the given separator char or a NULL char. - Successive separators at the start of *inp are skipped. The effect is - that, when this function is called in a loop, all neighboring - separators are ignored. e.g. the string "aa.bb...cc" will tokenize to - the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the - separator is 'b'. - - Returns 0 (false) if it finds no token, else non-0 (true). - - Output: - - - (*inp) will be set to the first character of the next token. - - - (*end) will point to the one-past-the-end point of the token. - - If (*inp == *end) then the end of the string has been reached - without finding a token. - - Post-conditions: - - - (*end == *inp) if no token is found. - - - (*end > *inp) if a token is found. - - It is intolerant of NULL values for (inp, end), and will assert() in - debug builds if passed NULL as either parameter. - - When looping, one must be sure to re-set the inp and end - parameters on each iterator. For example: - - @code - char const * head = "/a/b/c"; - char const * tail = NULL; - while( cwal_strtok( &inp, '/', &tail ) ) { - ... - head = tail; - tail = NULL; - } - @endcode - - If the loop calls 'continue', it must be careful to - ensure that the parameters are re-set, to avoid an endless - loop. This can be simplified with a goto: - - @code - while( cwal_strtok( &head, '/', &tail ) ) { - if( some condition ) { - ... do something ... - ... then fall through ... - } - else { - ... do something ... - ... then fall through ... - } - // setup next iteration: - head = tail; - tail = NULL; - } - @endcode - - or a for loop: - - @code - for( ; cwal_strtok(&head, '/', &tail); - head = tail, tail = NULL){ - ... - } - @endcode - - TODO: an implementation which takes a UTF8 char separator, or a - UTF8 string separator (we have the code in th1ish and s2). -*/ -bool cwal_strtok( char const ** inp, char separator, - char const ** end ); - -/** - Returns the first Function in v's prototype chain, including v. -*/ -cwal_function * cwal_value_function_part( cwal_engine * e, - cwal_value * v ); - -/** - Returns the first Object in v's prototype chain, including v. -*/ -cwal_object * cwal_value_object_part( cwal_engine * e, - cwal_value * v ); - -/** - Returns the first Array in v's prototype chain, including v. -*/ -cwal_array * cwal_value_array_part( cwal_engine * e, - cwal_value * v ); -/** - Returns the first Hash in v's prototype chain, including v. -*/ -cwal_hash * cwal_value_hash_part( cwal_engine * e, - cwal_value * v ); - -/** - Returns the first Buffer in v's prototype chain, including v. -*/ -cwal_buffer * cwal_value_buffer_part( cwal_engine * e, - cwal_value * v ); -/** - Returns the first Exception in v's prototype chain, including v. -*/ -cwal_exception * cwal_value_exception_part( cwal_engine * e, - cwal_value * v ); - -/** - Returns the first String in v's prototype chain, including v. -*/ -cwal_string * cwal_value_string_part( cwal_engine * e, - cwal_value * v ); - -/** - If the 3rd parameter is NULL, this returns the first Native - in v's prototype chain, including v. If the 3rd param - is not NULL then it returns the first Native in the - chain with a matching typeID. - - Returns 0 if no match is found. -*/ -cwal_native * cwal_value_native_part( cwal_engine * e, - cwal_value * v, - void const * typeID ); - -/** - Returns v, or the first value from v's prototype chain which is - capable of containing properties. Returns 0 if !v or if no - prototype exists which can hold properties. -*/ -cwal_value * cwal_value_container_part( cwal_engine * e, cwal_value * v ); - -/** - Installs or removes a callback hook. If h is not NULL, its - contents are bitwise copied into space owned by e, replacing - any existing callback hook. If h is NULL, any installed - callback hook is cleared (with no notification to the hooks!). - - @see cwal_callback_hook -*/ -int cwal_callback_hook_set(cwal_engine * e, cwal_callback_hook const * h ); - - -/** - Dumps e's internalized strings table to e's output channel. If - showEntries is true it lists all entries. If includeStrings is not - 0 then strings of that length or less are also output (longer ones - are not shown). If includeStrings is 0 then the strings are not - output. Note that the strings are listed in an unspecified order - (actually orded by (hash page number/hash code), ascending, but - that's an implementation detail). -*/ -void cwal_dump_interned_strings_table( cwal_engine * e, - char showEntries, - cwal_size_t includeStrings ); - -/** - Dumps some allocation-related metrics to e's output channel. - Intended only for optimization and debugging purposes. -*/ -void cwal_dump_allocation_metrics( cwal_engine * e ); - - -/** - Marks v as being exempted (or not) from vacuum operations, but - otherwise does not affect its lifetimes. Values marked as being - exempted, and any values they contain/reference (which includes all - array/tuple entries and property/hash table keys and values), will - be treated as script-visible, named variables for purposes of - cwal_engine_vacuum() (that is, a vacuum will not destroy them). - - If the 2nd argument is true, the value is marked as vacuum-proof, - otherwise it is unmarked, making it _potentially_ (based on its - exactly place in the universe) subject to subsequent vacuuming. - - Returns 0 on success or if v is a built-in value (they are - inherently vacuum-proof), CWAL_RC_MISUSE if v is 0. - - The intent of this function is only to make internal Values which - are not accessible via script code and which need to stay - alive. Such values require a reference (see cwal_value_ref()) and - to be vacuum-proofed via this function. As of this writing, in the - whole cwal/th1ish/s2 constellation, only a small handful of values - are marked as vacuum-proof: (A) cwal's internal list of prototypes - (only the list, not the prototypes) and (B) a piece of th1sh's and - s2's internals where it stashes its own non-script visible - values. Any values reachable via a vacuum-proof container are safe - from vacuuming, thanks to side-effects of cwal's lifetime - management. - - Achtung: if v is ever to be made visible to script code, it most - certainly should be set to NOT vacuum-proof, by passing 0 as this - function's 2nd argument, or else it won't ever be able to be - vacuumed up if it gets orphaned (with cycles) in a script. If it - gets no cycles and all references are gone, it can still be reaped - immediately or (depending on other conditions) swept up later. - - @see cwal_engine_vacuum() -*/ -int cwal_value_make_vacuum_proof( cwal_value * v, char yes ); - -/** - Returns true if v has explicitly been made vacuum-proof using - cwal_value_make_vacuum_proof() OR if it is a built-in constant - value, else false. A value which is not explicitly vacuum-proof may still - be implicitly vacuum-proofed via a container which creates a path - leading to the value. -*/ -bool cwal_value_is_vacuum_proof( cwal_value const * v ); - -/** - Adjusts (optional) metrics (only) which keep track of how much - memory has been allocated by a client for use with cwal. This - information is generally only useful for debugging and - reporting purposes, and cannot be used to enforce any sort of - memory caps. - - The second parameter is the amount of memory to report (for a - positive value) or recall (a negative value). Clients who wish - to use this should pass it a positive value when allocating - memory and a negative value while cleaning up. A realloc may - require first passing the negative value of the old size - followed by the positive value of the new size. - - The point of this routine is to allow higher-level clients to - help account for memory they allocate for use with their cwal - bindings. e.g. s2 reports the memory allocated for Functions - this way, so that they can be counted via the engine's metrics. - - Clients should not use this to report changes in memory to - cwal_buffer instances, as those are handled at the library - level. Its primary intended use is for tracking allocation - totals for memory allocated on behalf of client-side types, - e.g. the C part of a cwal_native value, which cwal knows is - there (because it holds a (void*) to it), but does not have any - information regarding its size or semantics. - - If amount is negative and its absolute value is larger than the - currently declared allocation total, the total is reduced to 0, - as opposed to underflowing. - - As of 20141214, cwal_malloc() and friends optionally track all - memory they manage, such that they can report the exact amount of - memory they've been requested to process. That is part of the - cwal_memcap_config mechanism, and when it is enabled, any - cwal_malloc()-allocated memory which clients report here is - effectively counted double (once by the allocator and once by the - client) by cwal_dump_allocation_metrics(). No harm done, though, - other than double counting of that memory in metrics dumps. -*/ -void cwal_engine_adjust_client_mem( cwal_engine * e, cwal_int_t amount ); - -/** - Sets client-side flags on the container value v. - - A container is defined as: cwal_props_can() returns true for the - value. - - If v is a container value, its flags are set to the given flags and - the old flags value is returned. - - If !v or v is not a container then 0 is returned (which is also - the default flags value. - - Notes about these flags: - - - They are reserved for cwal client use. Whether that means a - scripting engine on top of cwal or a client above that is up to the - layer between cwal and the higher-level client. - - - Their interpretation is of course client-dependent. - - - There are only 16 of them. We can't have more without increasing - the sizeof() for container values. - - - These are independent of flags set via, e.g. - cwal_prop_set_with_flags_v(). - - @see cwal_container_client_flags_get() - @see cwal_container_flags_get() -*/ -cwal_flags16_t cwal_container_client_flags_set( cwal_value * v, cwal_flags16_t flags ); - -/** - Gets any flags set using cwal_container_client_flags_set(), or 0 if !v or - v is-not-a Container type. Note that 0 is also a legal return value - for a container, and is the default if no flags have been - explicitly set on the value. - - @see cwal_container_client_flags_set() - @see cwal_container_flags_set() -*/ -cwal_flags16_t cwal_container_client_flags_get( cwal_value const * v ); - -/** - Sets a bitmask of values from the cwal_container_flags enum - as the given value's flags. - - A container is defined as: cwal_props_can() returns true for the - value. - - If v is a container value, its flags are set to the given flags and - the old flags value is returned. - - If !v or v is not a container then 0 is returned (which is also - the default flags value. - - @see cwal_container_flags_get() - @see cwal_container_client_flags_set() - @see cwal_container_client_flags_get() -*/ -cwal_flags16_t cwal_container_flags_set( cwal_value * v, cwal_flags16_t flags ); - -/** - Gets any flags set using cwal_container_flags_set(), or 0 if !v or - v is-not-a Container type. Note that 0 is also a legal return value - for a container, and is the default if no flags have been - explicitly set on the value. - - @see cwal_container_flags_set() - @see cwal_container_client_flags_set() -*/ -cwal_flags16_t cwal_container_flags_get( cwal_value const * v ); - - -/** - Works like cwal_printfv(), but appends all output to a - dynamically-allocated string, expanding the string as necessary to - collect all formatted data. The returned null-terminated string is - owned by the caller and it must be cleaned up using cwal_free(). If !fmt - or if the expanded string evaluates to empty, null is returned, not - a 0-byte string. -*/ -char * cwal_printfv_cstr( cwal_engine * e, char const * fmt, va_list vargs ); - -/** - Equivalent to cwal_printfv_cstr(), but takes elipsis arguments instead - of a va_list. -*/ -char * cwal_printf_cstr( cwal_engine * e, char const * fmt, ... ); - -/** - Returns the value of the CWAL_VERSION_STRING build-time - configuration macro (from static memory). If the length param is - not NULL then the length, in bytes, of the string is written in - (*length). -*/ -char const * cwal_version_string(cwal_size_t * length); - -/** - Returns the value of the CWAL_CPPFLAGS build-time - configuration macro (from static memory). If the length param is - not NULL then the length, in bytes, of the string is written in - (*length). -*/ -char const * cwal_cppflags(cwal_size_t * length); - -/** - Returns the value of the CWAL_CFLAGS build-time configuration - configuration macro (from static memory). If the length param is - not NULL then the length, in bytes, of the string is written in - (*length). -*/ -char const * cwal_cflags(cwal_size_t * length); - -/** - Returns the value of the CWAL_CXXFLAGS build-time - configuration macro (from static memory). If the length param is - not NULL then the length, in bytes, of the string is written in - (*length). -*/ -char const * cwal_cxxflags(cwal_size_t * length); - -/** - If e is NULL, returns CWAL_RC_MISUSE, else returns 0 unless the - cwal APIs have internally flagged e as being "dead". This state - ONLY happens when conditions which are normally assert()ed in debug - builds are noticed in non-debug builds. Once this flag has been - set, then e is in a corrupt state and might have leaked memory to - avoid touching memory it believes to be corrupted. - - In debug builds, this will ALWAYS return 0 (if e is valid) because - the conditions which set this flag all assert() in debug builds, - crashing the app outright. - - If this function returns non-0 for a valid argument, there is - generically no recovery option other than letting e's memory - leak and exiting the app, leaving cleanup to the OS. If - e is passed to cwal_engine_destroy(), that destruction might - try to step on some of the apparently corrupted memory, so - the results are undefined. - - Corruption of the type which triggers such conditions are - essentially always caused by unref'ing cwal_value pointers too many - times (i.e. after cwal has stuck it in the recycler or freed it). - - TODO: this flag is not currently set for all assertions, but - it basically needs to be. -*/ -int cwal_is_dead(cwal_engine const * e); - -/** - Creates a new "tuple" value with the given length. Tuples are - basically fixed-length arrays which cannot hold properties. - - Returns NULL if !e or on allocation error. - - The cwal_value_type_id() for tuples is CWAL_TYPE_TUPLE and their - cwal_type_id_name() is "tuple". - - @see cwal_new_tuple_value() - @see cwal_tuple_set() - @see cwal_tuple_get() - @see cwal_tuple_length() - -*/ -cwal_tuple * cwal_new_tuple(cwal_engine * e, uint16_t n); - -/** - Equivalent to passing the result of cwal_new_tuple() to - cwal_tuple_value(). - - @see cwal_new_tuple_value() -*/ -cwal_value * cwal_new_tuple_value(cwal_engine * e, uint16_t n); - -/** - Returns the cwal_value part of tp, or NULL if !tp. - - @see cwal_new_tuple() -*/ -cwal_value * cwal_tuple_value(cwal_tuple const *tp); - -/** - Returns the length of the given tuple (the number of slots - it has for storing elements). - - @see cwal_new_tuple_value() -*/ -uint16_t cwal_tuple_length(cwal_tuple const * tp); - -/** - Gets the value stored at the given index in the given - tuple. Returns NULL if n is out of range (not less than - cwal_tuple_length()) or if that slot has no value. - - @see cwal_new_tuple() - @see cwal_tuple_set() -*/ -cwal_value * cwal_tuple_get(cwal_tuple const * tp, uint16_t n); - -/** - Sets the value at the given index in the given - tuple. Returns NULL if n is out of range (not less than - cwal_tuple_length()) or if that slot has no value. - - This might (depending on refcounts) destroy any prior item held in - that slot. - - Returns 0 on success, CWAL_RC_MISUSE if !p, CWAL_RC_RANGE if n is - not less than cwal_tuple_length(tp). - - Note that, unlike cwal_array_set(), this function never has to - allocate, so cannot fail if its arguments are valid. - - @see cwal_new_tuple() - @see cwal_tuple_get() - @see cwal_tuple_length() -*/ -int cwal_tuple_set(cwal_tuple * tp, uint16_t n, cwal_value * v); - -/** - Works identically to cwal_array_visit(). - - @see cwal_new_tuple_value() -*/ -int cwal_tuple_visit( cwal_tuple * tp, cwal_value_visitor_f f, void * state ); - -/** - If v was created using cwal_new_tuple_value() or cwal_new_tuple(), - this function returns its cwal_tuple part, else it returns NULL. - - @see cwal_new_tuple_value() - @see cwal_new_tuple() -*/ -cwal_tuple * cwal_value_get_tuple( cwal_value * v ); - -/* tuple is not a property container, thus it cannot be a prototype, thus no: - cwal_tuple * cwal_tuple_part( cwal_engine * e, cwal_value * v ); -*/ - -/** - A type for making build-time configuration data of the library - easily available to clients. - - Use cwal_build_info() to get at this info. -*/ -struct cwal_build_info_t { - - /* Config sizes... */ - cwal_size_t const size_t_bits; - cwal_size_t const int_t_bits; - cwal_size_t const maxStringLength; - - /* Strings... */ - char const * const versionString; - char const * const cppFlags; - char const * const cFlags; - char const * const cxxFlags; - - /* Booleans... */ - char const isJsonParserEnabled; - char const isDebug; - - /* sizeof()s... */ - struct { - cwal_size_t builtinValues; - cwal_size_t cwalValue; - cwal_size_t voidPointer; - } sizeofs; -}; -typedef struct cwal_build_info_t cwal_build_info_t; - -/** - Returns the library's shared/static cwal_build_info_t object. -*/ -cwal_build_info_t const * cwal_build_info(void); - -/** - Returns the new value (an Object) on success, 0 on error. The only - error cases are misuse (e is NULL or does not have an active scope) - or allocation error. - - The new object is returned without any references - the caller - effectively takes "ownership" (given what that means in this - framework). - - @see cwal_callback_f_build_info() -*/ -cwal_value * cwal_build_info_object(cwal_engine * e); - -/** - A cwal_callback_f() implementation which wraps - cwal_build_info_object(). - - On success, returns 0 and sets *rv to the build info object. On - error CWAL_RC_OOM is returned. -*/ -int cwal_callback_f_build_info(cwal_callback_args const * args, cwal_value ** rv); - -/** - Sets err's state to the given code/string combination, using - cwal_buffer_printf() formatting. - - If fmt is 0 or !*fmt then any existing error message is reset. - - As a special case, if code==CWAL_RC_OOM, it behaves as if fmt is - 0 to avoid allocating any new memory. - - The e argument is required for its allocator - this function does - not directly modify e's state, only err's (noting that err may well - be embedded in e, so indirect modification of e is possible). - - If the 2nd argument is NULL, e's error state is used. - - On success it returns the 3nd argument (NOT 0!). On error it returns - some other non-0 code. -*/ -int cwal_error_setv( cwal_engine * e, cwal_error * err, int code, char const * fmt, va_list ); - -/** - Elipses counterpart of cwal_error_setv(). -*/ -int cwal_error_set( cwal_engine * e, cwal_error * err, int code, char const * fmt, ... ); - -/** - Copies src's error state over to dest, reusing dest's buffer memory - if possible. - - If src is NULL then e's error state is used, otherwise if dest is NULL - then e's error state is used. That is, ONE of the 2nd or 3rd - arguments may be NULL, but not both. - - Returns 0 on success, CWAL_RC_MISUSE if src==dest, CWAL_RC_OOM on - allocation error. -*/ -int cwal_error_copy( cwal_engine * e, cwal_error const * src, cwal_error * dest ); - -/** - Resets any any state in err, but keeps any memory in place for - re-use. -*/ -void cwal_error_reset( cwal_error * err ); - -/** - Resets e's error state using cwal_error_reset(). -*/ -void cwal_engine_error_reset( cwal_engine * e ); - -/** - Returns a pointer to e's error state object. The pointer is owned - by e. When other cwal APIs refer to a cwal_engine's "error state," - they's referring to this object unless specified otherwise. - - Note that the core library does not actually use this object. It's - intended as a convenience for downstream code, and was added to the - API to support decoupling of certain downstream code. -*/ -cwal_error * cwal_engine_errstate( cwal_engine * e ); - -#if 0 -/* needed? */ -/** - Const-correct counterpart of cwal_engine_errstate(). -*/ -cwal_error const * cwal_engine_errstate_c( cwal_engine const * e ); -#endif - -/** - Moves the error state from one s2_error object to another, intended - as an allocation optimization when propagating error state up the - API. - - This "uplifts" an error from the 'from' object to the 'to' - object. After this returns 'to' will contain the prior error state - of 'from' and 'from' will contain the old error message memory of - 'to'. 'from' will be re-set to the non-error state (its buffer - memory is kept intact for later reuse, though). - - Results are undefined if either parameter is NULL or either is not - properly initialized. i.e. neither may refer to uninitialized - memory. Copying s2_error_empty at declaration-time is a simple way - to ensure that instances are cleanly initialized. -*/ -void cwal_error_move( cwal_error * from, cwal_error * to ); - -/** - Frees all memory owned by err, but does not free err. The e - argument is required for its underlying allocator. If err is NULL - then e's error state is cleared. -*/ -void cwal_error_clear( cwal_engine * e, cwal_error * err ); - -/** - If err->code is not 0, *msg and *msgLen (if they are not NULL) - are assigned to the message string and its length, respectively. - It is legal for the returned *msg value to be NULL, which simply - indicates that no error string was provided when the error state - was set. - - Returns err->code. - - If it returns 0 (err has no error state) then it does not modify - msg or msgLen. -*/ -int cwal_error_get( cwal_error const * err, char const ** msg, cwal_size_t * msgLen ); - -/** - Works like cwal_error_get(), using e's error state as the source. -*/ -int cwal_engine_error_get( cwal_engine const * e, char const ** msg, cwal_size_t * msgLen ); - -/** - Takes err's error string and uses it to create a new Exception - value. If !err, e's error state is used. On success, it returns a - new Exception with the err->code error code and a "message" - property containing err's error string. If err->msg is empty, then - a generic message is created based on err->code. - - If scriptName is not 0 and (*scriptName) then the exception - gets a "script" string property with that value. - - If (line>0) then the exception gets line/column properties - holding the line/col values. - - Returns 0 on error (OOM). - - This does not set e's exception state, but it does (on success) - clear err's error state (so that we can give the string directly to - cwal instead of copying it). err may or may not still own memory - buffer memory after this call, so it must (eventually) be cleaned - up using cwal_error_clear(). -*/ -cwal_value * cwal_error_exception( cwal_engine * se, - cwal_error * err, - char const * scriptName, - int line, int col ); - -/** - Converts err (or e's error state, if err is NULL) to an exception - value using cwal_error_exception() (see that func for important - details) then set's e's exception state to that exception. - - Like cwal_exception_set(), this function returns CWAL_RC_EXCEPTION - on success or some other non-0 code if creation of the exception - fails (generally speaking, probably CWAL_RC_OOM). - - If line<=0 then err->line and err->col are used in place of the given - line/column parameters. - - If script is 0 and err->script is populated, that value is used - instead. -*/ -int cwal_error_throw( cwal_engine * se, cwal_error * err, - char const * script, - int line, int col ); - - -/* LICENSE - - This software's source code, including accompanying documentation - and demonstration applications, are licensed under the following - conditions... - - Certain files are imported from external projects and have their - own licensing terms. Namely, the JSON_parser.* files. See their - files for their official licenses, but the summary is "do what you - want [with them] but leave the license text and copyright in - place." - - The author (Stephan G. Beal - [https://wanderinghorse.net/home/stephan/]) explicitly disclaims - copyright in all jurisdictions which recognize such a - disclaimer. In such jurisdictions, this software is released into - the Public Domain. - - In jurisdictions which do not recognize Public Domain property - (e.g. Germany as of 2011), this software is Copyright (c) - 2011-2021 by Stephan G. Beal, and is released under the terms of - the MIT License (see below). - - In jurisdictions which recognize Public Domain property, the user - of this software may choose to accept it either as 1) Public - Domain, 2) under the conditions of the MIT License (see below), or - 3) under the terms of dual Public Domain/MIT License conditions - described here, as they choose. - - The MIT License is about as close to Public Domain as a license - can get, and is described in clear, concise terms at: - - https://en.wikipedia.org/wiki/MIT_License - - The full text of the MIT License follows: - - -- - Copyright (c) 2011-2021 Stephan G. Beal - (https://wanderinghorse.net/home/stephan/) - - 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. - - --END OF MIT LICENSE-- -*/ - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#endif /* WANDERINGHORSE_NET_CWAL_H_INCLUDED */ -/* end of file include/wh/cwal/cwal.h */ -/* start of file include/wh/cwal/cwal_printf.h */ -#ifndef WANDERINGHORSE_NET_CWAL_APPENDF_H_INCLUDED -#define WANDERINGHORSE_NET_CWAL_APPENDF_H_INCLUDED 1 -#ifdef _MSC_VER - #define _CRT_NONSTDC_NO_DEPRECATE -#endif -#include -#include /* FILE handle */ -#ifdef __cplusplus -extern "C" { -#endif -/** @page cwal_printf_page_main cwal_printf printf-like API - - This API contains a printf-like implementation which supports - aribtrary data destinations. - - Authors: many, probably. This code supposedly goes back to the - early 1980's. - - Current maintainer: Stephan Beal (https://wanderinghorse.net/home/stephan) - - License: Public Domain. - - The primary functions of interest are cwal_printfv() and cwal_printf(), which works - similarly to printf() except that they take a callback function which they - use to send the generated output to arbitrary destinations. e.g. one can - supply a callback to output formatted text to a UI widget or a C++ stream - object. -*/ - -/** - @typedef int (*cwal_printf_appender_f)( void * arg, char const * data, unsigned n ) - - The cwal_printf_appender_f typedef is used to provide - cwal_printfv() with a flexible output routine, so that it can be - easily send its output to arbitrary targets. - - The policies which implementations need to follow are: - - - arg is an implementation-specific pointer (may be 0) which is - passed to cwal_printfv(). cwal_printfv() doesn't know what this - argument is but passes it to its cwal_printf_appender_f - argumnt. Typically it will be an object or resource handle to which - string data is pushed or output. - - - The 'data' parameter is the data to append. If it contains - embedded nulls, this function will stop at the first one. Thus - it is not binary-safe. - - - n is the number of bytes to read from data. - - - Returns 0 on success, some non-0 value on error. Ideally it - should return a value from the cwal_rc_e enum or a value which is - guaranteed not to collide with that enum. -*/ -typedef int (*cwal_printf_appender_f)( void * arg, - char const * data, - unsigned int n ); - -/** - This function works similarly to classical printf implementations, - but instead of outputing somewhere specific, it uses a callback - function to push its output somewhere. This allows it to be used for - arbitrary external representations. It can be used, for example, to - output to an external string, a UI widget, or file handle (it can - also emulate printf by outputing to stdout this way). - - INPUTS: - - pfAppend : The is a cwal_printf_appender_f function which is - responsible for accumulating the output. If pfAppend returns non-zero - then processing stops immediately and that code is returned. - - pfAppendArg : is ignored by this function but passed as the first - argument to pfAppend. pfAppend will presumably use it as a data - store for accumulating its string. - - fmt : This is the format string, as in the usual printf(). - - ap : This is a pointer to a list of arguments. Same as in - vprintf() and friends. - - OUTPUTS: - - Returns 0 on success. (Years of practice have shown that classical - printf() return semantics don't make terribly much sense for this - API.) On error, it is not generically possible to know how much, if any - output was generated. - - CURRENT (documented) exceptions to conventional PRINTF format - specifiers: - - %%n IS NOT SUPPORTED. Years of practice have shown that the classical - return semantics of printf() are not useful for this particular API, - and thus the semantics of this API were changed to something more - useful (which does not support the notion of %%n). - - %%z (DISABLED IN THE CWAL BUILD!) works like %%s, but takes a - non-const (char *) and free()s the string after appending it to the - output. NEVER EVER EVER pass memory allocated via cwal_alloc() to - %%z, as cwal_alloc() may, depending on various runtime options, - manage memory in a way incompatible with free(). - - %%h (HTML) works like %s but converts certain characters (like '<' - and '&' to their HTML escaped equivalents. - - %%t (URL encode) works like %%s but converts certain characters into - a representation suitable for use in an HTTP URL. (e.g. ' ' gets - converted to %%20) - - %%T (URL decode) does the opposite of %t - it decodes URL-encoded - strings. - - %%r requires an int and renders it in "ordinal form". That is, the - number 1 converts to "1st" and 398 converts to "398th". - - %%q quotes a string as required for SQL. That is, '\'' characters get - doubled. - - %%Q as %%q, but includes the outer '\'' characters and null pointers - replaced by SQL NULL. - - (The %%q and %%Q specifiers are options inherited from this printf - implementation's sqlite3 genes.) - - %%j JSON-escapes a string. %%!j does the same but adds outer double - quotes. It treats NULL as an empty string. - - These extensions may be disabled by setting certain macros when - compiling the implementation file (see that file for details). -*/ -int cwal_printfv(cwal_printf_appender_f pfAppend, - void * pfAppendArg, - const char *fmt, - va_list ap); - -/** - The elipsis counterpart of cwal_printfv(). -*/ -int cwal_printf(cwal_printf_appender_f pfAppend, - void * pfAppendArg, - const char *fmt, - ... ); - -/** - Emulates fprintf() using cwal_printfv(). -*/ -int cwal_printf_FILE( FILE * fp, char const * fmt, ... ); - -/** - va_list variant of cwal_printf_FILE(). -*/ -int cwal_printfv_FILE( FILE * fp, char const * fmt, va_list args ); - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif /* WANDERINGHORSE_NET_CWAL_APPENDF_H_INCLUDED */ -/* end of file include/wh/cwal/cwal_printf.h */ -#endif/*!defined(WANDERINGHORSE_NET_CWAL_AMALGAMATION_H_INCLUDED)*/ -/* end of file /home/stephan/fossil/cwal/cwal_amalgamation.h */ -/* start of file s2_config.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -#if !defined(WANDERINGHORSE_NET_CWAL_S2_CONFIG_H_INCLUDED) -#define WANDERINGHORSE_NET_CWAL_S2_CONFIG_H_INCLUDED 1 -/** - s2_config.h is intended to be auto-generated by a configure script - in order to tell us whether certain system-level features are - available. If the configuration process is unavailable for some reason, - toggling all config options to off (with a value of 0) "should" work - in a pinch. - - We have a fundamental conflict of interest here in terms of - embedding these options here and accessing them from the loadable - modules (all of which build against the amalgamation header). - - We disable all of the configurable options by default when - building in amalgamation mode so as to avoid forcing 3rd-party - prerequisites on users of s2_amalgamation.[ch]. - - When building with the amalgamation build (where these entries - cannot be easily/maintainably replaced), these defines can be - passed on via the compiler flags or by \#include'ing a separate - header beforehand which sets them. cwal_config.h (which gets - injected as the top-most section in cwal_amalgamation.h) supports - the flag HAVE_CONFIG_H, which tells it to #include "config.h", so - overrides can be put there. Note, however, that THIS file gets - added to the amalgamation *after* that one, so overriding - cwal-level options cannot work from this file. -*/ - -#if !defined(CWAL_VERSION_STRING) -# define CWAL_VERSION_STRING "cwal 601b514dd3f0cf7cde97274b9fa46b2826d54ccc 2021-07-24 09:36:48 configured 2021-07-24 10:37" -#endif -#if !defined(CWAL_CFLAGS) -# define CWAL_CFLAGS "-Werror -Wall -Wextra -Wsign-compare -fPIC -std=c99 -g -Wpedantic" -#endif -#if !defined(CWAL_CPPFLAGS) -# define CWAL_CPPFLAGS "-I/home/stephan/include" -#endif - -#if !defined(S2_OS_WINDOWS) && !defined(S2_OS_UNIX) -# if defined(_WIN32) -# define S2_OS_WINDOWS -# else -# define S2_OS_UNIX -# endif -#endif - -#if !defined(S2_HAVE_USLEEP) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_USLEEP 0 -# else -# define S2_HAVE_USLEEP 1 /* @ HAVE_USLEEP@ */ -# endif -#endif - -#if !defined(S2_HAVE_CLOCK_GETTIME) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_CLOCK_GETTIME 0 -# else -# define S2_HAVE_CLOCK_GETTIME 1 /* @ HAVE_CLOCK_GETTIME@ */ -# endif -#endif - -#if !defined(S2_HAVE_REGCOMP) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_REGCOMP 0 -# else -# define S2_HAVE_REGCOMP 1 /* @ HAVE_REGCOMP@ */ -# endif -#endif - -#if !defined(S2_HAVE_STAT) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_STAT 0 -# else -# define S2_HAVE_STAT 1 /* @ HAVE_STAT@ */ -# endif -#endif - -#if !defined(S2_HAVE_MKDIR) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_MKDIR 0 -# else -# define S2_HAVE_MKDIR 1 /* @ HAVE_MKDIR@ */ -# endif -#endif - -#if !defined(S2_HAVE_LSTAT) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_LSTAT 0 -# else -# define S2_HAVE_LSTAT 1 /* @ HAVE_LSTAT@ */ -# endif -#endif - -#if !defined(S2_HAVE_CHDIR) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_CHDIR 0 -# else -# define S2_HAVE_CHDIR 1 /* @ HAVE_CHDIR@ */ -# endif -#endif - -#if !defined(S2_HAVE_GETCWD) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_GETCWD 0 -# else -# define S2_HAVE_GETCWD 1 /* @ HAVE_GETCWD@ */ -# endif -#endif - -#if !defined(S2_HAVE_REALPATH) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_REALPATH 0 -# else -# define S2_HAVE_REALPATH 1 /* @ HAVE_REALPATH@ */ -# endif -#endif - -#if !defined(S2_HAVE_DLOPEN) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_DLOPEN 0 -# else -# define S2_HAVE_DLOPEN 1 /* @ HAVE_DLOPEN@ */ -# endif -#endif - -/* Ensure that only 1 of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN are set, - so that the build doesn't get confused about which to use. The - makefile(s) must also use this same selection process. -*/ -#if S2_HAVE_DLOPEN -# undef S2_HAVE_LTDLOPEN -# define S2_HAVE_LTDLOPEN 0 -#elif !defined(S2_HAVE_LTDLOPEN) -# if defined(S2_AMALGAMATION_BUILD) -# define S2_HAVE_LTDLOPEN 0 -# else -# define S2_HAVE_LTDLOPEN 0 /* @ HAVE_LTDL@ */ -# endif -#endif - -#if !defined(S2_INTERNAL_MINIZ) -# define S2_INTERNAL_MINIZ 0 -#endif - -#if defined(S2_OS_UNIX) -/************************************************************************ -Massage various defines to try to import specific features which our -local man pages claim we get via such massaging... -************************************************************************/ -# if !defined(_XOPEN_SOURCE) - /** Linux: _XOPEN_SOURCE - >=700 for usleep() - >=500 for lstat(), chdir(), realpath() - */ -# define _XOPEN_SOURCE 700 -# endif -# ifndef _XOPEN_SOURCE_EXTENDED - /* Linux: - lstat() - */ -# define _XOPEN_SOURCE_EXTENDED -# endif -# ifndef _BSD_SOURCE - /* Linux: _BSD_SOURCE: - chdir() (glibc <= 2.19) - realpath() (glibc <= 2.19) - */ -# define _BSD_SOURCE -# endif -# ifndef _DEFAULT_SOURCE - /* Linux: _DEFAULT_SOURCE: - realpath() (glibc >= 2.19) - >= 200112L for lstat() (glibc 2.20+) - */ -# define _DEFAULT_SOURCE -# endif -# if !defined(_POSIX_C_SOURCE) - /* Linux: _POSIX_C_SOURCE: - >= 200112L for lstat() (glibc 2.10+) - >= 200809L for chdir() - */ -# define _POSIX_C_SOURCE 200809L /*200112L*/ /*199309L*/ -# endif -#endif /* S2_OS_UNIX */ - - -#if !defined(S2_HAVE_SIGACTION) -# if defined(S2_AMALGAMATION_BUILD) -# if defined(S2_OS_UNIX) -# define S2_HAVE_SIGACTION 1 -# else -# define S2_HAVE_SIGACTION 0 -# endif -# else -# define S2_HAVE_SIGACTION 1 /* @ HAVE_SIGACTION@ */ -# endif -#endif - - -#if defined(S2_OS_WINDOWS) -# define S2_DIRECTORY_SEPARATOR "\\" -#else -# define S2_DIRECTORY_SEPARATOR "/" -#endif - -#endif /* WANDERINGHORSE_NET_CWAL_S2_CONFIG_H_INCLUDED */ -/* end of file s2_config.h */ -/* start of file t10n.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -#ifndef NET_WANDERINGHORSE_CWAL_S2_T10N_H_INCLUDED_ -#define NET_WANDERINGHORSE_CWAL_S2_T10N_H_INCLUDED_ - -#ifdef __cplusplus -extern "C" { -#endif -typedef struct s2_ptoker s2_ptoker; -typedef struct s2_ptoken s2_ptoken; - -/** - Represents a token ID in the s2_ptoken/s2_cptoken APIs. Values - above 0 always represent valid IDs and most contexts treat 0 as EOF - (often a "virtual" EOF for a block construct). It's tempting to use - a 16-bit type for this, but the s2 amalgamated unit tests (as of - 20200106) use up about half of that range (2/3rds if we retain - "junk" tokens), so it's easy to conceive of overflowing that. - - It's also somewhat tempting to use 18 bits (262144) for the ID and - the remaining 14 (16348) for the token type. -*/ -typedef uint32_t s2_token_id; - -/** - A sentinel s2_token_id meaning "no ID." This "really should" be - an extern const s2_token_id, but we then cannot use it in struct - initializers :/. Its value MUST be 0. -*/ -#define s2_token_id_none (0) - -/** - Numeric type used for counting script line and column numbers. - Note that we aim for a 16-bit type to shave a few bytes from - oft-used token types. As of this writing (20200105), the single - largest s2 script (its amalgamated unit tests) is right at 5400 lines - (size=160kb), so any dreams of scripts with more than 64k lines - would seem to be... ah... somewhat ambitious. -*/ -typedef uint16_t s2_linecol_t; - -/** - s2 token type and operator IDs. - - Values >=0 and <=127 can be translated literally to their - equivalent char value. Values over 127 are symbolic, not - necessarily mapping to a single byte nor a Unicode code point with - the same value. (There are very likely numerous collisions with - Unicode code points in this enum.) - - @see s2_ttype_cstr() -*/ -enum s2_token_types { -/** - Used as the token type by s2_ptoker_next_token() when a - tokenization-level error is encountered. -*/ -S2_T_TokErr = -2, - -/** - The generic EOF marker. Used by s2_ptoker_next_token() when the - end of the tokenizer's input range is reached. Note that this - token is also used for "virtual" EOF and does NOT necessarily map - to a NUL byte in the input. e.g. when sub-parsing part of a - larger expression, the subexpression will get a subset of the - parent range to parse, and its virtual EOF will be part of its - parent parser's input range. -*/ -S2_T_EOF = -1, - -/** - S2_T_INVALID is guaranteed by the API to be the entry in this - enum with the value 0, whereas the concrete values for other - non-ASCII-range tokens is unspecified except that they are - guaranteed to be non-0. -*/ -S2_T_INVALID = 0, - -S2_T_Tab = 9, -S2_T_NL = 10, -S2_T_VTab = 11, -S2_T_FF = 12, -S2_T_CR = 13, -S2_T_At = 64 /* \@ */, -/** - Generic EOL token, for \\r, \\n, and \\r\\n. - - Whether or not newlines end an expression is (or should be) - context-dependent, and may depend on what token(s) lie(s) - before it in the parsing process. -*/ -S2_T_EOL = 213, -/** ASCII 32d, but runs of spaces are translated to - S2_T_Blank. */ -S2_T_Space = 32 /* ' ' */, -/** Generic token for runs of s2_is_blank() characters. */ -S2_T_Blank = 132, -S2_T_Whitespace = 232, -S2_T_UTFBOM = 332 /* UTF byte-order marker (0xEF 0xBB 0xBF) */, - -S2_T_OpNot = 33 /* ! */, - -S2_T_OpHash = 35 /* # */, -S2_T_Shebang = 135 /* #!... */, - -S2_T_OpModulo = 37 /* % */, -S2_T_OpModuloAssign = 237 /* %= */, -S2_T_OpModuloAssign3 = 337 /* X.Y %= Z*/, - -S2_T_OpAndBitwise = 38 /* & */, -S2_T_OpAnd = 238 /* && */, -S2_T_OpAndAssign = 338 /* &= */, -S2_T_OpAndAssign3 = 438 /* X.Y &= Z */, - -S2_T_ParenOpen = 40 /* ( */, -S2_T_ParenGroup = 140 /* (...) */, -S2_T_ParenClose = 41 /* ) */, - -S2_T_OpMultiply = 42 /* * */, -S2_T_OpMultiplyAssign = 242 /* *= */, -S2_T_OpMultiplyAssign3 = 342 /* X.Y*=Z */, - -S2_T_OpPlus = 43 /* + */, -S2_T_OpPlusUnary = 243 /* + */, -S2_T_OpPlusAssign = 343 /* += */, -S2_T_OpPlusAssign3 = 443 /* X.Y+=Z */, -S2_T_OpIncr = 543 /* ++ */, -S2_T_OpIncrPre = 643 /* ++ */, -S2_T_OpIncrPost = 843 /* ++ */, - -S2_T_Comma = 44 /* , */, -S2_T_RHSEval = 144 /* internal-use-only pseudo-operator */, - -S2_T_OpMinus = 45 /* - */, -S2_T_OpMinusUnary = 245 /* - */, -S2_T_OpMinusAssign = 345 /* -= */, -S2_T_OpMinusAssign3 = 445 /* X.Y-=y */, -S2_T_OpDecr = 545 /* -- */, -S2_T_OpDecrPre = 645 /* -- */, -S2_T_OpDecrPost = 745 /* -- */, - -S2_T_OpDot = 46 /* . */, -S2_T_OpArrow = 146 /* -> */, -S2_T_OpArrow2 = 246 /* => */, -S2_T_OpDotDot = 346 /* .. */, -S2_T_OpDotLength = 446 /* .# */, - -S2_T_OpDivide = 47 /* X/Y */, -S2_T_OpDivideAssign = 147 /* X/=Y */, -S2_T_OpDivideAssign3 = 247 /* X.Y/=Z */, - -S2_T_Colon = 58 /* : */, -S2_T_Colon2 = 258 /* :: */, -S2_T_OpColon2 = S2_T_Colon2, -S2_T_OpColonEqual = 358 /* := */, - -S2_T_Semicolon = 59 /* ; */, -/** Generic end-of-expression token. */ -S2_T_EOX = 159, - -S2_T_CmpLT = 60 /* < */, -S2_T_CmpLE = 260 /* <= */, -S2_T_OpShiftLeft = 360 /* << */, -S2_T_OpShiftLeftAssign = 460 /* <<= */, -S2_T_OpShiftLeftAssign3 = 560 /* X.Y<<=Z */, -S2_T_HeredocStart = 660 /* <<< */, -S2_T_Heredoc = 670 /* A "fully slurped" heredoc. Prior to 2020-08-27, - heredocs here a special cse of S2_T_SquigglyBlock - for historical reasons. */, - -S2_T_OpAssign = 61 /* = */, -S2_T_OpAssign3 = 161 /* = */, -S2_T_CmpEq = 261 /* == */, -S2_T_CmpNotEq = 361 /* != */, -S2_T_CmpEqStrict = 461 /* === */, -S2_T_CmpNotEqStrict = 561 /* !== */, -S2_T_OpInherits = 661 /* inherits */, -S2_T_OpNotInherits = 761 /* !inherits */, -S2_T_OpContains = 861 /* =~ */, -S2_T_OpNotContains = 961 /* !~ */, -S2_T_OpAssignConst3 = 1061 /* X.Y:=Z */, - -S2_T_CmpGT = 62 /* > */, -S2_T_CmpGE = 262 /* >= */, -S2_T_OpShiftRight = 362 /* >> */, -S2_T_OpShiftRightAssign = 462 /* >>= */, -S2_T_OpShiftRightAssign3 = 562 /* X.Y>>=Z */, - -S2_T_Question = 63 /* ? */, -S2_T_QDot = 163 /* ?. reserved for potential future use */, - -S2_T_BraceOpen = 91 /* [ */, -S2_T_BraceGroup = 191 /* [...] */, -S2_T_Backslash = 92 /* \\ */, -S2_T_BraceClose = 93 /* ] */, - -S2_T_OpXOr = 94 /* ^ */, -S2_T_OpXOrAssign = 294 /* ^= */, -S2_T_OpXOrAssign3 = 394 /* X.Y^=Z */, - -S2_T_SquigglyOpen = 123 /* { */, -S2_T_SquigglyBlock = 223 /* {...} */, - -S2_T_OpOrBitwise = 124 /* | */, -S2_T_OpOr = 224 /* || */, -S2_T_OpOr3 = 324 /* ||| */, -S2_T_OpElvis = 424 /* ?: */, -S2_T_OpOrAssign = 524 /* |= */, -S2_T_OpOrAssign3 = 624 /* X.Y|=Z */, -S2_T_SquigglyClose = 125 /* } */, -S2_T_OpNegateBitwise = 126 /* ~ */, - - -S2_T_Literal__ = 1000, -S2_T_LiteralInt, -S2_T_LiteralIntDec, -S2_T_LiteralIntHex, -S2_T_LiteralIntOct, -S2_T_LiteralIntBin, -S2_T_LiteralDouble, -S2_T_LiteralStringDQ, -S2_T_LiteralStringSQ, -S2_T_LiteralString /* for "untranslated" strings */, -S2_T_PropertyKey /* special case of LiteralString */, -S2_T_Identifier, - - -S2_T_ValueTypes__ = 2000, -S2_T_Value, -S2_T_Undefined, -S2_T_Null, -S2_T_False, -S2_T_True, -S2_T_Object, -S2_T_Array, -S2_T_Function, - -S2_T_Keyword__ = 3000, -S2_T_KeywordAffirm, -S2_T_KeywordAssert, -S2_T_KeywordBREAKPOINT, -S2_T_KeywordBreak, -S2_T_KeywordCOLUMN, -S2_T_KeywordCatch, -S2_T_KeywordClass, -S2_T_KeywordConst, -S2_T_KeywordContinue, -S2_T_KeywordDefine, -S2_T_KeywordDefined, -S2_T_KeywordDelete, -S2_T_KeywordDo, -S2_T_KeywordEcho, -S2_T_KeywordEnum, -S2_T_KeywordEval, -S2_T_KeywordException, -S2_T_KeywordExit, -S2_T_KeywordFILE, -S2_T_KeywordFILEDIR, -S2_T_KeywordFalse, -S2_T_KeywordFatal, -S2_T_KeywordFor, -S2_T_KeywordForEach, -S2_T_KeywordFunction, -S2_T_KeywordIf, -S2_T_KeywordImport, -S2_T_KeywordInclude, -S2_T_KeywordInterface, -S2_T_KeywordIs, -S2_T_KeywordIsA, -S2_T_KeywordLINE, -S2_T_KeywordNameof, -S2_T_KeywordNew, -S2_T_KeywordNull, -S2_T_KeywordPragma, -S2_T_KeywordPrivate, -S2_T_KeywordProc, -S2_T_KeywordProtected, -S2_T_KeywordPublic, -S2_T_KeywordRefcount, -S2_T_KeywordReturn, -S2_T_KeywordS2Out, -S2_T_KeywordSRCPOS, -S2_T_KeywordScope, -S2_T_KeywordStatic, -S2_T_KeywordThrow, -S2_T_KeywordTrue, -S2_T_KeywordTry, -S2_T_KeywordTypeinfo, -S2_T_KeywordTypename, -S2_T_KeywordUndefined, -S2_T_KeywordUnset, -S2_T_KeywordUKWD, -S2_T_KeywordUsing /* using() function-like keyword, as opposed to - function() using(...) {} */, -S2_T_KeywordVar, -S2_T_KeywordWhile, - -S2_T_Comment__ = 4000, -S2_T_CommentC, -S2_T_CommentCpp, - -S2_T_Mark__ = 5000, -S2_T_MarkVariadicStart, - -S2_T_Misc__ = 6000, -/** - A pseudo-token used internally to translate empty [] blocks to a - PHP-style array-append operation. - - The parser current only allows this op in the context of an assignment -*/ -S2_T_ArrayAppend, -S2_T_Foo, - -S2_T_comma_kludge_ -}; - - -#if 0 -typedef struct s2_byte_range s2_byte_range; -/** - Holds a pair of pointers indicating a range - to an abstract string data source. -*/ -struct s2_byte_range { - /** - The starting position of source. - */ - char const * begin; - /** - One-past-the-end position. - */ - char const * end; -}; -#define s2_byte_range_empty_m {0,0} -extern const s2_byte_range s2_byte_range_empty; -#endif - -/** - A "parser token" - tokens used by the s2 tokenization and - evaluation process. -*/ -struct s2_ptoken{ - /** - If set to non-0, this token is proxying an s2_cptoken with this - ID. This is part of the experimental framework for adding - optional support for "compiled" tokens to s2's eval engine. - */ - s2_token_id id; - - /** - A s2_token_types value. - */ - int16_t ttype; - - /** - 1-based line number relative to the s2_ptoker which - sets this via s2_ptoker_next_token(). - - It turns out that we can't reliably count this unless (slightly - over-simplified) the tokenizer moves only forward. Once - downstream code starts manipulating s2_ptoken::begin and - s2_ptoken::end, the counting gets messed up (and we have lots - of cases which do that). - */ - s2_linecol_t line; - - /** - 0-based column number relative to the s2_ptoker which - sets this via s2_ptoker_next_token(). - */ - s2_linecol_t column; - - /** - The starting point of the token, relative to its containing - script. Invalid tokens have a NULL begin value. - - 202008: direct access to this is being phased out in favor of an - ongoing abstraction. Use s2_ptoker_begin() to access it. - */ - char const * _begin; - - /** - The one-after-the-end point for the token. When tokenizing - iteratively, each next token starts at the end position of the - previous token. - - 202008: direct access to this is being phased out in favor of an - ongoing abstraction. Use s2_ptoker_end() to access it. - */ - char const * _end; - - /** - Some token types "trim" their bytes to some subset of [begin, - end). For such token types, the range [adjBegin, adjEnd) should - be used for fetching their "inner" bytes, while [begin, end) - will hold the full token bytes. - - Currently the types for which this is done include: - - S2_T_SquigglyBlock, S2_T_Heredoc, S2_T_BraceGroup, - S2_T_ParenGroup. - - 202008: direct access to this is being phased out in favor of an - ongoing abstraction. Use s2_ptoker_adjbegin() to access it. - */ - char const * _adjBegin; - - /** - The one-after-the-end counterpart of adjBegin. - - 202008: direct access to this is being phased out in favor of an - ongoing abstraction. Use s2_ptoker_adjend() to access it. - */ - char const * _adjEnd; -}; - -/** - Empty-initialized s2_ptoken structure, intended for - const-copy initialization. -*/ -#define s2_ptoken_empty_m {0,S2_T_INVALID,0,0,0,0,0,0,} - -/** - Empty-initialized s2_ptoken structure, intended for - copy initialization. -*/ -extern const s2_ptoken s2_ptoken_empty; - -/** - An internal implementation detail to speed up line-counting - (something s2 has to do quite often). -*/ -struct s2_ptoker_lccache_entry { - char const * pos; - int line; - int col; -}; -typedef struct s2_ptoker_lccache_entry s2_ptoker_lccache_entry; -/** - Empty-initialized s2_ptoker_lccache_entry structure, intended for - const-copy initialization. -*/ -#define s2_ptoker_lccache_entry_empty_m {0,0,0} -/** - An internal implementation detail to speed up line-counting - (something s2 has to do inordinately often). -*/ -struct s2_ptoker_lccache { - /** Current position in this->lines. */ - volatile int cursor; - /** "Size" of each cache slot, based on the size of the - tokenizer's input range. */ - int slotSize; - /** Cache of line-counting position results. */ - volatile s2_ptoker_lccache_entry lines[ - 10 - /* reminders to self: 10 or 20 actually, in the 20191228 - amalgamated s2 unit tests, perform slightly better than 60 - does. Multiple tests show 10 to be an all-around good - value. */ - ]; -}; -typedef struct s2_ptoker_lccache s2_ptoker_lccache; -/** - Empty-initialized s2_ptoker_lccache structure, intended for - const-copy initialization. -*/ -#define s2_ptoker_lccache_empty_m \ - {0,0,{ \ - s2_ptoker_lccache_entry_empty_m, s2_ptoker_lccache_entry_empty_m, \ - s2_ptoker_lccache_entry_empty_m, s2_ptoker_lccache_entry_empty_m, \ - s2_ptoker_lccache_entry_empty_m, s2_ptoker_lccache_entry_empty_m, \ - s2_ptoker_lccache_entry_empty_m, s2_ptoker_lccache_entry_empty_m, \ - s2_ptoker_lccache_entry_empty_m, s2_ptoker_lccache_entry_empty_m \ - }} - -/** - The s2_ptoker class is a simple basis for a tokenizer, largely - syntax- and language-independent. Its origins go back many years - and several projects. - - This tokenizer requires that all input be available in advance of - tokenization and remain valid for its own lifetime. - - @see s2_ptoker_init() - @see s2_ptoker_next_token() - @see s2_ptoker_lookahead() - @see s2_ptoker_putback() - @see s2_ptoker_next_token_set() -*/ -struct s2_ptoker { - /** - Used for memory management in "v2" operations. Will be NULL for - "historical-style" instances. - - We might want to change this to an s2_engine, but then we'd have - a circular dependency, and those make me lose sleep. - */ - cwal_engine * e; - - /** - Starting position of input. - - The full input range is [begin, end). - */ - char const * begin; - - /** - One-past-the-end position of the input (i.e. the position - where the NUL byte normally is). - */ - char const * end; - - /** - Error string (static memory) from tokenization - errors. Set by s2_ptoker_next_token(). - */ - char const * errMsg; - - /** - NUL-terminated string used for error reporting. May be a file - name or a descriptive name like "eval script". The bytes are - owned by "someone else" - not this object - and must outlive this - object. - */ - char const * name; - - /** - Used for calculating line/col info for sub-parsing errors. - */ - s2_ptoker const * parent; - - /** - For ongoing token compilation experimentation. If this->parent is - non-NULL then this pointer is assumed to belong to the top-most - parent in the chain, else it is assumed to be owned by this - instance. - */ - void * compiled; - - /** - Used for capturing line/column offset info for "distant child" - tokenizers, which "know" they derive from another but have no - access to it (it may be long gone). - */ - s2_linecol_t lineOffset; - - /** - Column counterpart of lineOffset. - */ - s2_linecol_t colOffset; - - /** - 1-based current tokenization line number. - - This is maintained by s2_ptoker_next_token(), updated - as it crosses newline boundaries. - - This can only be tracked properly as long as - s2_ptoker_next_token() is called linearly. Once clients (e.g. s2 - internals) starts jumping around the input, this counting breaks. - */ - s2_linecol_t currentLine; - /** - 0-based current tokenization column number. - - This is maintained by s2_ptoker_next_token(). See - notes in this->currentLine. - */ - s2_linecol_t currentCol; - - /** - Flags which may change how this object tokenizes. - */ - cwal_flags32_t flags; - - /** - The current token. Its state is set up thusly: - - Initially, token.begin must be this->begin and token.end - must be 0. That state is used by s2_ptoker_next_token() to - recognize the initial token state and DTRT. - - During tokenization, this object's state is updated to reflect - the range from [this->begin, this->end) matching a token (or - an error position, in the case of a tokenization error). - */ - s2_ptoken token; - - /** - The put-back token. s2_ptoker_next_token() copies this->token to - this object before attempting any tokenization. - s2_ptoker_putback() copies _pbToken over this->token and clears - _pbToken. - - Do not manipulate this directly. use s2_ptoker_putback(), - s2_ptoker_putback_get(), and (if really needed) - s2_ptoker_putback_set(). - */ - s2_ptoken _pbToken; - - /** - An experiment in cutting down on tokenization. Token lookahead - ops, and some client code, set this after they have done a - lookahead, conditionally setting this to that looked-ahead - token. s2_ptoker_next_token() will, if this is set, use this - token and skip the tokenization phase. This member is cleared by - s2_ptoker_token_set() and s2_ptoker_next_token(). - - Do not manipulate this directly: use s2_ptoker_next_token_set() - to modify it. - */ - s2_ptoken _nextToken; - - /** - Used for marking an error position, which is part of the line/col - counting mechanism used for error reporting. - - Do not manipulate this directly. Use s2_ptoker_errtoken_get(), - s2_ptoker_errtoken_set(), and s2_ptoker_errtoken_has(). - */ - s2_ptoken _errToken; - - /** - These tokens are used to capture a range of tokens solely for - their string content. _Some_ APIs set this to a range encompasing - all input which they consume. e.g. it can be used to record the - whole result of multiple s2_ptoker_next_token() calls by setting - capture.begin to the start of the first token and capture.end the - end of the last token captured. The s2_t10n-internal APIs do not - manipulate this member. - - We model capturing as a token range, rather than a simple byte - range, with the hopes that this approach will work with the - eventual addition of compiled tokens. - */ - struct { - /** First token in the capture range. */ - s2_ptoken begin; - /** - The one-after-the-end token in the capture range. Note that - this means that the string range of the capture is - [this->begin.begin, this->end.begin). For completely empty - ranges this token will be the same as this->begin. - */ - s2_ptoken end; - } capture; - - - /** - Internal implementation detail for the line-counting results - cache. Full disclosure: in order to avoid having to modify the - signatures of a metric boatload of functions to make their - (s2_ptoker const *) parameters non-const, this member may be - modified in/via one routine where this s2_ptoker instance is - otherwise const. Const-correct behaviour would require that we - make a whole family of downstream functions non-const just to - account for this internal optimization, which i'm not willing to - do (as much as i love my const). This is where we could use the - "mutable" keyword in C++, but, alas, this is C89. - */ - s2_ptoker_lccache _lcCache; -}; -/** Empty-initialized s2_ptoker object. */ -#define s2_ptoker_empty_m { \ - 0/*e*/, 0/*begin*/,0/*end*/, \ - 0/*errMsg*/, \ - 0/*name*/, \ - 0/*parent*/, \ - 0/*compiled*/, \ - 0/*lineOffset*/,0/*colOffset*/, \ - 1/*currentLine*/,0/*currentCol*/,0/*flags*/, \ - s2_ptoken_empty_m/*token*/, \ - s2_ptoken_empty_m/*_pbToken*/, \ - s2_ptoken_empty_m/*_nextToken*/, \ - s2_ptoken_empty_m/*_errToken*/, \ - {s2_ptoken_empty_m,s2_ptoken_empty_m}/*capture*/, \ - s2_ptoker_lccache_empty_m/*_lcCache*/ \ - } -/** Empty-initialized s2_ptoker object. */ -extern const s2_ptoker s2_ptoker_empty; - -/** - Flags for use with s2_ptoker::flags and possibly - related contexts. -*/ -enum s2_t10n_flags { -/** - Sentinel value. Must be 0. -*/ -S2_T10N_F_NONE = 0, -/** - If set, the '-' character is considered a legal identifier by - s2_read_identifier2() except when the '-' appears at the start of - the input. -*/ -S2_T10N_F_IDENTIFIER_DASHES = 1, - -/** - An internal-only flag which signifies to error-handling routines - that the given s2_ptoker is only ever used in a strictly linear - fashion, allowing such routines to take advantage of line/column - state of the tokenizer and avoid counting lines for error - reporting. Note that the overwhelming majority of s2_ptokers in s2 - are NOT purely linear and it's normally impossible to know in - advance whether one will be or not. In such cases it will use the - position of either its error token or current token, in that order. - - Reminder to self: this was added to support s2_cptoker - experimentation, as s2_cptoker internally uses s2_ptoker and - "compilation" but tokenizes all the input before "compiling" it. - This flag allows errors in that step to be reported without an - extra line-counting step. -*/ -S2_T10N_F_LINEAR_TOKER = 0x10 -}; - -/** - Must be passed a s2_ptoker and its input source. If len - is negative then the equivalent of strlen() is used to calculate - its length. - - Returns 0 on success, CWAL_RC_MISUSE if !t or !src. It has no other - error contditions, so "cannot fail" if its arguments are valid. - - Use s2_ptoker_next_token() to fetch the next token, s2_ptoker_lookahead() - to "peek" at the next token, and s2_ptoker_putback() to put a just-fetched - token back. - - An s2_ptoker instance initialized via this interface does not - strictly need to be passed to s2_ptoker_finalize(), but there is no - harm in doing so. -*/ -int s2_ptoker_init( s2_ptoker * t, char const * src, cwal_int_t len ); - -/** - INCOMPLETE - DO NOT USE. - - Must be passed a s2_ptoker and its input source. If len is negative - then the equivalent of strlen() is used to calculate its - length. This routine, unlike s2_ptoker_init(), may allocate memory - and may pre-parse its input, and can therefore fail in a number of - ways. Thus its result code must always be checked. - - Calling this obligates the caller to eventually pass t to - s2_ptoker_finalize(), regardless of whether this routine succeeds - or not, to free up any resources this routine may have allocated. - - Returns 0 on success, CWAL_RC_MISUSE if !t or !src. - - Use s2_ptoker_next_token() to fetch the next token, s2_ptoker_lookahead() - to "peek" at the next token, and s2_ptoker_putback() to put a just-fetched - token back. -*/ -int s2_ptoker_init_v2( cwal_engine * e, s2_ptoker * t, char const * src, cwal_int_t len, - uint32_t flagsCurrentlyUnused ); - -/** - Resets the tokenization state so that the next call to - s2_ptoker_next_token() will start at the beginning of the - input. This clears the putback and error token state, and between - this call and the next call to s2_ptoker_next_token() (or similar), - the current token state is invalid. -*/ -void s2_ptoker_reset( s2_ptoker * t ); - -/** - Clears t's state and frees any memory is may be using. This does - not free t, but will free any memory allocated on its behalf. - - Note that if any sub-tokenizers which derive from t (i.e. have t - set as their s2_ptoker::parent value), they MUST be cleaned up - first, as they may refer to memory owned by a parent. - - It is safe to pass this function an instance which was not passed - to s2_ptoker_init() or s2_ptoker_init_v2() if (and only if) the - instance was copy-initialized from s2_ptoker_empty or - s2_ptoker_empty_m. -*/ -void s2_ptoker_finalize( s2_ptoker * t ); - -/** - Initializes t as a sub-tokenizer of parent, using parent->token - as t's input range. - - Returns CWAL_RC_RANGE if parent->token does not have a valid - byte range. - - Returns 0 on success. - - On success, [t->begin, t->end) point to the sub-tokenization range - and t->parent points to parent. - - Results are undefined if either argument is NULL or points to - uninitialized memory. - - If this function succeeds, the caller is obligated to eventually - pass t to s2_ptoker_finalize(). If it fails, t will be finalized if - needed. If this routine fails, passing t to s2_ptoker_finalize() is - a harmless no-op if t was initially copy-initialized from - s2_ptoker_empty or s2_ptoker_empty_m (i.e. it's in a well-defined - state). -*/ -int s2_ptoker_sub_from_toker( s2_ptoker const * parent, - s2_ptoker * dest); - -/** - Initializes sub as a sub-tokenizer of the given parent - tokenizer/token combintation, using the given token as sub's input - range. - - Returns CWAL_RC_RANGE if parent does not have a valid byte range. - - Returns 0 on success. - - On success, [t->begin, t->end) point to the sub-tokenization range - and t->parent points to parent. - - Results are undefined if either argument is NULL or points to - uninitialized memory. -*/ -int s2_ptoker_sub_from_token( s2_ptoker const * parent, s2_ptoken const * token, - s2_ptoker * sub ); - -/* s2_ptoker const * s2_ptoker_root( s2_ptoker const * t ); */ - -/** - Returns the top-most object from t->parent, or t if !t->parent. -*/ -s2_ptoker const * s2_ptoker_top_parent( s2_ptoker const * t ); - -/** - Returns either t->name or the first name encountered while climbing - up the t->parent chain. Returns 0 if no name is found. If len is - not 0 then if this function returns non-0, len is set to that - name's length. -*/ -char const * s2_ptoker_name_first( s2_ptoker const * t, cwal_size_t * len ); -/** - Returns the top-most name from t and its parent chain. - Returns 0 if no name is found. - - FIXME: add (cwal_size_t * len) parameter. -*/ -char const * s2_ptoker_name_top( s2_ptoker const * t ); - -/** - Returns the first explicit non-0 error position marker from t or - its nearest ancestor (via t->parent). If the 2nd argument is not - NULL, *foundIn is assigned to the tokenizer in which the error - position was found. - - @see s2_ptoker_err_pos() -*/ -char const * s2_ptoker_err_pos_first( s2_ptoker const * t, - s2_ptoker const ** foundIn); - -/** - Tries to return an "error position" for the given tokenizer, under - the assumption that something has just "gone wrong" and the client - wants to know where (or whereabouts) it went wrong. It first tries - s2_ptoker_err_pos_first(). If that returns NULL, it returns either - the position of pt's current token or putback token. If those are - NULL, pt->begin is returned (indicating that tokenization has not - yet started or failed on the first token). - - @see s2_ptoker_err_pos_first() -*/ -char const * s2_ptoker_err_pos( s2_ptoker const * pt ); - -/** - Fetches the next token from t. t must have been successfully - intialized using s2_ptoker_init(). - - This function is intended to be called repeatedly until it either - returns 0 (success) AND has (t->ttype==S2_T_EOF) or until it - returns non-0 (error). On each iteration, clients should collect - any of the token information they need from t->token before calling - this again (which changes t's state). - - Note that this is a lower-level function than s2_next_token(). - That one builds off of this one. - - On success 0 is returned and t is updated as follows: - - t->token.begin points to the start of the token. t->token.end - points to the one-past-the-end character of the token, so the - length of the token is (t->token.end - t->token.begin). t->ttype - will be set to one of the S2_T_xxx constants. - - At the end of input, t->ttype will be S2_T_EOF and the token - length with be 0 (t->begin==t->end). - - On error non-0 is returned, t->ttype will be S2_T_TokErr, and - t->errMsg will contain a basic description of the error. On success - t->errMsg will be 0, so clients may use that to check for errors - instead of checking the result code or token type S2_T_TokErr. The - bytes in t->errMsg are guaranteed to be static. On error - t->token.begin will point to the starting position of the erroneous - or unrecognized token. - - The underlying tokenizer is fairly grammar-agnostic but tokenizes - many constructs as they exist in C-like languages, e.g. ++ is a - single token (as opposed to two + tokens), and >>= is also a single - token. A 1-byte character in the range (1,127), which is not - otherwise already tagged with a type, gets its character's ASCII - value set as its t->ttype. - - This function saves the pre-call current token state to a putback - token, and the token can be "put back" by calling s2_ptoker_putback(). - - Use s2_ptoker_lookahead() to "peek" at the next token while keeping - the token iterator in place. - - Any tokenization error is assumed to be unrecoverable, and it is - not normally useful to call this again (after an error) without - re-initializing the tokenizer first. -*/ -int s2_ptoker_next_token( s2_ptoker * t ); - -/** - Sets pt's next token to be a bitwise copy of tk, such that the next - call to s2_ptoker_next_token() will evaluate to that token. It is - up to the caller to ensure that tk is valid for pt (e.g. by having - read tk via a call to s2_ptoker_lookahead()). - - This is an optimization useful when one has looked-ahead in pt and - determined that the looked-ahead token should be the next-consumed - token. Using s2_ptoker_putback() would also be an option, but this - approach is more efficient because it eliminates the - re-tokenization of the put-back token. - - Note that this does not modify the putback token. If - s2_ptoker_next_token() is called immediately after this, the - putback token will be set to whichever token was most recently - consumed before *this* routine was called (i.e. exactly the same as - would happen without this routine having been called). - - @see s2_ptoker_next_token() -*/ -void s2_ptoker_next_token_set( s2_ptoker * const pt, s2_ptoken const * const tk ); - -/** - For a given s2_token_types values, this returns a unique - string representation of its type ID. The returned bytes - are static. Returns 0 for an unknown value. - - This function serves two main purposes: - - 1) So that we can let gcc warn us if any values in s2_token_types - collide with one another. (Implementing it caught two collisions.) - - 2) Help with debugging. The strings returned from this function are - intended for "informational" or "entertainment" value only, not - (de)serialization. -*/ -char const * s2_ttype_cstr( int ttype ); - -/** - Similar to s2_ptoker_next_token(), but it skips over any tokens for - which s2_ttype_is_junk() returns true. On returning, st->token - holds the last-tokenized position. - - After this call, the putback token will be the previous token - read before this call. i.e. the intervening junk tokens are not - placed into the putback token. -*/ -int s2_ptoker_next_token_skip_junk( s2_ptoker * st ); - -/** - If st has no putback token, 0 (false) is returned and this function - has no side-effects, otherwise st->token is replaced by the putback - token, the putback token is cleared, and non-0 (true) is returned. - - Note that s2_ptoker_next_token_set() is often a more efficient - option, provided semantics allow for it (which they don't always - do). After calling this routine, s2_ptoker_next_token() must - re-tokenize the next token, whereas s2_ptoker_next_token_set() - allows s2_ptoker_next_token() to bypass that tokenization step. -*/ -char s2_ptoker_putback( s2_ptoker * st ); - -/** - Sets a bitwise copy of tok as the current putback token. It is up - to the caller to ensure that tok is valid for the given tokenizer's - current state. -*/ -void s2_ptoker_putback_set( s2_ptoker * const st, s2_ptoken const * const tok ); - -/** - Returns st's current putback token, which should be bitwise - copied by the caller because its state may change on any calls - to the s2_ptoker API. -*/ -s2_ptoken const * s2_ptoker_putback_get( s2_ptoker const * const st ); - -/** - Returns st's current error token. It never returns NULL - the - memory is owned by st and may be modified by any future calls into - its API. The token will have a ttype of S2_T_INVALID, and no - begin/end values, if there is no error. -*/ -s2_ptoken const * s2_ptoker_errtoken_get( s2_ptoker const * st ); - -/** - Bitwise copies tok to be st's current error token. If tok is NULL, - the error token is cleared, else it is up to the caller to ensure - that the token is valid for st's current state. - - Note that setting an error token does not trigger an error - it is - intended only to mark the part of the tokenizer which might be - associated with an error, for downstream error reporting of that - position. -*/ -void s2_ptoker_errtoken_set( s2_ptoker * const st, s2_ptoken const * const tok ); - -/** - Returns non-0 if st has an error token set which points to somewhere - in st's range, else returns 0. -*/ -char s2_ptoker_errtoken_has( s2_ptoker const * st ); - -/** - Uses s2_ptoker_next_token() to fetch the next token, sets *tgt to the - state of that token, resets the tokenizer position to its - pre-call state, and returns the result of the s2_ptoker_next_token() - call. After calling this, both the current token position and the - putback token will be as they were before this function was - called, but st->errMsg might contain error details if non-0 is - returned. - - - Pedantic note: the _contents_ of st->token and st->_pbToken will - change during the life of this call, but they will be reverted - before it returned. The point being: don't rely on pointers held - within those two members being stable between before and after - this call, and always reference the addresses directly from - the current state of st->token. - - @see s2_ptoker_lookahead_skip() -*/ -int s2_ptoker_lookahead( s2_ptoker * st, s2_ptoken * tgt ); - - -/** - A function signature for predicates which tell the caller whether - a s2_token_types value meets (or does not meet) a certain - condition. - - Implementations must return ttype if ttype meets their - predicate condition(s), else false (0). Note that S2_T_INVALID - is guaranteed by the API to be 0. -*/ -typedef int (*s2_ttype_predicate_f)( int ttype ); - -/** - Similar to s2_ptoker_lookahead(), but it skips over any leading - tokens for which pred() returns true. On success *tgt contains - the content of the token which either failed the predicate or is - an EOF token. The client can force st to that tokenization - position by passing it to s2_ptoker_token_set(). - - Before this function returns, st->token and st->_pbToken are - restored to their pre-call state and st->_nextToken is cleared. It - is legal for tgt to be st->_nextToken, and that will take precedence - over clearing that value. -*/ -int s2_ptoker_lookahead_skip( s2_ptoker * st, s2_ptoken * tgt, - s2_ttype_predicate_f pred ); - - -/** - Works like s2_ptoker_lookahead_skip(), but inverts the meaning of - the predicate: it stops at the first token for which pred() - returns true. -*/ -int s2_ptoker_lookahead_until( s2_ptoker * st, s2_ptoken * tgt, - s2_ttype_predicate_f pred ); - - -/** - Sets st->_pbToken to st->token, st->token to *t, and clears - st->_nextToken. -*/ -void s2_ptoker_token_set( s2_ptoker * st, s2_ptoken const * t ); - -#if 0 -/** - Returns a pointer to st's current token, the state of which may - change on any any future calls into the s2_ptoker API. Never - returns NULL. - - Though the pointer to this token currently (20200105) never changes - (only its state does), clients are encouraged to behave as if it - might change, as "planned potential changes" to the API may well - make that the case. -*/ -s2_ptoken const * s2_ptoker_token_get( s2_ptoker const * st ); -/** - Returns the token type (ttype) of st's current token. -*/ -int s2_ptoker_ttype( s2_ptoker const * st ); -#endif - -/** - Returns st->token.ttype if st's current token represents an EOF. -*/ -int s2_ptoker_is_eof( s2_ptoker const * st ); - -/** - Returns st->token.ttype if st's current token represents an - end-of-expression. -*/ -int s2_ptoker_is_eox( s2_ptoker const * st ); - -/** - Returns ttype if ttype is an end-of-line token. -*/ -int s2_ttype_is_eol( int ttype ); - -/** - Returns ttype if ttype represents a "space" token - (in any of its various incarnations). -*/ -int s2_ttype_is_space( int ttype ); - -/** - Returns ttype if the given token type is considered a "junk" token - (with no syntactical meaning). - - Junk includes the following token types: - - ASCII 32d (SPACE), ASCII 13d (CR), ASCII 9d (TAB), - S2_T_Blank, S2_T_CommentC, S2_T_CommentCpp, - S2_T_Whitespace, S2_T_Shebang, S2_T_UTFBOM - - Note that S2_T_NL/S2_T_EOL (newline/EOL) is not considered junk - here, as it is an expression separator in some contexts and - skippable in others. - - Potential TODO: treat S2_T_CommentCpp as an EOL because this type - of token implies one. -*/ -int s2_ttype_is_junk( int ttype ); - -/** - Returns ttype if ttype is a basic assignment op: - - S2_T_OpAssign, S2_T_ArrayAppend (internally treated as - assignment). -*/ -int s2_ttype_is_assignment( int ttype ); - -/** - Returns ttype if ttype refers to one of the "combo assignment" - operators, e.g. +=, -=, *=, etc. -*/ -int s2_ttype_is_assignment_combo( int ttype ); - -/** - Returns ttype if op is 0 or represents an operator which - may legally directly proceed a unary operator. -*/ -int s2_ttype_may_precede_unary( int ttype ); - -/** - Returns ttype if ttype represents a symbol that unambiguously marks - the end of an expression: - - S2_T_EOF, S2_T_EOX, S2_T_Semicolon -*/ -int s2_ttype_is_eox( int ttype ); - -/** - Returns ttype if ttype represents an EOF (or virtual EOF) token. -*/ -int s2_ttype_is_eof( int ttype ); - -/** - Returns ttype if ttype presents a "group" type: - - S2_T_ParenGroup, S2_T_BraceGroup, S2_T_SquigglyBlock -*/ -int s2_ttype_is_group( int ttype ); - -/** - Returns ttype if it respends a token whose value can be converted - to a cwal_value with ease. -*/ -int s2_ttype_is_object_keyable( int ttype ); - -/** - Returns ttype if it represents an operator which - is (in principal) capable of short-circuiting part - of its arguments: - - S2_T_OpOr, S2_T_OpAnd, S2_T_Question (in the context of ternary - if). -*/ -int s2_ttype_short_circuits( int ttype ); - -/** - Returns ttype if ttype represents a type which is a unary operator - which requires an *identifier* as its operand: - - S2_T_OpIncr, S2_T_OpDecr, S2_T_OpIncrPre, S2_T_OpIncrPost, - S2_T_OpDecrPre, S2_T_OpDecrPost - - Noting that the pre/post operator name pairs initially represent - the same token (e.g. S2_T_OpIncrPre (++x) vs S2_T_OpIncrPost - (x++)), requiring eval-time context to be able to distinguish - between prefix and postfix forms. - - If it is not, 0 is returned. -*/ -int s2_ttype_is_identifier_prefix( int ttype ); - -/** - Returns ttype if ttype is S2_T_LiteralIntDec or one of its - non-decimal counterparts, else returns 0. -*/ -int s2_ttype_is_int( int ttype ); - -/** - Works like s2_ttype_is_int(), but includes S2_T_LiteralDouble as a - matching type. -*/ -int s2_ttype_is_number( int ttype ); - -/** - If pos is in the range [src,end) then this function calculates - the line (1-based) and column (0-based) of pos within [src,end) - and sets line/col to those values if those pointers are not - NULL. If pos is out of range CWAL_RC_RANGE is returned and - this function has no side-effects. Returns 0 on success. - - Note that s2 globally follows emacs line/column conventions: - lines are 1-based and columns are 0-based. -*/ -int s2_count_lines( char const * src, char const * end_, - char const * pos_, - s2_linecol_t *line, s2_linecol_t *col ); - -/** - Wrapper around s2_count_lines(), which uses [pt->begin, pt->end) - as the source range. -*/ -int s2_ptoker_count_lines( s2_ptoker const * pt, char const * pos, - s2_linecol_t * line, s2_linecol_t * col ); - -/** - Incomplete/untested. Don't use. - - tok must be a token from pt. - - This variant tries to use line/column info embedded in tok before - falling back to counting "the hard way". Note that tok->line and - tok->col are relative to pt->begin, and pt (or one of its parents) - may have line/column offset info to apply to the results, making - the line/col relative to the top-most s2_ptoker in the hierarchy. - - Bug: the tok->line/column counting is known to not work as expected - because of how we hop around while parsing :/. -*/ -int s2_ptoker_count_lines2( s2_ptoker const * pt, - s2_ptoken const * tok, - s2_linecol_t * line, s2_linecol_t * col ); - -#if 0 -/** - Collects info from pt which is useful in error reporting. pos is expected - to be a position within [pt->begin,pt->end). Its line/column position - is calculated as for s2_count_lines() (so *line will be 0 if pos is out - of range). If pos is 0 then pt->errPos is used. - - If name is not NULL, *name is set to the value returned from - s2_ptoker_name_top(). - - Any of the (name, line, col) parameters may be 0. -*/ -void s2_ptoker_err_info( s2_ptoker const * pt, - char const ** name, - char const * pos, - int * line, int * col ); -#endif - - -/** - Unescapes the raw source stream defined by [begin,end) and copies - it to dest (_appending_ to any existing content in dest). Returns 0 - on success. On success, dest->used is set to the length of the - unescaped content plus its old length, not counting the trailing - NUL (but the buffer is NUL-terminated). - - Unescapes the following backslash-escaped characters: '0' (zero), - 'b' (backspace), 't' (tab), 'n' (newline), 'r' (carriage return), - 'f' (form-feed), 'v' (vertical tab), uXXXX (2-byte Unicode - sequences), UXXXXXXXX (4-byte Unicode), backslash (reduces to a - single backslash), single- and double-quotes[1]. - - All other characters (including a NUL byte or a single slash - appearing at the end of the input string) treat a preceding - backslash as if it is a normal character (they retain it). The - reason for this is to avoid that certain client code (e.g. the - poreg (POSIX Regex) module) does not have to double-escape strings - which have to be escaped for underlying C libraries. - - This is safe to use on an empty string (begin==end), in which case - the first byte of the result will be the trailing NUL byte. - - Because this appends its results to dest, the caller may (depending - on how he is using the buffer) need to remember the value of - dest->used before this is called, as that will mark the point at - which this function starts appending data. - - Returns 0 on success, CWAL_RC_MISUSE if passed invalid arguments, - CWAL_RC_RANGE if \\uXXXX and \\UXXXXXXXX are not given 4 resp. 8 hex - digits or if those digits resolve to a non-UTF-8 character. - - [1] = note that this routine does not know which quotes (if any) - wrap up the input string, so it cannot know that only single- _or_ - double-quotes need unescaping. So it unescapes both. -*/ -int s2_unescape_string( cwal_engine * e, - char const * begin, - char const * end, - cwal_buffer * dest ); - -/** - Assumes that zPos is the start of an identifier and reads until the - next non-identifier character. zEnd must be the logical EOF for - zPos. On returning, *zIdEnd is set to the one-after-the-end - position of the read identifier (which will be (*zIdEnd-pos) bytes - long). - - Expects the input to be valid ASCII/UTF-8, else results are - undefined. - - s2 treats ANY UTF-8 character outside the ASCII range as an - identifier character. - - Returns the number of identifier characters read. - - @see s2_read_identifier2() -*/ -int s2_read_identifier( char const * zPos, char const * zEnd, - char const ** zIdEnd ); - -/** - An alternate form of s2_read_identifier() which honors the - S2_T10N_F_IDENTIFIER_DASHES flag if it is set in the final - argument. If that flag is not set it behaves as documented for - s2_read_identifier(). - - @see s2_read_identifier() -*/ -int s2_read_identifier2( char const * zPos, char const * zEnd, - char const ** zIdEnd, - uint32_t flags ); - -/** - Returns true if ch is one of: - - ' ' (ASCII 32d), \\t (ASCII 9d) -*/ -char s2_is_blank( int ch ); - -/** - Returns true if s2_is_blank(ch) is true of if ch is one of: - - \\n (ASCII 10d), \\r (13d), \\v (11d), \\f (12d) -*/ -char s2_is_space( int ch ); - -/** - Returns true if ch is-a digit character (0..9). -*/ -char s2_is_digit( int ch ); - -/** - Returns non-0 if ch is-a hexidecimal digit character (0..9, - a..F, A..F), else returns 0. -*/ -char s2_is_xdigit( int ch ); - -/** - Returns non-0 if character ch is an octal digit character (0..7), - else returns 0. -*/ -char s2_is_octaldigit( int ch ); - -/** - Returns non-0 if ch is an ASCII alphabetic character (a..z, - A..Z), else returns 0. -*/ -char s2_is_alpha( int ch ); - -/** - Returns non-0 if s2_is_alpha(ch) or s2_is_digit(ch), else returns - 0. -*/ -char s2_is_alnum( int ch ); - -/** - Checks whether tok's range contains only "junk" tokens or not. If - tok's range contains only noise tokens, 0 is returned, otherwise - the token type ID of the first non-noise token is returned. Note - that it also returns 0 if there is a tokenization error. This is - primarily used to determine whether "super-tokens" (e.g. - parenthesis/brace groups) contain any usable content before - attempting to evaluate them. -*/ -int s2_ptoken_has_content( s2_ptoken const * tok ); - -/** - If token->begin is not 0 and less than token->end, then (token->end - - token->begin) is returned, else 0 is returned (which is not a - valid token length except for an EOF token). -*/ -#define s2_ptoken_len(TOK) \ - ((cwal_size_t)((s2_ptoken_begin(TOK) \ - && s2_ptoken_end(TOK) > s2_ptoken_begin(TOK)) \ - ? (cwal_size_t)(s2_ptoken_end(TOK) - s2_ptoken_begin(TOK)) \ - : 0)) -/*cwal_size_t s2_ptoken_len( s2_ptoken const * token );*/ - -/** - If the given token has an "adjusted" begin/end range, this function - returns the length of that range, else it behaves identically to - s2_ptoken_len(). - - Typically only group-level tokens and heredocs have an adjusted - range. -*/ -#define s2_ptoken_len2(TOK) \ - ((cwal_size_t)(((s2_ptoken_adjbegin(TOK) && \ - s2_ptoken_adjbegin(TOK)<=s2_ptoken_adjend(TOK)) \ - ? (cwal_size_t)(s2_ptoken_adjend(TOK) - s2_ptoken_adjbegin(TOK)) \ - : s2_ptoken_len(TOK)))) -/*cwal_size_t s2_ptoken_len2( s2_ptoken const * token );*/ - -/** - If t is the result of s2_ptoker_next_token() and is any of the - following types then this sets *rc (if rc is not NULL) to its - integer representation and returns true: S2_T_LiteralIntOct, - S2_T_LiteralIntDec, S2_T_LiteralIntHex, S2_T_LiteralIntBin. Results - are undefined if t's state does not conform to the internal - requirements for one of the above-listed t->ttype values (i.e. t's - state must have been set up by s2_ptoker_next_token() or a relative - of that function). - - Returns false (0) if all conditions are not met or if the - conversion catches a syntax error which the tokenizer did not (but - that "should not happen", and may trigger an assert() in debug - builds). - - ACHTUNG: this does not handle a leading sign, as the sign is, at - this level of the tokenization API, a separate token. - - @see s2_ptoken_parse_double() - @see s2_cstr_parse_double() - @see s2_cstr_parse_int() -*/ -char s2_ptoken_parse_int( s2_ptoken const * t, cwal_int_t * rc ); - -/** - The double counterpart of s2_ptoken_parse_int(), but only parses - tokens with ttype S2_T_LiteralDouble. Results are undefined if - t->ttype is S2_T_LiteralDouble but the byte range referred to by t - is not a valid double token. - - ACHTUNG: this does not handle a leading sign, as the sign is, at - this level of the tokenization API, a separate token. - - @see s2_ptoken_parse_int() - @see s2_cstr_parse_double() - @see s2_cstr_parse_int() -*/ -char s2_ptoken_parse_double( s2_ptoken const * t, cwal_double_t * rc ); - -/** - Works like s2_ptoken_parse_int() except that: - - - slen specifies the input length. If slen <0 then cwal_strlen(str) - is used to calculate the input length. - - - It uses all bytes in the range [str, str+slen) as input and will - fail if the input contains anything other than a numeric value, - optionally with leading space. - - - It accepts a leading sign character, regardless of the integer - notation (decimal, hex, octal). It ignores spaces around the sign - character. - - @see s2_ptoken_parse_double() - @see s2_ptoken_parse_int() - @see s2_cstr_parse_double() -*/ -char s2_cstr_parse_int( char const * str, cwal_int_t slen, cwal_int_t * result ); - -/** - The double counterpart of s2_cstr_parse_int(). - - @see s2_ptoken_parse_double() - @see s2_ptoken_parse_int() - @see s2_cstr_parse_int() -*/ -char s2_cstr_parse_double( char const * str, cwal_int_t slen, - cwal_double_t * result ); - -/** - Expects a filename-like string in the first slen bytes of str, with - directory components separated by either '/' or '\\' (it uses the - first of those it finds, in that order, as the separator). If any - separator is found, a pointer to the last instance of it in str is - returned, otherwise 0 is returned. -*/ -char const * s2_last_path_sep(char const * str, cwal_size_t slen ); - -/** - If tok->ttype is S2_T_Identifier and the token's contents are one - of (true, false, null, undefined) then the corresponding built-in - cwal value is returned (e.g. cwal_value_true() or - cwal_value_null()), else NULL is returned. tok may not be NULL. The - returned value, if not NULL, is guaranteed to be a shared instance - which lives outside of the normal cwal_value lifetime management. -*/ -cwal_value * s2_ptoken_is_tfnu( s2_ptoken const * tok ); - -/** - Returns a pointer to the token's contents, setting its length to - *len if len is not NULL. tok must not be NULL. Note that the - returned string is almost certainly not NUL-terminated (or - terminates at the very end of the s2_ptoker from which tok - originates), thus capturing the length is normally required. - - This does not strip any leading/spaces of tokens which have been - "adjusted" to do so. i.e. the whole token's contents are part of - the returned byte range. -*/ -char const * s2_ptoken_cstr( s2_ptoken const * tok, - cwal_size_t * len ); - -/** - If tok has an "adjusted" begin/end range, this returns a pointer to - the start of that range and len, if not NULL, is assigned the - length of that range. If it has no adjusted range, it functions - identically to s2_ptoken_cstr(). - - Typically only group-level tokens and heredocs have an adjusted - range. -*/ -char const * s2_ptoken_cstr2( s2_ptoken const * tok, - cwal_size_t * len ); - -/** - If st->capture is set up properly, this returns a pointer to the - start of the range and *len (if not NULL) is set to the length of - the captured range. If no capture is set, or it appears to be - invalid, NULL is returned. -*/ -char const * s2_ptoker_capture_cstr( s2_ptoker const * st, - cwal_size_t * len ); - -/** - A helper type for tokenizing conventional PATH-style strings. - Initialize them with s2_path_toker_init() and iterate over them - with s2_path_toker_next(). -*/ -struct s2_path_toker { - /** Begining of the input range. */ - char const * begin; - /** One-after-the-end of the input range. */ - char const * end; - /** Position for the next token lookup. */ - char const * pos; - /** List of token separator characters (ASCII only). */ - char const * separators; -}; -typedef struct s2_path_toker s2_path_toker; -/** - Default-initialized s2_path_toker instance, intended for const-copy - initialization. On Windows builds its separators member is set to - ";" and on other platforms it's set to ":;". -*/ -#if defined(S2_OS_WINDOWS) -# define s2_path_toker_empty_m {NULL,NULL,NULL,";"} -#else -# define s2_path_toker_empty_m {NULL,NULL,NULL,":;"} -#endif - -/** - Default-initialized s2_path_toker instance, intended for - copy initialization. - - @see s2_path_toker_empty_m -*/ -extern const s2_path_toker s2_path_toker_empty; - -/** - Wipes out pt's current state by copying s2_path_toker_empty over it - and initializes pt to use the given path as its input. If len is 0 - or more then it must be the length of the string, in bytes. If len - is less than 0, cwal_strlen() is used to determine the path's - length. (When dealing with inputs which are not NUL-terminated, - it's critical that the user pass the correct non-negative length.) - - If the client wants to modify pt->separators, it must be done so - *after* calling this. - - Use s2_path_toker_next() to iterate over the path entries. -*/ -void s2_path_toker_init( s2_path_toker * pt, char const * path, cwal_int_t len ); - -/** - Given a s2_path_toker which was formerly initialized using - s2_path_toker_init(), this iterates over the next-available path - component in the input, skipping over empty entries (consecutive - separator characters). - - The separator characters are specified by pt->separators, which must - be a NUL-terminated string of 1 or more characters. - - If a non-empty entry is found then: - - - *token is set to the first byte of the entry. - - - *len is assigned to the byte length of the entry. - - If no entry is found then: - - - *token, and *len are not modified. - - - CWAL_RC_NOT_FOUND is returned if the end of the path was found - while tokenizing. - - - CWAL_RC_MISUSE is returned if pt->separators is NULL or empty or - contains any non-ASCII characters. - - - CWAL_RC_RANGE is returned if called after the previous case, or - if the input object's path has a length of 0. - - In any non-0-return case, it's not a fatal error, it's simply - information about why tokenization cannot continue, and can - normally be ignored. After non-0 is returned, the tokenizer must be - re-initialized if it is to be used again. - - Example: - - @code - char const * t = 0; - cwal_size_t tLen = 0; - s2_path_toker pt = s2_path_toker_empty; - s2_path_toker_init(&pt, path, pathLen); - while(0==s2_path_toker_next(&pt, &t, &tLen)){ - // The next element is the tLen bytes of memory starting at t: - printf("Path element: %.*s\n", (int)tLen, t); - } - @endcode -*/ -int s2_path_toker_next( s2_path_toker * pt, char const ** token, cwal_size_t * len ); - -/* - The following macros are part of an ongoing porting/abstraction - effort to eliminate direct access to token members, the eventual - goal being to permit the token type to be easily swapped out so that - we can enable "compiled" (pre-parsed) chains of tokens (which would - require far more memory than s2 currently uses but would be tons - faster for most scripts). -*/ -#define s2_ptoken_begin(P) ((P)->_begin) -#define s2_ptoken_begin2(P) ((P)->_adjBegin ? (P)->_adjBegin : (P)->_begin) -#define s2_ptoken_begin_set(P,X) (P)->_begin = (X) -#define s2_ptoken_end(P) ((P)->_end) -#define s2_ptoken_end2(P) ((P)->_adjEnd ? (P)->_adjEnd : (P)->_end) -#define s2_ptoken_end_set(P,X) (P)->_end = (X) -#define s2_ptoken_adjbegin(P) ((P)->_adjBegin) -#define s2_ptoken_adjbegin_set(P,X) (P)->_adjBegin = (X) -#define s2_ptoken_adjbegin_incr(P,X) (P)->_adjBegin += (X) -#define s2_ptoken_adjend(P) ((P)->_adjEnd) -#define s2_ptoken_adjend_set(P,X) (P)->_adjEnd = (X) -#define s2_ptoken_adjend_incr(P,X) (P)->_adjEnd += (X) - -#define s2_ptoker_begin(PT) ((PT)->begin) -#define s2_ptoker_end(PT) ((PT)->end) -#define s2_ptoker_token(PT) (PT)->token -#define s2_ptoker_len(PT) ((PT)->end - (PT)->begin) - -#ifdef __cplusplus -}/*extern "C"*/ -#endif -#endif -/* include guard */ -/* end of file t10n.h */ -/* start of file s2.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* - License: same as cwal. See cwal.h resp. cwal_amalgamation.h for details. -*/ -#ifndef NET_WANDERINGHORSE_CWAL_S2_H_INCLUDED_ -#define NET_WANDERINGHORSE_CWAL_S2_H_INCLUDED_ -/** @file */ /* for doxygen */ - - -/** @page page_s2 s2 scripting language - - s2 is a light-weight yet flexible scripting language based on the - cwal (Scripting Engine Without A Language) library. cwal provides - the abstract Value types, memory lifetime management, and garbage - collection systems, and s2 provides a scripting language on top of - that. - - A brief overview of properties of s2 which are of most interest to - potential clients: - - - Library licensing: dual Public Domain/MIT, with one optional - BSD-licensed part (the JSON input parser) which can be compiled - out if that license is too restrictive. - - - A lightweight, portable[1] C API for binding client-defined - functionality to script-space, making it scriptable and trackable - by the script's garbage collector. Tests so far put s2 on par - with lua in terms of memory usage. - - - s2's primary distributable is two C files: one header and one - implementation file, called the "amalgamation build," intended for - direct drop-in use in arbitrary client source trees. The canonical - build tree is intended primarily for development of cwal/s2, not - for development of client applications. The amalgamation includes - the cwal amalgamation, so it need not be acquired separately. - - - Has an expression-oriented syntax with a distinct JavaScript - flavor, but distinctly different scoping/lifetime rules. - - - cwal's garbage collector guarantees that client-specific - finalizers (for client-specified types and, optionally, for - function bindings) get called, provided it is used properly, though - it cannot always guaranty the order of destruction. - - - cwal and s2 are developed with the compiler set to pedantic - warning/error levels (and them some), and the s2 test suite - includes valgrind-based testing and reporting. New code rarely hits - the trunk without having run the whole test suite through valgrind. - - - Extending s2: the default s2 shell (s2sh) provides not only a - copy/paste bootstrap for creating client applications, but can be - extended by clients via script code or C (linking their features in - directly in or loading them via DLLs) without modifying its - sources, as described in the manual linked to below. - - - [1] = Portable means: C99 (as of July 2021). The canonical copy is - developed using the highest level of compiler warnings, treating - all warnings as errors (i.e. -Wall -Werror -Wpedantic -Wextra). - - In addition to these API docs, s2 as a whole is described in gross - amounts of detail in the s2/manual directory of its canonical - source tree, browsable online at: - - https://fossil.wanderinghorse.net/r/cwal/doc/ckout/s2/index.md - - That covers both the scripting language itself (in detail) and how - to use it in C (mainly via overviews and links to working code). -*/ -#include /* struct tm */ -#include /* FILE * */ - -#ifdef __cplusplus -extern "C" { -#endif -typedef struct s2_engine s2_engine; -typedef struct s2_scope s2_scope; -typedef struct s2_stoken s2_stoken; -typedef struct s2_stoken_stack s2_stoken_stack; -typedef struct s2_estack s2_estack; -typedef struct s2_sweep_guard s2_sweep_guard; -typedef struct s2_func_state s2_func_state; -/** Convenience typedef. */ -typedef struct s2_strace_entry s2_strace_entry; - -enum s2_rc_e { -S2_RC_placeholder = CWAL_RC_CLIENT_BEGIN, -/** - Used internally by routines which visit lists of keys/values - to provide a mechanism for aborting traversal early without - triggering an error. -*/ -S2_RC_END_EACH_ITERATION, - -/** - To be used only by the 'toss' keyword, if it ever gets added. -*/ -S2_RC_TOSS, - -/** Sentinel. */ -S2_RC_end, -/** - Client-thrown exceptions which use their own error codes "should - not" use codes below this value. Put differently, result codes - starting at this value are guaranteed not to collide with - CWAL_RC_xxx and S2_RC_xxx codes. Keep in mind that numerous - CWAL_RC_xxx and S2_RC_xxx codes have very specific meanings in - various contexts, so returning, from cwal/s2-bound client-side - code, values which just happen to collide with those may confuse s2 - and/or cwal into Doing The Wrong Thing. (Examples: CWAL_RC_OOM is - used solely to propagate out-of-memory (OOM) conditions, and - handling of CWAL_RC_RETURN is context-specific.) -*/ -S2_RC_CLIENT_BEGIN = S2_RC_end + 2000 - (S2_RC_end % 1000) -}; - -/** - - Enum specifying the precedences of operators in s2. - - Derived from: - - http://n.ethz.ch/~werdemic/download/week3/C++%20Precedence.html (now 404) - - http://en.cppreference.com/w/cpp/language/operator_precedence - - Regarding operator associativity: - - Wikipedia: - - http://en.wikipedia.org/wiki/Operator_associativity - - Says: - - "To prevent cases where operands would be associated with two - operators, or no operator at all, operators with the same - precedence must have the same associativity." - - We don't strictly follow that advice (by accident) but have not had - any unexpected problems in that regard. -*/ -enum s2_precedents { -S2_PR__start = 1, - -/** - Parens precedence is actually irrelevant here, as we parse parens - groups as atomic values instead of operators. -*/ -S2_PR_ParensOpen, -S2_PR_Comma, - -/** - Internal pseudo-operator for RHS evaluation in some case. - - No longer used - can be removed. -*/ -S2_PR_RHSEval, - -/** - = += -= *= /= <<= >>= %= &= ^= |= -*/ -S2_PR_Assignment__, -/** - The = operator. -*/ -S2_PR_OpAssign =S2_PR_Assignment__, -/** - PHP-style array-append. Essentially works like (array DOT index = - ...), where the index is the array's current length. Only usable in - assignment contexts. -*/ -S2_PR_ArrayAppend = S2_PR_Assignment__, - -/* S2_PR_Conditional__, */ -/** - In JavaScript ternary if has a higher precedence than - assignment, but we're going to go with the C/C++ precedence - here. -*/ -S2_PR_TernaryIf = S2_PR_Assignment__, - -S2_PR_Logical__, -S2_PR_LogicalOr = S2_PR_Logical__ /* || */, -S2_PR_LogicalOr3 = S2_PR_Logical__ /* ||| */, -S2_PR_LogicalOrElvis = S2_PR_Logical__ /* ?: */, -S2_PR_LogicalAnd = S2_PR_Logical__ /* && */, - -S2_PR_Bitwise__, -S2_PR_BitwiseOr = S2_PR_Bitwise__, -S2_PR_BitwiseXor = S2_PR_Bitwise__ + 1, -S2_PR_BitwiseAnd = S2_PR_Bitwise__ + 2, - -S2_PR_Equality__, -S2_PR_CmpEq = S2_PR_Equality__, -S2_PR_CmpEqStrict = S2_PR_Equality__, -S2_PR_CmpNotEq = S2_PR_Equality__, -S2_PR_CmpNotEqStrict = S2_PR_Equality__, - -S2_PR_Relational__, -S2_PR_CmpLT = S2_PR_Relational__, -S2_PR_CmpGT = S2_PR_Relational__, -S2_PR_CmpLE = S2_PR_Relational__, -S2_PR_CmpGE = S2_PR_Relational__, -S2_PR_OpInherits = S2_PR_Relational__, - -/** - '=~'. Should this have Equality precedence? -*/ -S2_PR_Contains = S2_PR_Relational__, -/** - '!~'. Should this have Equality precedence? -*/ -S2_PR_NotContains = S2_PR_Relational__, - -/* - TODO? - - <== and >== for strict-type LE resp GE - - ==< resp ==> for strict LT/GT -*/ - -S2_PR_Bitshift__, -S2_PR_ShiftLeft = S2_PR_Bitshift__, -S2_PR_ShiftRight = S2_PR_Bitshift__, - -S2_PR_Additive__, -S2_PR_Plus = S2_PR_Additive__, -S2_PR_Minus = S2_PR_Additive__, - -S2_PR_Multiplicative__, -S2_PR_Multiply = S2_PR_Multiplicative__, -S2_PR_Divide = S2_PR_Multiplicative__, -S2_PR_Modulo = S2_PR_Multiplicative__, - -S2_PR_Unary__, -S2_PR_PlusUnary = S2_PR_Unary__, -S2_PR_MinusUnary = S2_PR_Unary__, -S2_PR_LogicalNot = S2_PR_Unary__, -S2_PR_BitwiseNegate = S2_PR_Unary__, -S2_PR_Keyword = S2_PR_Unary__, -S2_PR_IncrDecr = S2_PR_Unary__, -/** - Used by 'foreach' to tell evaluation to stop at this operator. Its - precedence must be higher than S2_PR_Comma but lower than anything - else. -*/ -S2_PR_OpArrow2 /* => */ = S2_PR_Unary__, -/* C++: S2_PR_Unary__ ==> - - sizeof, new, delete, & (addr of), * (deref), - (typeCast), -*/ - -/* S2_PR_Kludge__, */ -/* S2_PR_DotDot = S2_PR_Kludge__, */ - -S2_PR_Primary__, -S2_PR_FuncCall = S2_PR_Primary__, -S2_PR_Subscript = S2_PR_Primary__, -S2_PR_Braces = S2_PR_Primary__, -S2_PR_DotDeref = S2_PR_Primary__, -S2_PR_DotDot = S2_PR_DotDeref, -S2_PR_OpColon2 = S2_PR_DotDeref, -S2_PR_OpArrow = S2_PR_DotDeref, - - -/* - C++: S2_PR_Primary__ ==> - - typeid(), xxx_cast - -*/ - - -S2_PR_Specials__, -S2_PR_ParensClose, -S2_PR_NamespaceCpp /* :: */, -S2_PR_end__ -}; - -/** - @internal - - Represents a combination value/operator for an s2_engine. Each - token represents one operand or operator for an s2 evaluation - stack. They get allocated often, but recycled by their associated - s2_engine, so allocations after the first few stack-pops are - O(1) and cost no new memory. - - Token instances must not be in use more than once concurrently, - e.g. a token may not be in more than one stack at a time, nor may - it be in the same stack multiple times. Multiple entries may - reference the same value, provided it is otherwise safe from being - swept/vacuumed up. -*/ -struct s2_stoken{ - /** - A s2_token_types value. - */ - int ttype; - /** - Certain token types have a value associated with them. The - tokenization process will create these, but will not add a - reference to them (because doing so complicates lifetimes, in - particular for result values which need up-scoping). This means - the client must be careful when using them, to ensure that they - get a ref if one is needed, and to either clean them up or - leave them to the GC if they don't want them. - */ - cwal_value * value; - - /** - Used for creating chains (e.g. a stack). - */ - s2_stoken * next; - - /** - Used by the parser to communicate source code location - information to the stack machine. This is necessary so that - errors generated at the stack machine level (the operator - implementations) can report the location information, though the - stack machine does not otherwise know anything about the source - code. - */ - s2_ptoken srcPos; -}; - - -/** - Empty-initialized s2_stoken structure, intended for - const-copy initialization. -*/ -#define s2_stoken_empty_m { \ - S2_T_INVALID/*ttype*/, \ - 0/*value*/, \ - 0/*next*/, \ - s2_ptoken_empty_m/*srcPos*/ \ - } - -/** - Empty-initialized s2_stoken structure, intended for - copy initialization. -*/ -extern const s2_stoken s2_stoken_empty; -/** - Holds a stack of s2_stokens. -*/ -struct s2_stoken_stack { - /** - The top of the stack. - - Maintained via s2_stoken_stack_push(), - s2_stoken_stack_pop(), and friends. - */ - s2_stoken * top; - /** - Number of items in the stack. - */ - int size; -}; - -/** - Empty-initialized s2_stoken_stack structure, intended for - const-copy initialization. -*/ -#define s2_stoken_stack_empty_m {0,0} - -/** - Empty-initialized s2_stoken_stack structure, intended for - copy initialization. -*/ -extern const s2_stoken_stack s2_stoken_stack_empty; - -/** - An "evaluation stack," a thin wrapper over two s2_stoken_stacks, - intended to simplify swapping the stacks in and out of an s2_engine - while parsing subscripts. s2 internally uses a distinct stack per - sub-eval, rather than one massive stack, as it's simply easier to - manage. When the main eval loop is (re)entered, a new eval stack is - put on the C stack (a local var of the eval function) and s2_engine - is pointed to it so we know which stack is current. - - Reminders to self: - - - 20160131 this type was designed to _not_ ref/unref values added - to the stack, but that may have turned out to be a mistake. It - simplifies much other code but string interning combined with our - internal use of "temp" values leads to unmanagable/incorrect - refcounts for interned string values in some cases, triggering - cwal-level corruption detection assertions. Rewiring s2 so that - this type does much of the reference count managent for a given - (sub)expression is certainly worth considering, but could require a - weekend or more of intense concentration :/. Until then, the main - eval routine uses a "ref holder" array to keep all being-eval'd - temporaries alive and valid for the duration of the eval. That - requires an extra cwal_array, but it isn't all that costly if - memory recycling is on (which it should be except when testing new - code (as recycling often hides memory misuse)). - - Current (20160215) plans: - - - expand this class to include: - - - Add a (cwal_engine*) or (s2_engine *) handle, as deps allow for - (see below, regarding s2_engine::skipMode, for why we may need the - latter). - - - Done: Add a (cwal_array *), allocated in the cwal_scope which is - active when the stack was initialized. Each token pushed into - this->vals is added to the array as a NULL entry if it has no - cwal_value, else that value is placed in the array. This array is - made vacuum-safe, which would eliminate s2's need for its vacsafe - flags. It would also hold refs, so no sweep-up during the stack's - useful lifetime. When this->vals is popped, we either unhand or - unref the value, depending on whether we want to (semantically) - discard it or not. See below for a novella on this topic (it's got - a happy ending, at least in theory). - - - We'd need to fix a few places in s2's eval engine which - create/push a token and then add the value themselves. They would - need to make the s2_estack aware of the token. We could do that by - limiting token allocation to the s2_estack-internal API. - - - Need explicit init/finalizer funcs for s2_stoken_stack, to clean - up the stash array. - - - Pushing a value token to this->vals adds it to the current stash - array at the same index position as the stack length-1. This allows - us to use the array as a ref holder and vacuum-safe environment for - the values. We don't want to use it as a replacement for - s2_stoken_stack because that needs to hold non-cwal_value state - (and making that type manage ref handling would require a rather - more invasive round of refactoring to clean up ref handling - _everywhere_ else because moving ref handling into that type would - truly bugger some stuff up (essentially all operator handlers, and - most of the keywords, would need some touching). - - Pros and such: - - - All in-use values get refs and can be made vac-safe. This should - eliminate any of s2's "vacsafe" flag handling (which is relatively - invasive). It can eliminates the array used by s2_eval_ptoker(), at - least in theory. No... i think we still need that one to ensure - that the pending result value (between expressions) is vacuum-safe. - - - optimization: if an s2_estack starts life when - (s2_engine::skipLevel>0) then it doesn't need a stash array because - skipLevel will never (legaly) return to 0 within the lifetime of - that s2_estack (because skipLevel is also incremented/decremented - in a stack-like fashion). We need a flag for this or simply check - this->se->skipLevel in the appropriate places. If we instead add - the creation of the stash array as a init-time option, we can - remove a ref on s2_engine to this class (but we'd need a - cwal_engine ref, i any case). Having such an option might - internally allow a simpler migration, as we could use the no-array - setting for the first refactoring step to keep behaviour the same. - - - With that in place, we can remove a deep-seated cwal-core - property which really annoys me: because of a less-complete - understanding (on my part) of the intricacies of refcounting, in - particular the handling of temp vals (in particular in conjunction - with string interning), th1ish required the following behaviour of - cwal: during scope cleanup (and only at scope cleanup), when - being-finalized containers reference a value in an older scope and - that value has (because of this container's cleanup) a refcount of - 0, the value is not destroyed, but re-temp'd back in its own scope. - This was required to accommodate return propagation in many - cases. It is not required, however, if the client simply does the - right things vis-a-vis refcounting and rescoping. So... with this - in place, once we prove that s2 doesn't need that cwal-level - feature, we can remove that. (i, just now, ran s2's full test suite - (with various recycling/memory usage option combinations) without - this behaviour, and s2 handled it fine). However, that would also - require abandoning th1ish altogether because fixing it to do proper - ref/unref everywhere would be far more effort than it is worth (the - only place th1ish is used, AFAIK, is demo apps). (In my defense, at - the time i _thought_ th1ish _was_ doing the right thing. i have - since, via s2, discovered that that was not the case.) - - Cons (maybe): - - - We need to add a layer of push/pop APIs and keep the s2 core from - using the s2_stoken_stack push/pop, as those don't partake in ref - lifetime tracking. - - - Adding a stash array adds potental allocation errors for push() - cases which are currently fail-safe. e.g. pushing a value cannot - currently fail because it's a linked list, but adding it to an - array might require allocating the array (on the first push) or - allocation memory for the underlying cwal_value list (on any push). - - - We'd need a hack to keep the stash array from freeing its list - memory when popped, because often fill/empty the same value stack - throughout an expression. (i think... maybe...) Ideally, we could - use cwal_array_length_set(ar, poppedStackLength) to simply trim the - list as it's popped, but setting the length to 0 implicitly frees - (or recycles) the underlying (cwal_value*) list memory. We'd want - to, as an allocation optimization, avoid that. Maybe not use - length_set() at all, but simply cwal_array_set(ar, pos, 0), and - keep a potentially ever-growing array. - - - It will cost an array per subexpression because - s2_eval_expr_impl() (the main eval loop) stashes/replaces the - s2_estack each time it is entered/leaves. We cannot change that in - conjunction with a "stash" array because value lifetimes would go - pear shaped quickly: the "stash" array would be global-scope (or - close to it), which would implicity rescope all stack-pushed values - into that scope. It would be a worst-case scenario for garbage - collection. Experimentation with adding such an array to - s2_eval_expr_impl() shows that it works but that the array - allocation counts (or alloc requests) shoot through the roof, and - real alloc counts skyrocket if value recycling is disabled. We need - (for cwal_value lifetime reasons) a new instance for each pushed - cwal_scope. Aha... we could have s2_estacks have a parent - (s2_estack*), add a (cwal_scope*) to keep track of which scope it - was initially initialized in, and inherit (via normal ref counting) - the array. Yes. Yesssss... that just might work nicely. We'd also - need to track exactly which stack created the array, so we know - which one needs to... no, we don't... refcounts will clean it - up. Indeed. Since we already effectively push/pop s2_estack - instances in s2_eval_expr_impl(), the code is already structured to - make adding/referencing a parent pointer easy to do. The stash - array's allocation must NOT be done lazily: it must be done in the - cwal_scope active when the stack was initialized (and within which - it must be finalized) because the array must be allocated in a - scope appropriate for its values, and it might not create any or - might create them after a couple scopes have been pushed. Hmmm. In - which case we'd need to be sure to rescope it (right after - allocation) to s2_estack::scope (new member, a (cwal_scope*)). - When allocating the array, we can check if the (grand...)parent's - scope is the same. If it is, borrow its array (if it has one), else - alloc a local array and assign it back to the parent (only if it's - in the same scope) as a optimization for the parent's further use - (as well as for use by a downstream child-s2_estack of that - scope). DAMN... if we do all that, we'd need s2_estack to track the - length the array had when it first got ahold of it, and only add - items at that position and subsequent, and then clean up to its - starting position when done. Hmmm. Ugly. i'd rather not share the - arrays than do that :/. It would save some memory, though. Maybe an - RFC after the simpler (array instance per s2_estack) approach is - tried out and measured for allocations. -*/ -struct s2_estack{ - /** The value stack. */ - s2_stoken_stack vals; - /** The operator stack. */ - s2_stoken_stack ops; -}; - -/** - Empty-initialized s2_estack structure, intended for const-copy - initialization. -*/ -#define s2_estack_empty_m {s2_stoken_stack_empty_m, s2_stoken_stack_empty_m} - -/** - Empty-initialized s2_estack structure, intended for copy - initialization. -*/ -extern const s2_estack s2_estack_empty; - -/** @internal - - An internal helper type for swapping an s2_engine's sweep-guard - state in and out at certain points. - - The docs for this struct assume only one context: that this is - used embedded in an s2_engine struct. -*/ -struct s2_sweep_guard { - /** - If greater than 0, s2_engine_sweep() will neither sweep nor - vacuum. - - Reminder to self: we will likely never be able to use recursive - sweep. It's inherently dangerous due to how we (mis)handle - refs during eval. - */ - int sweep; - /** - If greater than 0, s2_engine_sweep() will not vacuum, but will - fall back to sweep mode (if not disabled) instead. This HAS to - be >0 if the client makes use of any non-script-visible values - which are not otherwise vacuum-proofed and may be needed at a - time when script code may trigger s2_engine_sweep() (see - cwal_value_make_vacuum_proof()). - */ - int vacuum; - - /** - Helps keeps track of when to sweep and vacuum - incremented once - per call to s2_engine_sweep(). - */ - int sweepTick; -}; - -/** @internal - - Empty-initialized s2_sweep_guard state, intended for const - initialization. -*/ -#define s2_sweep_guard_empty_m {0,0,0} - -/** @internal - - Empty-initialized s2_sweep_guard state, intended for non-const - copy initialization. -*/ -extern const s2_sweep_guard s2_sweep_guard_empty; - -/** @internal - - A strictly internal type used for swapping out certain internal - state when crossing certain subexpression boundaries (namely, block - constructs). -*/ -struct s2_subexpr_savestate { - int ternaryLevel; - /* Others are pending, but i've forgotten what they might need - to be. */ -}; -typedef struct s2_subexpr_savestate s2_subexpr_savestate; -#define s2_subexpr_savestate_empty_m {0} - -/** - An abstraction layer over cwal_scope so that we can store - more per-scope metadata. - - Potential TODO (20190911): add a cwal_native wrapper to each scope - to allow script-side code to potentially do some interesting things - with scopes. Each instance would have a prototype with methods like - get(), isDeclared(), declare(key,val[,asConst]), and a const - property named parent which points to the parent scope native. - It's not 100% clear what we might want to do with such a beast, - other than add a __scope keyword which accesses it. -*/ -struct s2_scope { - /** - The cwal_scope counterpart of this scope. It's owned by cwal and - must outlive this object. This binding gets set/unset via the - cwal_scope_push()/pop() hooks. - */ - cwal_scope * cwalScope; - - /** - Current sweep/vacuum-guard info. - */ - s2_sweep_guard sguard; - - /** - Some internal state which gets saved/cleared when a scope is - pushed and restored when it is popped. - */ - s2_subexpr_savestate saved; - - /** - A "safe zone" to store values which are currently undergoing - evaluation and need a reference held to them. NEVER upscope this - array to a different scope: it is internal-only, owned by this - scope. We store this in the scope, rather than in the eval() - routine, as an allocation optimization. This placement makes only - a tiny difference when recycling is enabled, but when it's - disabled, total allocation counts for the current (20171113) unit - tests are cut by more than a third, from ~35k allocs down to - ~21k, saving more than 1M of total alloc'd memory. (Woot! A - _whole megabyte!_ ;) - - This member is only initialized via the main eval loop and only - freed via the s2-side cwal_scope_pop() hook. - - Summary of why this is needed: the eval engine does not - historically directly hold refs to temp values which are floating - around in the eval stack while an expression is being eval'd. - While that's not normally a problem, there are cases where such - temp values can get freed up, in particular in the face of string - interning, where multiple cwal_value pointers are all pointing at - the same value yet the number of references is lower than the - number of pointers. Fixing this properly, i.e. holding references - to everything we put in the stack, would seemingly be a major - undertaking. This evalHolder is a simpler workaround, but has the - disadvantage of memory cost (whereas refs via the eval engine - would not cost us anything we don't already have). "One of these - days" i'd like to sit down and go patch the world such that the - eval stack maintains references to its values, at which point we - can get rid of evalHolder. That would require a fairly major - effort to get all of the operators and other actors ironed out, - and would likely introduce a very long tail of bugs while stray - refs/unrefs were discovered. The eval holder works well, the only - down-side being its memory cost (a few kb for s2's largest test - scripts, _possibly_ as much as 8 or 10k on highly recursive tests - involving require.s2). - - 20181123: reminder to self: with this piece in place, a recursive - sweep/vacuum might become legal. Hmmm. Something to try. If we - can recursively vacuum then we can eliminate, i believe, all of - the remaining "GC deathtrap" cases (barring malicious cases - implemented in C to specifically bypass vacuuming by making - values unnecessarily vacuum-proof). - - @see s2_eval_hold() - */ - cwal_array * evalHolder; - - /** - Internal flags. - */ - uint16_t flags; -}; - -#define s2_scope_empty_m {\ - NULL/*cwalScope*/, \ - s2_sweep_guard_empty_m/*sguard*/, \ - s2_subexpr_savestate_empty_m/*saved*/, \ - 0/*evalHolder*/, \ - 0U/*flags*/ \ -} -extern const s2_scope s2_scope_empty; - -/** - This class encapsulates a basic stack engine which uses - the cwal_value type as its generic operand type. - - Each s2_engine must be initialized with a cwal_engine, - which the s2_engine owns and uses for all memory management, - as well as the core Value Type system. - - @see s2_engine_alloc() - @see s2_engine_init() - @see s2_engine_finalize() -*/ -struct s2_engine{ - /** - The associated cwal_engine. - */ - cwal_engine * e; - - /** - A marker which tells us whether s2_engine_finalize() needs to - free this instance or not. - */ - void const * allocStamp; - - /** - The stacks of operators and values. This container gets - continually swapped out by the eval engine, such that each - expression has its own clean stack (this is internally easier to - handle, or seems to be). - */ - s2_estack st; - - /** - A general-purpose buffer for internal (re)use. Internal routines - must be certain never to simply reset this buffer: it might be in - use by higher-up calls in the stack. Always locally store - buffer.used, use memory at offsets (buffer.mem+oldUsed), then - re-set buffer.used=oldUsed and buffer.mem[oldUsed]=0 when - finished, so that higher-up callers don't get the rug pulled out - from under them. Growing the buffer is fine as long as all - (internal) users properly address buffer.mem (and never hold a - pointer to it). - */ - cwal_buffer buffer; - - /** - If greater than 0, "skip-mode" must be honored by all evaluation - code. Skip-mode basically means "consume as much as you normally - would, but have (if possible) no side-effects while doing so." - That allows us to consume tokens with or without actually - evaluating the results as we go (the engine pretends to evaluate, - but uses the 'undefined' value for everything, so it doesn't - actually allocate any values). This is the basis of short-circuit - evaluation. - */ - int skipLevel; - - /** - Every this-many calls to s2_engine_sweep() should either sweep or - vacuum. When 0, sweeping is disabled (generally not a good - idea). The lowest-level evaluation routine disables sweeping - during evaluation of an expression to keep the lifetimes of - temporaries safe. s2_eval_ptoker(), a high-level eval routine, - sweeps up after every expression, but only 1-in-skipInterval - sweep-ups has an effect. - - Reminder to self: something to try: if s2_eval_expr_impl() uses - an array to control lifetimes of temps (like s2_eval_ptoker() - does, except that eval needs to keep more than one value at a - time), we would almost not need sweeping at all, except to - cleanup those pesky ignored return values, orphaned cycles, and - such. That might also make a recursive sweep safe (recursive - vacuum is theoretically not safe). - - A sweepInterval of 1 is very aggressive, but is recommended - during testing/development because it triggers problems related - to value lifetime mismanagement more quickly, in particular if - vacuumInterval is also 1. - - Very basic tests on a large script with a milliseconds-precision - and massif's instruction counter show only a relatively small - impact, both in speed (no appreciable change) and total CPU - instructions (less than 0.5%), when comparing sweepInterval of 1 - vs 7. The former used 90kb RAM and 215M CPU instructions, and the - latter 92kb RAM and 214M instructions. i.e. there is (so far) no - compelling argument for increasing this above 1. - - [Much later on... 20181123] That said, since all of the above - text was written, it seems that all of the outlier cases - involving temporarily stray values have been eliminated, and - sweeping is essentially not happening in the whole s2 unit test - suite (a total of 2 sweeps, as of this writing, meaning there's - basically nothing to sweep). Vacuuming, on the other hand, is - picking up stray cyclic values (and vacuuming is the only way to - eliminate those (but only within the currently active - scope)). That would argue for increasing the sweepInterval - considerably, and maybe lowering the vacuumInterval to 1 or 2, so - that every sweep becomes a vacuum (noting that vacuuming is - computationally far more expensive). Even so... a sweepInterval - of 1 helps detect ref/unref misuse more quickly, and the sooner - such a problem is triggered, the easier it is to track down. Few - things are more irritating than an unpredictable crash caused by - delayed garbage collection tripping over a value which was - misused several scopes back. - */ - int sweepInterval; - - /** - Every this-many sweep attempts will be replaced by a vaccuum - instead if this->scopes.current->sguard.vacuum is 0. There is no - single optimal value. 1 is aggressive (always vacuuming instead - of sweeping, potentially very costly). In generic tests in - th1ish, 3-5 seemed to be a good compromise, and then only 1 in 10 - or 15 vacuum runs was cleaning up more than sweeping was, because - vacuuming only does its real magic when there are orphaned cyclic - structures laying around (which doesn't happen often). In scripts - with short-lived scopes, a value of 0 here is fine because scope - cleanup will also get those orphans, provided they're not - propagated up out of the scope (via explicit propagation or - containment in a higher-scoped container). - */ - int vacuumInterval; - - /** - Total number of s2_engine_sweep() calls which led to a sweep or - vacuum. - */ - int sweepTotal; - - /** - Some sort of container used by s2_stash_get() and - s2_stash_set(). This is where we keep internally-allocated - Values which must not be garbage-collected. This value is made - vacuum-proof. - */ - cwal_value * stash; - - /** - Internal holder for script function filename strings. This - mechanism provides script functions with a lifetime-safe (and - vacuum-safe) copy of their filename strings, while sharing - like-stringed values with other function instances. It moves - those strings into the top scope, and there's currently no - mechanism for removing them. That's a bummer, but not a huge - deal. The alternative would be duplicating the same (long) - filename strings across all functions from a given file. - */ - cwal_hash * funcStash; - - /** - Holds the current ternary-if depth to allow the eval loop to - provide better handling of (and error reporting for) ':' tokens. - */ - int ternaryLevel; - - /** - Used to communicate the "argv" value between the interpreter - parts which call functions and the pre-call hook called by - cwal. - */ - cwal_array * callArgV; - - /** - Gets set by the stack layer when an operator (A) triggers an - error and (B) has its srcPos set. Used to communicate - operator-triggered error location information back to the parser - layer. - */ - char const * opErrPos; - - /** - Used for collecting error location info during the parse/eval - phase of the current script, for places where we don't have - a direct local handle to the script. - */ - s2_ptoker const * currentScript; - - /** - An experiment. - - 20160125: reminder to self: the next time i write that without an - explanation for my future self to understand, please flog - me. Currently trying to figure out what this is for an am - at a loss. It doesn't appear to do anything useful. - - 20191209: used for implementing the experimental __using keyword - to access "using" properties from inside a script function. - */ - s2_func_state const * currentScriptFunc; - - /** - A pointer to an app-internal type for managing "user-defined - keywords". - */ - void * ukwd; - - /** - Various commonly-used values which the engine stashes away for - its own use. Their lifetimes are managed via this->stash. - */ - struct { - /** The word "prototype". */ - cwal_value * keyPrototype; - /** The word "this". */ - cwal_value * keyThis; - /** The word "argv". */ - cwal_value * keyArgv; - /** The word "__typename" (deprecated). */ - cwal_value * keyTypename; - /** The word "script". */ - cwal_value * keyScript; - /** The word "line". */ - cwal_value * keyLine; - /** The word "column". */ - cwal_value * keyColumn; - /** The word "stacktrace". */ - cwal_value * keyStackTrace; - /** - Name of the ctor function for the 'new' keyword ("__new"). - */ - cwal_value * keyCtorNew; - /** The word "value". */ - cwal_value * keyValue; - /** The word "name". */ - cwal_value * keyName; - /** Part of an ongoing experiment - the word "interceptee". */ - cwal_value * keyInterceptee; - /** The value for the s2out keyword. Initialized on its first use. */ - cwal_value * s2out; - /** The value for the import keyword. Initialized on its first use. */ - cwal_value * import; - /** The key for the import.doPathSearch flag. */ - cwal_value * keyImportFlag; - /** The value for the define keyword. Initialized on its first use. */ - cwal_value * define; - } cache; - - /** - Stack-trace state. - */ - struct { - /** - Stored as an optimization for sizing the target - array when collecting stack traces. - */ - cwal_size_t count; - /** - s2 will bail out if this call stack limit is reached, under the - assumption that infinite recursion is going on. Set to 0 to - disable the limt. - - Notes: - - - Only script-called functions count for this purpose, not - calls to Functions (be they script functions or not) called via - native code (cwal_function_call() and friends). - - - The unit test suite, as of 2014106, can get by with values - under 10. The require.s2 tests cap out somewhere between 20 and - 25 levels. Because require.s2 is inherently deep-stacked, - prudence dictates a 'max' value notably higher than that. - */ - cwal_size_t max; - /** - Head of the current stack trace. - */ - s2_strace_entry * head; - /** - tail of the current stack trace. - */ - s2_strace_entry * tail; - } strace; - - /** - State for various internal recycling bins. - */ - struct { - /** - Recycle bin for stokens. - */ - s2_stoken_stack stok; - - /** - The max number of items to keep in the stok recycler stack. - */ - int maxSTokens; - - /** - Recycle bin for script-function state. - */ - struct { - /** Head of the recyling list. */ - s2_func_state * head; - /** Current number of entries in the list. */ - int count; - /** Max allows entry count before we stop accepting new - items into the list, and free them instead. */ - int max; - } scriptFuncs; - } recycler; - - /** - Holds cwal_outputer instances managed by the s2_ob_push() family - of functions. - */ - cwal_list ob; - /** - Holds DLL/module handles so that the interpreter - can close them when it cleans up. - */ - cwal_list modules; - - /** - State for propagating dot-operator metadata between the main - eval() parts and the operator implementations. - */ - struct { - /** - The dot and # operators sets this to the LHS container resp. - hash. Extreme care must be taken to ensure that this does not - point to a stale value. - */ - cwal_value * lhs; - - /** - Used to differentiate between prop access wrt to types which - should bind as 'this' in calls to props resolved as - functions. Namely: - - obj.FUNC() // binds 'this' - - array.NUMBER // does not - - hash # KEY // does not - - All three set dotOp.lhs and dotOp.key. Only "this-respecting" - ops (namely, the DOT op) sets dotOp.self. - */ - cwal_value * self; - - /** - Set by the dot operator to its RHS (key) part. Used by - assignment and/or the unset op to get access to the property - name, as opposed to the resolved property value which the dot - operator evaluates to. - */ - cwal_value * key; - } dotOp; - - /** - Internal state for keeping s2's scopes in sync with cwal's via - cwal_engine_vtab's cwal_scope_hook_push_f() and - cwal_scope_hook_pop_f() mechanism. - - This addition allowed us to remove several now-obsolete - s2-specific scope management functions and keep the - cwal_scope/s2_scope stacks in perfect harmony, eliminating some - weird potential/hypothetical error cases (or confusion-causing - cases) where the scope stacks didn't line up as desired. This - change really should have been made years ago. The only real cost - for the s2 client is memory needed for a block of s2_scopes (via - the 'list' member of this struct). Aside from that allocation - (potentially, depending on the scripts, a small handful of - reallocations totaling anywhere from 400 bytes to a few kb), it's - been a 100% win. - - Added 20181123. - */ - struct { - /** - Manages an array of s2_scope in response to cwal_scope push/pop - events. - - How this memory is managed is not guaranteed by the API, but it - is currently handled via cwal_realloc(), which means that it's - counted among the memory counted in - cwal_engine::memcap::currentMem. - - The engine will grow this list as necessary, but won't shrink - it until the engine is finalized, at which point the memory is - (of course) freed. - - Reminder to self (20181123): s2's various test scripts/suites - have a max scope depth of anywhere from 9 to 22. - */ - s2_scope * list; - - /** - Points to memory in this->list for the current s2_scope. Note - that it is generally NOT SAFE to keep a pointer to an s2_scope - from this->list because any resize of the list can invalidate - it, but this member is only modified in the routines which - manager the list's size. - */ - s2_scope * current; - - /** - The number of entries reserved in this->list. - */ - cwal_size_t alloced; - - /** - A convenience counter which must always be the same as - s2_engine::e::curent->level. - */ - cwal_size_t level; - - /** - We need a cwal_scope to act as our top scope. This is that - scope. - */ - cwal_scope topScope; - - /** - An internal flag used only by s2_scope_push_with_flags() for - passing flags to the scope-pushed hook. - */ - uint16_t nextFlags; - } scopes; - - /** - Various internal flags. - */ - struct { - /** - If greater than 0 then some debug/tracing output of the stack - machine is generated. Use higher levels for more output. Note - that this output goes to stdout, not s2's output channel. - */ - int traceTokenStack; - /** - >0 means to trace PASSED assertions to cwal_output(). - >1 means to also trace FAILED assertions. - */ - int traceAssertions; - - /** - If true, sweeping keeps metrics (==performance hit) and - outputs them to stdout. Only for debugging, of course. - */ - int traceSweeps; - - /** - If true (the default), creating exceptions generates a stack - trace, else no stack trace is generated. This is simply a - performance tweak: stack traces are normally exceedingly - helpful but for test code which intentionally throws lots of - exceptions, they are costly. - - Potential TODO: interpret this as a maximum depth, with 0 being - unlimited and negative being disabled. - */ - int exceptionStackTrace; - - /** - To avoid that certain constellations miss an "interrupt" - request, we need a flag for "was interrupted" which lives - outside of the this->err state. TODO: also propagate - OOM/EXIT/FATAL this way? - - TODO: do not clear this via s2_engine_err_reset(), and add - a separate API for that. We really want interruption to trump - everything. - */ - volatile int interrupted; - - /** - A bitmask of features which are explicitly disabled, from the - s2_disabled_features enum. - */ - cwal_flags32_t disable; - } flags; - - /** - Various metrics. - */ - struct { - /** - Total number of s2_stoken_alloc() calls for this - s2_engine instance. - */ - unsigned int tokenRequests; - /** - Total number of calls into cwal_malloc() to allocate - an s2_stoken. - */ - unsigned int tokenAllocs; - /** - Number of tokens currently allocated but not yet - freed nor recycled. - */ - unsigned int liveTokenCount; - /** - Maximum number of s2_stokens alive throughout the life - of this object. - */ - unsigned int peakLiveTokenCount; - - /** - Number of script-side assert()ions which have been run. - */ - unsigned int assertionCount; - /** - Current sub-expression (e.g. parens/brace group) parsing - leve. - */ - int subexpDepth; - /** - The highest-ever sub-expression depth. - */ - int peakSubexpDepth; - - /** - Maximum number of cwal_scope levels deep concurrently. - */ - cwal_size_t maxScopeDepth; - - /** - Number of script-side functions created. - */ - unsigned int funcStateRequests; - - /** - Number of script-side functions for which we had - to allocate an s2_func_state instance. - */ - unsigned int funcStateAllocs; - /** - Total memory allocated for the internal state - for script-side functions, NOT including - their sourceInfo bits, as those are already - recorded in the cwal metrics. - */ - unsigned int funcStateMemory; - - /** - The number of calls to s2_next_token(). - */ - unsigned int nextTokenCalls; - - /** - The number of times which s2_engine::scopes::list is - successfully grown to add new scopes. It does not increment on - (re)allocation errors. - */ - unsigned short totalScopeAllocs; - - /** - The total number of cwal_scopes pushed via cwal_scope_push() - and friends. This does not track the top-level scope which the - cwal engine pushes before s2_engine can take over (which - s2_engine immediately pops and re-pushes so that is can sync - its scope levels with cwal's). - */ - unsigned int totalScopesPushed; - - /** - Counts how many times we reuse a script filename from - this->funcStash. - */ - unsigned int totalReusedFuncStash; - /** Total number of times which either keyword lookup fell back to - checking for a UKWD or the defined() keyword (or similar) made - an explicit check for a UKWD. */ - unsigned ukwdLookups; - /** Total number of ukwdLookups which resulted in a hit. */ - unsigned ukwdHits; - } metrics; -}; - -/** @def S2_ENGINE_SWEEP_VACUUM_INTERVALS - - Two comma-separated integers representing the default values for - s2_engine::sweepInterval and s2_engine::vacuumInterval (in that - order). - - Long story short: the sweepInterval represent how often (measured - in full expressions) s2_engine will "sweep", and the vacuumInterval - represents every how many "sweeps" will be converted to a "vacuum" - (a more costly operation, but it can weed out stray/unreachable - cyclic structures). A sweepInterval of 1 is best when testing new - code, as it potentially helps uncover ref/unref misuse more quickly - than higher values do. vacuumInterval should only be 1 if - sweepInterval is relatively high (e.g. 5-10). -*/ -#if !defined(S2_ENGINE_SWEEP_VACUUM_INTERVALS) -# if defined(NDEBUG) -# define S2_ENGINE_SWEEP_VACUUM_INTERVALS 5/*sweepInterval*/, 2/*vacuumInterval*/ -# else -# define S2_ENGINE_SWEEP_VACUUM_INTERVALS 1/*sweepInterval*/, 5/*vacuumInterval*/ -# endif -#endif - -/** @def s2_engine_empty_m - - Empty-initialized s2_engine structure, intended for - const-copy initialization. -*/ -#define s2_engine_empty_m { \ - 0/*e*/, \ - 0/*allocStamp*/, \ - s2_estack_empty_m/*st*/, \ - cwal_buffer_empty_m/*buffer*/, \ - 0/*skipLevel*/, \ - S2_ENGINE_SWEEP_VACUUM_INTERVALS, \ - 0/*sweepTotal*/, \ - 0/*stash*/, \ - 0/*funcStash*/,\ - 0/*ternaryLevel*/, \ - 0/*callArgV*/, \ - 0/*opErrPos*/, \ - 0/*currentScript*/, \ - 0/*ukwd*/, \ - 0/*currentScriptFunc*/, \ - {/*cache*/\ - 0/*keyPrototype*/,\ - 0/*keyThis*/,\ - 0/*keyArgv*/,\ - 0/*keyTypename*/,\ - 0/*keyScript*/,\ - 0/*keyLine*/,\ - 0/*keyColumn*/,\ - 0/*keyStackTrace*/,\ - 0/*keyCtorNew*/,\ - 0/*keyValue*/,\ - 0/*keyName*/,\ - 0/*keyInterceptee*/,\ - 0/*s2out*/,\ - 0/*import*/,0/*keyImportFlag*/, \ - 0/*define*/ \ - }, \ - {/*strace*/0/*count*/, 100/*max*/, 0/*head*/,0/*tail*/}, \ - {/*recycler*/ \ - s2_stoken_stack_empty_m/*stok*/, \ - 50 /*maxSTokens*/, \ - {/*scriptFuncs*/ 0/*head*/, 0/*count*/, 20 /*max*/ } \ - }, \ - cwal_list_empty_m/*ob*/, \ - cwal_list_empty_m/*modules*/,\ - {/*dotOp*/\ - 0/*lhs*/, \ - 0/*self*/, \ - 0/*key*/ \ - }, \ - {/*scopes*/ \ - NULL/*list*/, \ - NULL/*current*/, \ - 0/*alloced*/, \ - 0/*level*/, \ - cwal_scope_empty_m/*topScope*/, \ - 0/*nextFlags*/ \ - }, \ - {/*flags*/ \ - 0/*traceTokenStack*/, \ - 0/*traceAssertions*/, \ - 0/*traceSweeps*/, \ - 1/*exceptionStackTrace*/, \ - 0/*interrupted*/, \ - 0U/*disable*/ \ - }, \ - {/*metrics*/ \ - 0/*tokenRequests*/, \ - 0/*tokenAllocs*/, \ - 0/*liveTokenCount*/, \ - 0/*peakLiveTokenCount*/, \ - 0/*assertionCount*/, \ - 0/*subexpDepth*/, \ - 0/*peakSubexpDepth*/, \ - 0/*maxScopeDepth*/,\ - 0/*funcStateRequests*/, \ - 0/*funcStateAllocs*/, \ - 0/*funcStateMemory*/, \ - 0/*nextTokenCalls*/, \ - 0/*totalScopeAllocs*/, \ - 0/*totalScopesPushed*/, \ - 0/*totalReusedFuncStash*/, \ - 0/*ukwdLookups*/,0/*ukwdHits*/ \ - } \ - } - -/** - Empty-initialized s2_engine structure, intended for - copy initialization. -*/ -extern const s2_engine s2_engine_empty; - - -/** - Initializes se and transfers ownership of e to it. - - se must be a cleanly-initialized s2_engine instance, allocated via - s2_engine_alloc() or stack-allocated and copy-initialized from - s2_engine_empty or s2_engine_empty_m (same contents, slightly - different usage contexts). - - e must be a freshly-cwal_engine_init() instance and the client MUST - NOT install any new functionality to it before this call because: - in order to gain control of all the scopes, s2 must destroy all of - e's scopes (it pushes a top scope upon initialization) before - continuing with its own initialization. - - Returns 0 on success, a CWAL_RC_xxx code on error, the only - potential ones at this phase of the setup being blatant misuse - (passing in a NULL pointer) an allocation error. - - After returning: - - A) Ownership of se is not modified. - - B) If this function fails because either of the arguments is 0, - ownership of e is not modified and CWAL_RC_MISUSE is returned, - otherwise ownership of e is transferred to se regardless of success - or failure (because there is no "detach and recover" strategy for - any errors after the first couple allocations). On success or - error, if both arguments are non-NULL, (se->e == e) will hold - after this function returns. - - C) The caller must pass eventually se to s2_engine_finalize() to - clean it up, regardless of success or error. - -*/ -int s2_engine_init( s2_engine * se, cwal_engine * e ); - -/** - Allocates a new s2_engine instance using e's allocator. Returns 0 - on error. - - In practice this is not used: s2_engine instances are typically - stack-allocated or embedded as a property of a larger object. In - both such cases s2_engine_empty or s2_engine_empty_m should be used - to empty-initialize their state before using them in any way. - - @see s2_engine_finalize() - @see s2_engine_init() - @see s2_engine_empty_m - @see s2_engine_empty -*/ -s2_engine * s2_engine_alloc( cwal_engine * e ); - -/** - Frees up all resources owned by se. If se was allocated using - s2_engine_alloc() then se is also freed, otherwise it is assumed - to have been allocated by the caller (possibly on the stack) and - is cleaned up but not freed. - - This call pops all cwal scopes from the stack, destroying any and - all cwal_value instances owned by the engine, as well as cleaning - up any memory owned/managed by those values (e.g. client-allocated - memory managed via a cwal_native instance). - - @see s2_engine_alloc() -*/ -void s2_engine_finalize( s2_engine * se ); - - -/** - Proxy for cwal_error_set(). -*/ -int s2_error_set( s2_engine * se, cwal_error * err, int code, char const * fmt, ... ); - -/** - A proxy for cwal_error_exception(). -*/ -cwal_value * s2_error_exception( s2_engine * se, - cwal_error * err, - char const * scriptName, - int line, int col ); - -/** - Converts err (or se's error state, if err is NULL) to an Exception - value using s2_error_exception() (see that func for important - details) then set's se's exception state to that exception. - - Like cwal_exception_set(), this function returns CWAL_RC_EXCEPTION - on success or some other non-0 code if creation of the exception - fails (generally speaking, probably CWAL_RC_OOM). - - If line<=0 then err->line and err->col are used in place of the given - line/column parameters. - - If script is 0 and err->script is populated, that value is used - instead. - - To convert an error set via s2_engine_err_set() (or equivalent) to - a cwal-level exception, pass 0 for all arguments after the first. -*/ -int s2_throw_err( s2_engine * se, cwal_error * err, - char const * script, - int line, int col ); - -/** - A convenience function for cwal_callback_f() implementations bound - to/via an s2_engine-managed cwal_engine. It is (almost) equivalent - to calling cwal_exception_setf() and passing args->engine as the - first argument and passing on the rest of the arguments as-is. It - returns CWAL_RC_EXCEPTION on success, some other "more critical" - error code (e.g. CWAL_RC_OOM) if an exception cannot be thrown. The - result is intended to be immediately returned from the callback, - without the callback processing any additional work (other than - cleanup/error recovery). -*/ -int s2_cb_throw( cwal_callback_args const * args, int code, - char const * fmt, ... ); - - -/** - Sets se->e's error state (via cwal_error_setv()) and exception state - (via cwal_exception_setf()) and returns CWAL_RC_EXCEPTION on - success or a "more fatal" non-0 code on error. - - Special case: if the given code is CWAL_RC_OOM, this function has - no side-effects and returns code as-is. -*/ -int s2_throw( s2_engine * se, int code, char const * fmt, ... ); - -/** - A proxy for cwal_engine_error_setv(). This does not - set or modify the _exception_ state. -*/ -int s2_engine_err_setv( s2_engine * se, int code, char const * fmt, va_list ); - -/** - A proxy for cwal_engine_error_set(). -*/ -int s2_engine_err_set( s2_engine * se, int code, char const * fmt, ... ); - -/** - If se has error state, that error code is returned, else if - an exception is pending, CWAL_RC_EXCEPTION is returned, else - 0 is returned. -*/ -int s2_engine_err_has( s2_engine const * se ); - -/** - Resets se's state, as per cwal_error_reset(), plus clears any - s2-level interruption flag. Does not clear any propagating - exception. -*/ -void s2_engine_err_reset( s2_engine * se ); - -/** - Works like s2_engine_err_reset(), but also clears any propagating - exception or "return"-style value. -*/ -void s2_engine_err_reset2( s2_engine * se ); - -/** - Clears se's state, as per cwal_error_clear(), plus - clears any interruption flag. -*/ -void s2_engine_err_clear( s2_engine * se ); - -/** - Returns se's error state, as per cwal_error_get(), with one special - case: if se has been "interrupted" via s2_interrupt(), - CWAL_RC_INTERRUPTED is returned, possibly without a message (*msg - and *msgLen may be be set to NULL resp. 0 if the interruption does - not include a message). -*/ -int s2_engine_err_get( s2_engine const * se, char const ** msg, cwal_size_t * msgLen ); - -/** - A utility function implementing unary and binary addition and - subtraction of numbers (in the form of cwal_values). - - This routine does not take overloading into account. - - To do binary operations: - - - Both lhs and rhs must be valid values. They will be converted - to a number if needed. lhs is the left-hand-side argument and rhs - is the right-hand-side. - - - Set doAdd to true to perform addition, 0 to perform - subtraction. - - For unary operations: - - - As for binary ops, but lhs must be NULL. The operation is - applied solely to rhs. - - The result value is assigned to *rv, and rv may not be NULL. - - Returns 0 on success, non-0 on misuse or allocation error. - - If lhs (binary) or rhs (unary) is a double value then the result - (except for cases listed below) will be a double, otherwise it - will be an integer. - - This routine makes some optimizations to avoid allocating memory - when it knows it does not have to. These optimizations are - internal details, but may change the expected type of the result, - and so are listed here: - - - If !lhs and doAdd is true (i.e. unary addition), then the - result is rhs. - - - Binary ops: if either argument is 0 resp. 0.0 then the result - is the other argument. i.e. A+0===A and 0+A===A. - - Returns 0 on success, non-0 on error. Errors can come in the form - of CWAL_RC_OOM or, for overloaded operators, any sort of error a - script-side function can cause. On non-OOM error, se's error - state will be update or an exception may be propagated. -*/ -int s2_values_addsub( s2_engine * se, char doAdd, - cwal_value * lhs, cwal_value * rhs, - cwal_value **rv ); - - -/** - The multiply/divide/modulo counterpart of s2_values_addsub(), - this routine applies one of those three operations to - its (lhs, rhs) arguments and assigns the result to *rv. - - This routine does not take overloading into account. - - If mode is negative, the division operation is applied, - If mode is 0, modulo is applies. If mode is greater than 0 - then multiplication is applied. - - Returns 0 on success, a non-0 CWAL_RC_xxx value on error. - Returns CWAL_SCR_DIV_BY_ZERO for either division or modulo by - 0. On error, se's error state will be update or (for overloaded - operators) an exception may be propagated. - - This routine applies several optimizations which might change - the expected result type: - - Modulo: - - - Always has an integer result except on modulo-by-0. - - Multiplication: - - - (0 * 1) === 0 - - (0.0 * 1) === 0.0 - - (N * 1) === N - - If either the lhs or rhs is a double then the result will be a - double unless a more specific optimization applies. -*/ -int s2_values_multdivmod( s2_engine * se, int mode, - cwal_value * lhs, cwal_value * rhs, - cwal_value **rv ); - -/** - Performs bitwise and bitshift operations on one or two values. - - This routine does not take overloading into account. - - This function has two modes: - - Unary: op must be S2_T_OpNegateBitwise and lhs must be NULL. - The bitwise negation operation is applied to rhs and the result - is stored in *rv. - - Binary: op must be one of the many S2_T_OpXXXAssign, - S2_T_OpXXXAssign3, - S2_T_Op{AndBitwise,OrBitwise,XOr,ShiftLeft,ShiftRight}. The binary - operation is applied to lhs and rhs and the result is stored in - *rv. - - The resulting value is always of type CWAL_TYPE_INTEGER, but it may - be optimized away to the lhs or rhs instance (as opposed to - creating a new one). - - Returns 0 on success. On error *rv is not modified. -*/ -int s2_values_bitwiseshift( s2_engine * se, int op, cwal_value * lhs, - cwal_value * rhs, cwal_value ** rv ); - - -/** - Prints out a cwal_printf()-style message to stderr and abort()s the - application. Does not return. Intended for use in place of - assert() in test code. The library only uses this internally to - confirm internal invariants in some places where an assert() would - be triggered in debug builds. -*/ -void s2_fatal( int code, char const * fmt, ... ); - - -/** - Flags for customizing s2_cb_print_helper()'s behaviour. -*/ -enum s2_cb_print_helper_options { -S2_PRINT_OPT_NONE = 0x00, -/** - Tells s2_cb_print_helper() to output one space character - (ASCII 0x20) between arguments. It does not output a space - at the start or end of the arguments. -*/ -S2_PRINT_OPT_SPACE = 0x01, -/** - Tells s2_cb_print_helper() to output one newline character (ASCII 0x0A) - At the end of the arguments, even if it does not output any arguments. -*/ -S2_PRINT_OPT_NEWLINE = 0x02, -/** - Tells s2_cb_print_helper() to apply s2_value_unwrap() to each argument. That - will "unwrap" one level of CWAL_TYPE_UNIQUE wrapper, but have no effect on - non-Unique values. -*/ -S2_PRINT_OPT_UNWRAP = 0x04, -/** - Tells s2_cb_print_helper() to "return" (in the cwal_callback_f() - sense) the callee Function value, rather than the undefined value. -*/ -S2_PRINT_OPT_RETURN_CALLEE = 0x08, -/** - Tells s2_cb_print_helper() to "return" (in the cwal_callback_f() - sense) the call's "this" value, rather than the undefined value. If - both this flag and S2_PRINT_OPT_RETURN_CALLEE are specified, an - exception is triggered. - - Note that when s2 calls a function "standalone" (outside the - context of an object property access), the function is its own - "this", so this flag will have the same effect as - S2_PRINT_OPT_RETURN_CALLEE in such cases. -*/ -S2_PRINT_OPT_RETURN_THIS = 0x10 -}; - -/** - This is intended to be a proxy for print()-like cwal_callback_f() - implementations. The first and second arguments should be passed in - as-is from the wrapping callback's first and second arguments. The - fourth argument should be a mask of s2_cb_print_helper_options - flags which customize its output. The third argument... - - If skipArgCount is > 0 then that many arguments will be skipped - over (ignored by this routine). The intent is that certain helper - functions might accept "prefix" arguments which it processes itself - before passing the rest on to this function. - - On success, *rv will, by default, be set to cwal_value_undefined(), - but that may be modified by including the - S2_PRINT_OPT_RETURN_CALLEE or S2_PRINT_OPT_RETURN_THIS flag, or - setting *rv explicitly from the wrapping callback _after_ calling - this. - - All output generated by this function is emitted via cwal_output(), - so it ends up in the s2 engine's configured output channel. - - Returns 0 on success, else an error code suitable for returning - from the wrapping callback. Potential errors include, but are not - limited to, I/O problems and out-of-memory. - - If both the S2_PRINT_OPT_RETURN_CALLEE and S2_PRINT_OPT_RETURN_THIS - flags are specified, an exception is triggered and - CWAL_RC_EXCEPTION is returned, which the caller of this routine is - expected to propagate back to its own caller. That said, the caller - will necessarily be C code and should not pass both of those flags - to this function. -*/ -int s2_cb_print_helper( cwal_callback_args const * args, - cwal_value **rv, - uint16_t skipArgCount, - cwal_flags32_t flags ); - -/** - A cwal_callback_f() implementation performing basic - output-to-console. All arguments are output with a single space - between them and a newline at the end. Output goes to - cwal_output(), so it might not go to the console. - - The callback returns args->callee via *rv. -*/ -int s2_cb_print( cwal_callback_args const * args, cwal_value **rv ); - -/** - Works just like s2_cb_print() except that it does not add - extra whitespace around its arguments, nor a newline at - the end of the line. - - The callback returns args->callee via *rv. -*/ -int s2_cb_write( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() implementation which sends a flush request to - args->engine's configured output channel. Returns 0 on success, - throws on error. -*/ -int s2_cb_flush( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() which implements a simple script file execution - method. Script usage: - - thisFunc(filename) - - Runs the script using s2_eval_filename() and assigns the result of - the last expression to *rv (or the undefined value if the script - evaluates to NULL (which can happen for a number of reason)). - - Requires that s2_engine_from_args() returns non-0 (i.e. that - args->engine was initialized along with a s2_engine instance). -*/ -int s2_cb_import_script(cwal_callback_args const * args, cwal_value ** rv); - -/** - Returns the prototype object for just about everything. That - instance gets stashed away in se. Ownership of the returned pointer - is unchanged. The caller MUST NOT unreference it - (cwal_value_unref() or cwal_value_unhand()) unless he explicitly - obtains a reference. -*/ -cwal_value * s2_prototype_object( s2_engine * se ); -cwal_value * s2_prototype_function( s2_engine * se ); -cwal_value * s2_prototype_array( s2_engine * se ); -cwal_value * s2_prototype_exception( s2_engine * se ); -cwal_value * s2_prototype_hash( s2_engine * se ); -cwal_value * s2_prototype_string( s2_engine * se ); -cwal_value * s2_prototype_double( s2_engine * se ); -cwal_value * s2_prototype_integer( s2_engine * se ); -cwal_value * s2_prototype_buffer( s2_engine * se ); -cwal_value * s2_prototype_enum(s2_engine * se); -cwal_value * s2_prototype_tuple( s2_engine * se ); - -/** - Returns the first value v of v's prototype chain - which is an enum value created by the enum keyword, - or 0 if none are. -*/ -cwal_value * s2_value_enum_part( s2_engine * se, cwal_value * v ); - -/** - Returns true (non-0) if v (not including prototypes) is an - enum-type value created by the enum keyword. -*/ -int s2_value_is_enum( cwal_value const * v ); - -/** - Adds a persistent value to the interpreter. These are stored, for - lifetime purposes, under the top-most scope with one reference to - it, and they are not visible to script code. They will be made - vacuum-proof so long as they are in the stash. - - This is where clients might store, e.g. references to their custom - native-side prototype objects (optionally (and preferably), they - may set them as normal variables, but that is not always feasible). - - key must be NUL-terminated. - - Returns 0 on success. -*/ -int s2_stash_set( s2_engine * se, char const * key, cwal_value * v ); - -/** - Equivalent to s2_stash_set() but takes its key in the form of a - Value instance. -*/ -int s2_stash_set_v( s2_engine * se, cwal_value * key, cwal_value * v ); - -/** - Fetches a value set with s2_stash_set(). Returns NULL if not found, - if !se, or if (!key || !*key). key must be NUL-terminated. -*/ -cwal_value * s2_stash_get( s2_engine * se, char const * key ); - -/** - Identical to s2_stash_get() but accepts the length of the key, in bytes. - If keyLen==0 and *key, then cwal_strlen(key) is used to calculate the - length. -*/ -cwal_value * s2_stash_get2( s2_engine * se, char const * key, cwal_size_t keyLen ); - -/** - Identical to s2_stash_get2() but returns the matching cwal_kvp on a - match (else NULL). -*/ -cwal_kvp * s2_stash_get2_kvp( s2_engine * se, char const * key, - cwal_size_t keyLen); - -/** - Identical to s2_stash_get() but takes its key in the form of a Value - instance. -*/ -cwal_value * s2_stash_get_v( s2_engine * se, cwal_value const * key ); - -/** - Wrapper around cwal_var_decl_v(). - - Notable error codes: - - CWAL_RC_ALREADY_EXISTS = key already exists -*/ -int s2_var_decl_v( s2_engine * se, cwal_value * key, - cwal_value * v, uint16_t flags ); - -/** - The C-string counterpart of s2_var_decl(). -*/ -int s2_var_decl( s2_engine * se, char const * name, - cwal_size_t nameLen, - cwal_value * v, uint16_t flags ); - -/** - Searches for a named variable in the given interpreter - engine. scopeDepth is as described for cwal_scope_search_v() - (normally 0 or -1 would be correct). - - Returns the found value on success, NULL if no entry is found or if - any arguments are invalid (!se, !key). -*/ -cwal_value * s2_var_get( s2_engine * se, int scopeDepth, - char const * key, cwal_size_t keyLen ); - -/** - Functionally identical to s2_var_get(), but takes its - key as a cwal_value. -*/ -cwal_value * s2_var_get_v( s2_engine * se, int scopeDepth, - cwal_value const * key ); - -/** - If self is not NULL then this performs a lookup in the current - scope (recursively up the stack) for a variable with the given - key. If self is not NULL then it performs a property search on - self, recursive up the prototype chain if necessary. On success - the result is stored in *rv and 0 is returned. See s2_set_v() for - how Arrays are (sometimes) handled differently. - - If no entry is found: - - - If self has the S2_VAL_F_DISALLOW_UNKNOWN_PROPS container client - flag then CWAL_RC_NOT_FOUND is returned and se's error state - contains the details. e.g. enums do that. - - - Otherwise 0 is returned and *rv is set to 0. - - @see s2_set_v() -*/ -int s2_get_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value ** rv ); - -/** - C-string variant of s2_get_v(). -*/ -int s2_get( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value ** rv ); - -/** - Sets a named variable in the given interpreter engine. scopeDepth - is as described for cwal_scope_chain_set_v() (normally 0 would be - correct). Use a value of NULL to unset an entry. - - Returns 0 on success, CWAL_RC_MISUSE if !ie or !key, and - potentially other internal/low-level error codes, e.g. CWAL_RC_OOM - if allocation of space for the property fails. -*/ -int s2_var_set( s2_engine * se, int scopeDepth, - char const * key, cwal_size_t keyLen, - cwal_value * v ); - -/** - Functionally identical to s2_var_set(), but takes its - key as a cwal_value. -*/ -int s2_var_set_v( s2_engine * se, int scopeDepth, - cwal_value * key, cwal_value * v ); - -/** - If self is NULL then this behaves as a proxy for - cwal_scope_chain_set_with_flags_v(), using the current scope, - otherwise... - - self must be NULL or a container type (!cwal_props_can(self)), - or non-0 (not sure _which_ non-0 at the moment) is returned. - - v may be NULL, indicating an "unset" operation. - - If self is an a Array or has one in its prototype: if key is-a - Integer then cwal_array_set() is used to set the property, else - cwal_prop_set_v(self,key,v) is used. - - If self is-a Hashtable and s2_hash_dot_like_object() (or - equivalent) has been used to set its "like-an-object" flag, then - this function manipulates the hash entries, not object properties - (hashes can have both). - - Otherwise the key is set as an object-level property of self. - - Returns the result of the underlying setter call (of which there - are several posibilities). - - If !v then this is an "unset" operation. In that case, if !self - and key is not found in the scope chain, CWAL_RC_NOT_FOUND is - returned, but it can normally be ignored as a non-error. - - @see s2_get_v() -*/ -int s2_set_with_flags_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value * v, - uint16_t kvpFlags ); - -/** - Identical to s2_set_with_flags_v(), passing CWAL_VAR_F_PRESERVE as - the final parameter. -*/ -int s2_set_v( s2_engine * se, cwal_value * self, - cwal_value * key, cwal_value * v ); - -/** - C-string variant of s2_set_with_flags_v(). -*/ -int s2_set_with_flags( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value * v, uint16_t flags ); - -/** - Equivalent to calling s2_set_with_flags() with the same - arguments, passing CWAL_VAR_F_PRESERVE as the final - argument. -*/ -int s2_set( s2_engine * se, cwal_value * self, - char const * key, cwal_size_t keyLen, - cwal_value * v ); - -/** - Installs a core set of Value prototypes into se. - - Returns 0 on success. -*/ -int s2_install_core_prototypes(s2_engine * se); - -enum s2_next_token_flags { -/** - Specifies that s2_next_token() should treat EOL tokens as - significant (non-junk) when looking for the next token. -*/ -S2_NEXT_NO_SKIP_EOL = 0x01, -/** - Specifies that s2_next_token() should not "post-process" any - tokens. e.g. it normally slurps up whole blocks of parens and - braces, and re-tags certain token types, but this flag disables - that. The implication is that the caller of s2_next_token() "really - should" use a non-null 4th argument, so that the main input parser - does not get confused by partially-consumed block constructs or - mis-tagged semicolons. -*/ -S2_NEXT_NO_POSTPROCESS = 0x02 -}; - -/** - A form for s2_ptoker_next() which is specific to s2_engine - evaluation, performing various token skipping and - post-processing. It searches for the next non-junk token, then - perhaps post-processes it before returning. - - If tgt is NULL then the token is consumed as normal and st->token - contains its state after returning. If tgt is not-NULL then the - this works like a lookahead: the consumed token is copied to *tgt - and st's token/putback state are restored to their pre-call state - (regardless of success or failure). - - The flags parameter must be 0 or a bitmask of s2_next_token_flags - values. Note that by default newlines are treated as junk tokens - and ignored. - - Returns 0 on success. On error the input must be considered - unparsable/unrecoverable unless st is a sub-parser of a compound - token in a larger parser (e.g. a (list) or {script} or [braces]), - in which case parsing may continue outside of the subparser - (because we know, at that point, that the group tokenizes as an - atomic token). - - This function does not throw exceptions in se, but instead - updates se's error state on error. -*/ -int s2_next_token( s2_engine * se, s2_ptoker * st, int flags, s2_ptoken * tgt ); - -/** - Uses s2_next_token(se,pr,nextFlags,...) to peek at the next token - in pr. If the next token has the type ttype, this function returns - ttype, else it returns 0. If consumeOnMatch is true AND the next - token matches, pr->token is set to the consumed token. -*/ -int s2_ptoker_next_is_ttype( s2_engine * se, s2_ptoker * pr, int nextFlags, - int ttype, char consumeOnMatch ); - -/** - Creates a value for a token. This basically just takes the t->src - and makes a string from it, but has different handling for certain - token types. Specifically: - - - Integers (decimal, octal, and hex) and doubles are created as - the corresponding cwal numeric types. - - - Strings literals get unescaped. - - - Heredocs are not unescaped, but their opening/closing markers are - trimmed, as are any whitespace implied by their modifier flag (if - any). - - - S2_T_SquigglyBlock is, for historical reasons, returned as - a string, but it does not get unescaped like a string literal. - It does get left/right trimmed like a heredoc, though. - - - Any other token type is returned as-is as a string value, - without unescaping. - - On success *rv contains the new value. - - Returns 0 on success, CWAL_RC_OOM on OOM, and may return - CWAL_RC_RANGE if unescaping string content fails (so far only ever - caused by ostensibly invalid inputs, e.g. \U sequences). - - Note: the 2nd argument is only here for error reporting in one - corner case (unescaping a string fails). - - ACHTUNG: a returned string value might not be a new instance, and - it is CRITICAL that the client not call cwal_value_unref() on it - without calling cwal_value_ref() first. Not following this advice - can lead to the caller pulling the returned string value out from - under other code. Conversely, failing to call cwal_value_unref() - may (depending on other conditions) lead to a cwal_scope-level leak - until that scope is swept up or popped. -*/ -int s2_ptoken_create_value( s2_engine * se, - s2_ptoker const * pr, - s2_ptoken const * t, - cwal_value ** rv ); - -/** - Sets se's error state, as for s2_engine_err_set(), and returns that - call's result value. If fmt is NULL or !*fm, st->errMsg is used (if - set) as the message. If se->opErrPos or s2_ptoker_err_pos(st) (in - that order) are set to a position within [st->begin,st->end) then - line/column information, relative to st->begin, is appended to the - message. Sometimes it is necessary for callers to set or tweak st - error position explicitly before calling this (see - s2_ptoker_errtoken_set()). - - Returns the error code, not 0, on success! Returns some other - non-0 code on error. - - This routine takes pains not to allocate any memory, which also - means not generating an error message, if the given error code is - CWAL_RC_OOM. -*/ -int s2_err_ptoker( s2_engine * se, s2_ptoker const * st, - int code, char const * fmt, ... ); - - -/** - Similar to s2_err_ptoker(), but clears se's error state and sets - se's Exception state. One exception (as it were) is if the code is - CWAL_RC_OOM, in which case se's error state is set but no exception - is thrown and no formatted message is generated because doing - either would require allocating memory. -*/ -int s2_throw_ptoker( s2_engine * se, s2_ptoker const * pr, int code, - char const * fmt, ... ); - -/** - Throws se's error state (which must be non-OK), using pr (if not - NULL, else se->currentScript) as the source for error location - information. It tries to determine the position of the error - based on se's and pr's state at the time this is called. It is - particularly intended to be called from code which: - - a) is calling non-throwing, error-producing code (e.g. op-stack processing). - - b) wants to convert those errors to script-side exceptions. - - Returns CWAL_RC_EXCEPTION on success, some "more serious" non-0 - code on errors like allocation failure (CWAL_RC_OOM). -*/ -int s2_throw_err_ptoker( s2_engine * se, s2_ptoker const * pr ); - -/** - Throws a value as an exception in se. - - If pr is not NULL, it is used for collecting error location - information, which is set as properties of the exception. If pr is - NULL, se's current script is used by default. If both are NULL, - no script-related information is added to the exception. - - If v is-a Exception (including inheriting one), it is assumed that - the exception already contains its own error state: errCode is - ignored and script location information is only added if v does not - appear to have/inherit that information. - - If v is-not-a Exception, v is used as the "message" property of a - newly-created exception, and errCode its "code" property. - - 20191228: behaviour changed slightly for the case that v is-a - Exception, in that script location information are no longer always - harvested because doing so normally leads to having duplicated - error state (most notably the stack trace) in the inherited - exception and v. If, however, v appears to inherit no script - location information (which can happen if the inherited exception - was born in native space, as opposed to via the script-side - exception() keyword), script info is harvested and installed in v - (not in its inherited exception object). -*/ -int s2_throw_value( s2_engine * se, s2_ptoker const * pr, int errCode, cwal_value *v ); - - -/** - Flags for eventual use with s2_eval_expr() and friends. -*/ -enum s2_eval_flags_t { -/** - Treat the eval-parsing as a lookahead of the expression token(s) - instead of consuming them. It may still _evaluate_ the contents - on its way to finding the end of the expression, unless - S2_EVAL_SKIP is used as well. - - Reminder to self: it seems that this is no longer used internally, - so it's a candidate for removal. -*/ -S2_EVAL_NO_CONSUME = 0x01, -/** - Forces the parsing (if not already done so by a higher-level - parse) into "skip mode." - - Used for short-circuit evaluation, which only evaluates the - tokens for syntactical correctness, without having side-effects - which affect the script's result. It is legal to use S2_EVAL_SKIP - in conjunction with S2_EVAL_NO_CONSUME, as this can be used to - confirm syntactic correctness (at least for the top-most level of - expression) and find the end of the expression point without - "really" evaluating it. i.e. for short-circuit logic. -*/ -S2_EVAL_SKIP = 0x02, -/** - NOT YET IMPLEMENTED. May never be. - - Treat comma tokens as end-of-expression instead of as a binary - operator. -*/ -S2_EVAL_COMMA_EOX = 0x04, - -/** - Inidicates that a new cwal_scope should be pushed onto the (cwal) - stack before expression parsing starts, and popped from the stack - when it ends. The result value of the expression will be up-scoped - into the calling (cwal) scope. -*/ -S2_EVAL_PUSH_SCOPE = 0x10, - -/** - An internal-use flag, and bits at this location and higher this are - reserved for internal use. -*/ -S2_EVAL_flag_bits = 16 -}; - -/** - Tokenizes and optionally evaluates one complete expression's worth - of token state from st. This is the core-most evaluator for s2 - - there is not a lower-level one in the public API. Care must be - taken with lifetimes, in particular with regards to sweeping and - vacuuming, when using this routine. - - If flags contains S2_EVAL_NO_CONSUME, it behaves as is if it - tokenized an expression and then re-set st's token state. This - can be used to check expressions for eval'ability without - consuming them. Be careful not to use that flag in a loop, as it - will continually loop over the same tokens unless the caller - manually adjusts st->token between calls. - - If rv is not NULL then on success the result of the expression - is written to *rv. Lifetime/ownership of the expression is not - modified from its original source (which is indeterminate at this - level), and the client must take a reference if needed. - - If rv is not NULL and the expression does not generate a value - (e.g. an empty expression or EOF), *rv is set to 0. - - On any sort of error, *rv is not modified. - - Sweeping is disabled while an expression is running, to avoid that - any values used in the expression can live as long as they need to - without requiring explicit references everywhere. - - If flags contains S2_EVAL_SKIP then this function behaves - slightly differently: it parses the expression and runs it - through the normal evaluation channels, but it does so with "as - few side-effects as possible," meaning it can be used to skip - over expressions without (in effect) evaluating them. When - running in "skip mode," all operations are aware that they should - perform no real work (e.g. not allocating any new values), and - should instead simply consume all their inputs without doing - anything significant. This allows us to pseudo-evaluate an - expression to find out if it could be evaluated. In skip mode, if - rv is not NULL then any result value written to *rv "should" (by - library convention) be 0 (for an empty expression) or - cwal_value_undefined(). Achtung: if se->skipLevel is positive - then this function always behaves as if S2_EVAL_SKIP is set, - whether it is or not. All this flag does is temporarily - increments se->skipLevel. - - Returns 0 on success or any number of CWAL_RC_xxx values on - error. On error, se's error state is updated and/or a cwal-level - exception is thrown (depending on exactly what type of error and - when/where it happened). - - On success st->token holds the token which caused expression - tokenization to terminate. On non-error, if st->token is not at - its EOF, evaluation may continue by calling this again. When - running in consuming mode (i.e. not using S2_EVAL_NO_CONSUME), - then this routine sets st's putback token to the pre-call - st->token (i.e. the start of the expression). That means that a - s2_ptoker_putback() will put back the whole expression. - - Upon returning (regardless of success or error), st.capture will - point to the range of bytes captured (or partially captured - before an error) by this expression. - - If st->token is an EOF or end-of-expression (EOX) token after this - is called, *rv might still be non-0, indicating the expression - ended at an EOF/EOX. This is unfortunate (leads to more work after - calling this), but true. - - Before evaluation, se's current eval stack is moved aside, and it - is restored before returning. This means that calls to this - function have no relationship with one another vis-a-vis the stack - machine. Any such relationships must be built up in downstream - code, e.g. by calling this twice and using their combined result - values. This property allows the engine to recover from syntax - errors at the expression boundary level without the stack - manipulation code getting out of hand. It also means that - subexpressions cannot corrupt the stack parts used by the parent - (or LHS) expression(s). - - Garbage collection: this routine cannot safely sweep up while it is - running (and disables sweep mode for the duration of the - expression, in case a subexpression triggers a - sweep). s2_eval_ptoker() and friends can, though. Any temporaries - created in the current scope by this routine may be swept up after - it is called, provided the client has references in place wherever - he needs them (namely, on *rv). If the S2_EVAL_PUSH_SCOPE flag is - used, sweepup is not necessary because only *rv will survive past - the pushed scope. That said, pushing a scope for a single - expression is just a tad bit of overkill, and not really - recommended. Much Later: as of late 2017, this routine doesn't leak - any temporaries by itself, but the arbitrary script code it calls - might hypothetically do so via native code. -*/ -int s2_eval_expr( s2_engine * se, s2_ptoker * st, - int flags, cwal_value ** rv); - -/** - Flags for use with s2_eval_ptoker() and (potentially) friends. -*/ -enum s2_eval_flags2_t { -/** - Guaranteed to be 0, indicating no special flags. - */ -S2_EVALP_F_NONE = 0, -/** - Indicates that s2_eval_ptoker() should propagate CWAL_RC_RETURN - unconditionally. Normally it will transate that result into a - "return" value for the caller, the return 0. With this flag, it - will leave the s2-level return-handling semantics to the caller. -*/ -S2_EVALP_F_PROPAGATE_RETURN = 0x01 -}; - -/** - Evaluates all expressions (iteratively) in the s2 script code - wrapped by pt. pt contains the source code range, its optional - name, and its tokenizer lineage (if used in a sub-parser context). - - e2Flags may be 0, indicating no special evaluation flags, or a - bitmask of values from the s2_eval_flags2_t enum. - - If rv is NULL then any result value from the parsed expressions - is ignored. - - If rv is not NULL then the final result of the script is stored - in *rv. Its ownership is unspecified - it might be a new - temporary awaiting a reference (or to be discarded) or it might - be a long-lived value which made its way back from the global - scope. We just can't know at this point. What this means is: if - the caller needs to use *rv, he needs to do so immediately - (before the next sweep-up in the current scope or the current - scope ending). He may obtain a reference in "any of the usual - ways." - - Returns 0 on success, a CWAL_RC_xxx value on error. On error, se's - error state and/or se->e's exception state will be set (depending on - what caused the error) and *rv will not be modified. - - Garbage collection: while iterating over expressions, this routine - briefly holds a reference to the pending result value, and sweeps - up temporaries using s2_engine_sweep() (meaning that it may or may - not periodically clean up temporaries). The reference to *rv is - released (without destroying *rv) before this function returns, - meaning that *rv may have been returned to a probationary - (temporary) state by this call. If the caller needs to ensure its - safety vis-a-vis sweepup, he must obtain a reference to it. If - clients are holding temporaries in the current scope, they need to - push a cwal scope before running this, and pop that scope - afterwards, upscoping *rv to the previous if necessary (see - cwal_scope_pop2() and cwal_value_rescope()). - - Nuances: - - - See s2_eval_expr() for lots more details about the parsing and - evaluation process. - - - If pr->name is set then it is used as the name of the script, - otherwise a name will be derived from pr->parent (if set), - recursively. - - - A value followed by an end-of-expression (EOX: semicolon, - end-of-line, or (in some cases) end-of-file) results to that - value. A second "hard EOX" (i.e. a semicolon) will set the result - to NULL. For purposes of counting the first EOX, the EOL token is - considered an EOX, but multiple EOLs are not treated as multiple - EOX. Examples: "3;" === Integer 3, but "3;;" === C-level NULL. - Adding newlines between (or after) the final value and the - semicolons does not change the result. - - - *rv (if not 0) may be assigned to 0 even if the script - succeeds. This means either an empty script or a series of - semicolon/EOX tokens have removed the result from the stack (as - explained above). - - - If the expression triggers a CWAL_RC_RETURN result AND pt->parent - is NULL and e2Flags does _not_ have the S2_EVALP_F_PROPAGATE_RETURN - bit set, then the "return" result is treated as a legal value, and - any pending/propagating result is passed on to the caller via - *rv. If pt->parent is not NULL (or S2_EVALP_F_PROPAGATE_RETURN) - then CWAL_RC_RETURN is (needs to be) propagated up, and is returned - as an error (it is treated as one until it hits and handler which - accepts "return" results and knows how to fiddle s2_engine's - internals when doing so). - - Notes about "return" handling in s2: - - Return semantics are implemented by doing a combination of: - - - Calling s2_propagating_set() to set a "propagating" value. That - part keeps the value propagating up the popping scope stack. - - - Returning CWAL_RC_RETURN from the routine in question. When this - value is returned, consumers expecting it normally assert() that - s2_propagating_get() returns non-NULL, as they expect it to have - been set to implement 'return' keyword semantics. - - The point being: if callers want to propagate a 'return' from - the called script, they must: - - - Pass S2_EVALP_F_PROPAGATE_RETURN in the e2Flags bits. - - - Accept the result code of CWAL_RC_RETURN as a non-error. - - - Must call s2_propagating_take(se) to take over the returned value - or s2_propagating_set(se,0) to clear it (_potentially_ destroying - it immediately). It might be a temporary, it might not be - its - origin is indeterminate. If the caller needs to ensure it is kept - alive, he must arrange to do so, e.g. via getting a reference via - cwal_value_ref() and potentially (depending on his needs) making it - vacuum-safe by inserting it into a vacuum-safe container or using - cwal_value_make_vacuum_proof(). (Note, however, that vacuum-safing - is a rare need, intended only for use with values which will be - held around in C-space and never exposed to the script world.) - - - If the caller wants to propagate the result value further up the - cwal scope stack, he should use cwal_value_rescope() (or - s2_value_upscope()) to move it one scope up the stack before - popping his scope. (Alternatively, cwal_scope_pop2() can pop the - scope and rescope a single propagating value at the same time.) - - Similar handling is used for exit and break keywords, with the - CWAL_RC_EXIT and CWAL_RC_BREAK result codes. The throw keyword - returns the CWAL_RC_EXCEPTION code and its exception value is - fetched/reset using cwal_exception_get(), cwal_exception_set(), and - cwal_exception_take(), but exceptions are triggered in many places - from C code, as opposed to only via the throw keyword (which uses - the same mechanism). -*/ -int s2_eval_ptoker( s2_engine * se, s2_ptoker * pt, int e2Flags, - cwal_value **rv ); - -/** - Functionally equivalent to using s2_eval_ptoker() with a - s2_ptoker initialized to use the source range - [src,src+srcLen). The name parameter may be 0 - it is used when - generating error location information. If srcLen is negative, - cwal_strlen() is used to calculate src's length. - - If newScope is true, a new scope is used, otherwise the code is - eval'd in the current scope, which can have unwanted side effects - if the evaluation sweeps or (more particularly) vacuums up while - the calling code has non-sweep/vacuum-safe values laying around - (which it might, without knowing it). Unless you are 100% certain - about the current state of the scripting engine and all client-side - values owned by that engine, always use a new scope when running - this function. (Pro tip: you can almost never be 100% certain about - those conditions!) - - If newScope is true, rv is not NULL, and evaluation is successful, - *rv gets rescoped (if needed) into the calling scope before - returning. On error, *rv is not modified. - - @see s2_eval_buffer() - @see s2_eval_cstr_with_var() -*/ -int s2_eval_cstr( s2_engine * se, - char newScope, - char const * name, - char const * src, int srcLen, - cwal_value **rv ); - -/** - This is a convenience wrapper around s2_eval_cstr() which does the - following: - - 1) Pushes a new cwal scope, returning immediately if that fails. - - 2) Declares a scope-local variable with the name/value provided by - the 2nd and 3rd arguments. - - 3) Evaluates the given script code. (Presumably this script - references the local new variable.) - - 4) Pops the new scope. On success, if rv is not NULL, the result - value of the eval is written to *rv. If rv is NULL, or on error, - the result value (if any) is discarded. - - All parameters except for the 2nd and 3rd function as described for - s2_eval_cstr(). - - It is important that the caller hold a reference to the 3rd - argument before calling this. - - This function fills a relatively common niche where a module wants - to perform some of its initialization in script form and only needs - a single local var reference to do it. - - @see s2_eval_buffer() - @see s2_eval_cstr() -*/ -int s2_eval_cstr_with_var( s2_engine * se, - char const * varName, - cwal_value * varValue, - char const * scriptName, - char const * src, int srcLen, - cwal_value **rv ); - -/** - A convenience wrapper around s2_eval_cstr(). An empty - buffer is treated like a an empty string (""). -*/ -int s2_eval_buffer( s2_engine * se, - char newScope, - char const * name, - cwal_buffer const * buf, - cwal_value **rv ); - -/** - Like s2_eval_cstr(), but evaluates the contents of a file. fname is - the name of the file. fnlen is the length of the filename, which - must be non-0, but cwal_strlen() is used if fnlen is negative. - - If pushScope is true then the contents are run in a new scope, - otherwise they are run in the current scope (quite possibly not - what you want, but go ahead if you want). If rv is not NULL then - the result value of the evaluation (if any) is assigned to *rv. If - *rv is not 0 upon returning then *rv will (if needed) have been - moved into the calling cwal scope when this returns. - - If the script triggers a CWAL_RC_RETURN code then this function - treats that as a success result, sets *rv (if rv is not NULL) to - the 'return' value, and stops automatic propagation of that - value. Most non-exception errors get converted to exceptions, so as - to not be fatal to the importing script. - - Returns 0 on success. On error it may return a number of things: - - - CWAL_RC_OOM indicates an allocation failure. - - - CWAL_RC_EXCEPTION indicates that the file's script contents - threw or propagated an exception, which is available via - cwal_exception_get(). - - - CWAL_RC_FATAL: means 'fatal' was called (which implicitly - triggers an exception). Its pending exception can be found in - cwal_exception_get(). - - - CWAL_RC_EXIT: means the 'exit' keyword was called. Its result value - can be found in cwal_propagating_get(). - - - Most other non-0 codes cause se's error state to be updated with - more information (see s2_engine_err_get()). -*/ -int s2_eval_filename( s2_engine * se, char pushScope, - char const * fname, - cwal_int_t fnlen, - cwal_value ** rv ); - - -/** - Appends the given value to the given buffer in string form. - Returns 0 on success. - - Objects and Arrays/Tuples are buffered in JSON form, and this - function will fail if traversing them discovers cycles. -*/ -int s2_value_to_buffer( cwal_engine *e, cwal_buffer * buf, - cwal_value * arg ); - -/** - A cwal_callback_f() impl which uses s2_value_to_buffer() to convert - args->self to a string. The other arguments are ignored. -*/ -int s2_cb_value_to_string( cwal_callback_args const * args, cwal_value **rv ); - -/** - Returns se's underlying cwal_engine instance (used by - much of the lower-level scripting engine API). It is owned - by se. -*/ -cwal_engine * s2_engine_engine(s2_engine * se); - -/** - cwal_callback_f() implementation which acts as a proxy for - cwal_value_compare(). - - If passed two values it passes those to cwal_value_compare(), else - it passes args->self and args->argv[0] to it (in that order). - - If both the lhs/rhs values are the same pointer, they of course - compare as equivalent (without calling cwal_value_compare()). - - If passed 3 values and the 3rd is truthy, it checks to see if the - values have the same type. If they do not, it returns an arbitrary - (but stable) non-0 value. If they do have the same type, or if the - 3rd parameter is falsy, it behaves as if passed 2 parameters. - - Throws on usage error, else returns the result of the comparison - via *rv. - - Script signatures: - - integer compare(value rhs); // requires a 'this' - - integer compare(value lhs, value rhs[, boolean typeStrict = false]); - - The latter form works independently of the current 'this'. -*/ -int s2_cb_value_compare( cwal_callback_args const * args, cwal_value **rv ); - - -/** - Behaves more or less like the access(2) C function (_access() on - Windows builds). - - Returns true if the given filename is readable (writeable if - checkForWriteAccess is true), else false. -*/ -char s2_file_is_accessible( char const * fn, char checkForWriteAccess ); - -/** - Checks for the existence of a directory with the given NUL-terminated - name. If passed a true 2nd argument then it also checks whether the - directory is writeable. - - Returns true (non-0) if the directory exists and (if - checkForWriteAccess is true) writeable. If checkForWriteAccess is - false, it returns true if the directory can be stat()ed. - - Returns 0 if the given name cannot be stat()ed or if - checkForWriteAccess is true and the directory is not writeable. - - Currently on works on Unix platforms. On others it always returns - 0. -*/ -char s2_is_dir( char const * name, char checkForWriteAccess ); - - -/** - cwal_callback_f() impl binding s2_file_is_accessible() in scriptable form: - - fileIsAccessible(string filename [, bool checkWriteMode=false]) - - This function throws an exception if s2_disable_check() for the flag - S2_DISABLE_FS_STAT fails. -*/ -int s2_cb_file_accessible( cwal_callback_args const * args, cwal_value **rv ); - -/** - The directory counterpart of s2_cb_file_accessible(). -*/ -int s2_cb_dir_accessible( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl binding realpath(3). Script usage: - - string realpath(path) - - Returns the undefined value if it cannot resolve the name. Throws - on error or if the library is built without realpath(3) support. -*/ -int s2_cb_realpath( cwal_callback_args const * args, cwal_value **rv ); - - -/** - A cwal_callback_f() impl which passes args->self through - cwal_json_output() to produce JSON output. Assigns the resulting - string value to *rv. If args->argc is not 0 then args->argv[0] is - used to specify the indentation, as per cwal_json_output_opt. - - Script usage depends on whether or not args->self is-a (or inherits) - Buffer. If not, then the function's usage looks like: - - string t = self.toJSONToken([indentation=0 [, cyclesAsStrings=false]]) - - and returns the JSON-ified from of self. - - If self is-a Buffer, it looks like: - - self.toJSONToken(Value v [, indentation=0 [, cyclesAsStrings=false]]) - - It appends the JSON form of v to self and returns self. - - If cyclesAsStrings is true, recursion/cycles are rendered in some - useless (debugging only) string form, otherwise cycles cause an - exception to be thrown. -*/ -int s2_cb_this_to_json_token( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() which passes its first argument through - cwal_json_output() to produce JSON output. Assigns the resulting - string value to *rv. If args->argc is greater than 1 then the - second argument specifies the indentation: a positive number for - that many spaces per level and a negative number for that many hard - tabs per level. - - - string t = toJSONToken(value, [indentation=0 [, cyclesAsStrings=false]]) - - and returns the JSON-ified from of the value. - - If cyclesAsStrings is true, recursion/cycles are rendered in some - useless (debugging only) string form, otherwise cycles cause an - exception to be thrown. -*/ -int s2_cb_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv ); - - -/** - A cwal_callback_f() implementation which parses JSON string input. - - Script usage: - - @code - var json = '{"a":"hi!"}'; - var obj = thisFunction(json) - @endcode -*/ -int s2_cb_json_parse_string( cwal_callback_args const * args, cwal_value **rv ); - -/** - The file-based counterpart of s2_cb_json_parse_string(). It works - identically except that it takes a filename as input instead of a - JSON string. - - This callback honors the S2_DISABLE_FS_READ limitation, but it also - suggests that we may want a less-strict fs-read-disabling option - which allows JSON (since JSON is known to not allow execution of - foreign code nor loading of non-JSON content). -*/ -int s2_cb_json_parse_file( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl which works like getenv(3). Expects one - string argument (throws if it does not get one) and returns (via - *rv) either a string or the undefined value. -*/ -int s2_cb_getenv( cwal_callback_args const * args, cwal_value **rv ); - -/** - Installs JSON functionality into the given target value or (if (key - && *key)) a new Object property (with the given name) of that target - value. The target must be a container type. - - The functions installed: - - Object parse(String|Buffer jsonString) - - Object parseFile(String filename) - - string stringify(Value [, int indentation=0]) - - mixed clone(object|array) is equivalent to parse(stringify(value)), - but is defined in such a way that its "this" need not be this JSON - module. i.e. the function reference can be copied and used - independently of the target object, regardless of whether - target[parse] and target[stringify] are currently visible symbols. - - Returns CWAL_RC_TYPE if target is not a container-capable type. - - Returns 0 on success. -*/ -int s2_install_json( s2_engine * se, cwal_value * target, - char const * key); - -/** - If e was created in conjunction with an s2_engine and bound as its - client state using &s2_engine_empty as the type ID, this function - returns it, else it returns 0. That binding happens during - s2_engine_init(), so it will be set if e was successfully processed - via that routine. -*/ -s2_engine * s2_engine_from_state( cwal_engine * e ); - -/** - Equivalent to s2_engine_from_state(args->engine). - - This is intended for use in cwal_callback_f() implementations, for - the case that they need the underlying s2_engine (most don't). -*/ -s2_engine * s2_engine_from_args( cwal_callback_args const * args ); - - -/** - The "Path Finder" class is a utility for searching the filesystem - for files matching a set of common prefixes and/or suffixes - (i.e. directories and file extensions). - - @see s2_new_pf() - @see s2_pf_value() - @see s2_value_pf() - @see s2_value_pf_part() - @see s2_pf_dir_add() - @see s2_pf_dir_add_v() - @see s2_pf_dirs() - @see s2_pf_dirs_set() - @see s2_pf_ext_add() - @see s2_pf_ext_add_v() - @see s2_pf_exts_set() - @see s2_pf_exts() - @see s2_pf_search() -*/ -typedef struct s2_pf s2_pf; - -/** - Creates a new PathFinder instance. PathFinders are bound to cwal as - cwal_native instances and are initially owned by the currently - active scope. Returns NULL on allocation error. -*/ -s2_pf * s2_pf_new(s2_engine * se); - -/** - Returns the underlying cwal_value which acts as pf's "this". - - pf may not be NULL. - - @see s2_value_pf() - @see s2_value_pf_part() -*/ -cwal_value * s2_pf_value(s2_pf const * pf); - -/** - If v is-a PathFinder or derives from it, this function returns the - s2_pf part of v or one of its prototypes. - - It is legal for v to be NULL. - - @see s2_value_pf() - @see s2_pf_value() -*/ -s2_pf * s2_value_pf_part(cwal_value const *v); - -/** - If v was created via s2_pf_new() then this function returns - its s2_pf counterpart, else it returns NULL. - - It is legal for v to be NULL. - - @see s2_pf_value() - @see s2_value_pf_part() -*/ -s2_pf * s2_value_pf(cwal_value const * v); - -/** - Adds a directory to pf's search path. dir must be at least dirLen bytes - and may be an empty but may not be NULL. - - Returns 0 on success. - - @see s2_pf_dir_add_v() -*/ -int s2_pf_dir_add( s2_pf * pf, char const * dir, cwal_size_t dirLen); - -/** - Adds a file suffix (extension) to pf's search path. ext must be at - least extLen bytes and may be an empty but may not be NULL. - - Returns 0 on success. - - @see s2_pf_ext_add_v() -*/ -int s2_pf_ext_add( s2_pf * pf, char const * ext, cwal_size_t extLen); - -/** - Variant of s2_pf_dir_add() which takes its directory part in the - form of a cwal_value. - - Returns 0 on success. -*/ -int s2_pf_dir_add_v( s2_pf * pf, cwal_value * v ); - -/** - Variant of s2_pf_ext_add() which takes its directory part in the - form of a cwal_value. - - Returns 0 on success. -*/ -int s2_pf_ext_add_v( s2_pf * pf, cwal_value * v ); - -/** - Replaces pf's directory list with the given one. - - Returns 0 on success. -*/ -int s2_pf_dirs_set( s2_pf * pf, cwal_array * ar ); - -/** - Replaces pf's extension/suffix list with the given one. - - Returns 0 on success. -*/ -int s2_pf_exts_set( s2_pf * pf, cwal_array * ar ); - -/** - Symbolic values for use with s2_pf_search()'s final - parameter. - */ -enum s2_pf_search_policy { -/** - Indicates that ONLY directory names will be considered as matches. -*/ -S2_PF_SEARCH_DIRS = -1, -/** - Indicates that ONLY file (not directory) names will be considered - as matches. -*/ -S2_PF_SEARCH_FILES = 0, -/** - Indicates that both file and directory names will be considered as - matches. -*/ -S2_PF_SEARCH_FILES_DIRS = 1 -}; - -/** - Searches for a file whose name can be constructed by some - combination of pf's directory/suffix list and the given base name. - - The 5th argument specificies whether searching is allowed to match - directory names or not. A value of 0 means only files (not - directories) will be considered for matching purposes. A value - greater than zero means both files and directories may be - considered for matching purposes. A value less than zero means only - directories (not files) may be considered a match. (See the - s2_pf_search_policy enum for symbolic names for this policy.) - - BUG: the directory policy does not currently work on non-Unix - platforms because we don't have the code to check if a file name is - a directory for such platforms (patches are welcomed!). - - Returns NULL if !pf, !base, !*base, !baseLen, or on allocation - error (it uses/recycles a buffer to hold its path combinations). - - On success it returns a pointer to the (NUL-terminaed) path under - which it found the item and rcLen (if not NULL) will be set to the - length of the returned string. The bytes of the returned string are - only valid until the next operation on pf, so copy them if you need - them. - - If no match is found, rcLen is not modified. - - By default the host platform's customary path separator is used to - separate directory/file parts ('\\' on Windows and '/' everywhere - else). To change this, set the "separator" property of pf to a - string value (even an empty one, in which case the directory paths - added to pf should have the trailing separator added to them in - order for searching to work). - - Pedantic sidebar: if the search path is empty, a match can still be - found if the base name by itself, or in combination with one of the - configured extensions, matches an allowed type of filesystem entry - (as designated via the final argument). - - @see s2_pf_search_policy -*/ -char const * s2_pf_search( s2_pf * pf, char const * base, - cwal_size_t baseLen, cwal_size_t * rcLen, - int directoryPolicy); - - -/** - Returns pf's list of directories, creating it if needed. Only - returns NULL if !pf or on allocation error. - - In script space this value is available via the "prefix" property. -*/ -cwal_array * s2_pf_dirs(s2_pf *pf); - -/** - Returns pf's list of extensions/suffixes, creating it if needed. Only returns - NULL if !pf or on allocation error. - - In script space this value is available via the "suffix" property. -*/ -cwal_array * s2_pf_exts(s2_pf *pf); - -/** - A cwal_callback_f() implementing a constructor of PathFinder (s2_pf) - instances. On success, assigns the new instance to *rv. - - Requires that s2_engine_from_args() returns non-NULL. - - Script usage: - - var pf = ThisFunction() - - it optionally takes up to two array arguments for the - directory/extension lists, respectively. - - This is a holdover from before the 'new' keyword was added. -*/ -int s2_cb_pf_new( cwal_callback_args const * args, cwal_value **rv ); - -/** - Installs an Object named PathFinder (the s2_prototype_pf() object) - into the given value (which must be a container type). Returns 0 on - success, CWAL_RC_MISUSE if !se or !ns, CWAL_RC_TYPE if ns is not a - container, and CWAL_RC_OOM if allocating any component fails. -*/ -int s2_install_pf( s2_engine * se, cwal_value * ns ); - -/** - Returns the prototype object for PathFinder instances. That - instance gets stashed away in se. Ownership of the returned pointer - is unchanged. The caller MUST NOT unreference it - (cwal_value_unref() or cwal_value_unhand()) unless he explicitly - obtains a reference. -*/ -cwal_value * s2_prototype_pf(s2_engine *se); - -/** - Callback signature for s2 module import routines. - - When called by s2_module_load(), s2_module_init(), or similar, this - function type is passed the associated s2_engine instance and the - client-provided module result value address. - - Implementations "should" (by convention) return their module by - assigning it to *module. Optionally, they may use the s2_engine's - facilities to store the functionality long-term (in terms of value - lifetimes), e.g. using s2_stash_set(), or even forcing them into - the top-most scope. In any case, they should, on success, assign - some result value to *module, even if it's NULL. Note, however, - that NULL is not generally a useful result value. Most modules - return a "namespace object" which contains the module's - functionality. - - When assigning to *module, the API expects that this function will - not hold any extraneous references to the returned value. i.e. if - it's a new Value with no circular references, its refcount "should" - be zero when *module is assigned to and 0 is returned. The numerous - sample modules provide examples of how to do this properly. - - @see s2_module_load() -*/ -typedef int (*s2_module_init_f)( s2_engine * se, cwal_value ** module ); - -/** - Holds information for mapping a s2_module_init_f to a name. - Its purpose is to get installed by the S2_MODULE_xxx family of - macros and referenced later via a module-loading mechanism. -*/ -struct s2_loadable_module{ - /** - Symbolic name of the module. - */ - char const * name; - - /** - The initialization routine for the module. - */ - s2_module_init_f init; -}; - -/** Convenience typedef. */ -typedef struct s2_loadable_module s2_loadable_module; - -/** - If compiled without S2_ENABLE_MODULES then this function always - returns CWAL_RC_UNSUPPORTED and updates the error state of its - first argument with information about that code. - - Its first argument is the controlling s2_engine. - - Its second argument is the name of a DLL file. - - Its third argument is the name of a symbol in the given DLL which - resolves to a s2_loadable_module pointer. It may be NULL, in which - case a default symbol name is used (which is only useful when - plugins are built one per DLL). - - The final parameter is the target for the module's result value, - and it may not be NULL (but the value it points to should initially - be NULL, as it will be overwritten). It is passed directly to the - module's s2_loadable_module::init() function, which is responsible - for (on success) assigning *mod to the value the module wants to - return. - - This function tries to open a DLL named fname using the system's - DLL loader. If none is found, CWAL_RC_NOT_FOUND is returned and the - s2_engine's error state is populated with info about the error. If - one is found, it looks for a symbol in the DLL: if symName is not - NULL and is not empty then the symbol "s2_module_symName" is - sought, else "s2_module". (e.g. if symName is "foo" then it - searches for a symbol names "s2_module_foo".) If no such symbol is - found then CWAL_RC_NOT_FOUND (again) is returned and the - s2_engine's error state is populated, else the symbol is assumed to - be a (s2_loadable_module*), its init() function is called, and its - result is returned to the caller of this function. - - On error, this routine generally updates the s2_engine's error - state with more info (e.g. the name of the symbol on a symbol - lookup failure). Not all errors update the engine's error state, - only those with more information to convey than the result code. On - error, the result Value (final parameter) is not modified. - - Returns 0 on success. - - Note that the API provides no mechanism for unloading DLLs because - it is not generically possible to know if it is safe to do - so. Closing a DLL whose resources (e.g. a native class definition - for a client-bound type) are still in use leads, of course, to - undefined results. The caveat, however, is that because dlopen() - and friends allocate memory when we open DLLs, and we don't close - them, valgrind reports this (rightfully) as a leak. It is not so - much a leak as it is a required safety net. That said, the - interpreter will close all DLLs it opened (or believes it opened) - when it is finalized. That, however, opens up another potential - problem: interpreters will close a DLL one time for each time they - opened it. How the underlying (system-level) module API deals with - that is up to that API. The dlopen()-based and lt_dlopen()-based - implementations are safe in that regard (at least on Linux, - according to their man pages and a peek at their sources). - - In practice this routine is not called by client C code, but is - instead called indirectly via s2_cb_module_load(), which is a - script-side binding of this function. - - @see s2_cb_module_load() - @see S2_MODULE_DECL - @see S2_MODULE_IMPL - @see S2_MODULE_REGISTER - @see S2_MODULE_REGISTER_ -*/ -int s2_module_load( s2_engine * se, char const * fname, - char const * symName, cwal_value ** mod ); - -/** - Behaves similarly to s2_module_load(), and its first 3 parameters - are used as documented for that function, but this variant does not - invoke the init() method of the module before returning that module - via *mod. - - On success *mod is set to the module object. Its ownship is kinda - murky: it lives in memory made available via the module loader. It - remains valid memory until the DLL is closed. - - Returns 0 on success. On error, se's error state may contain more - information. - - After calling this, the next call would typically be - s2_module_init(). - - @see s2_module_load() - @see s2_module_init() -*/ -int s2_module_extract( s2_engine * se, - char const * dllFileName, - char const * symName, - s2_loadable_module const ** mod ); - -/** - This function pushes a new cwal scope, calls mod->init(), and - propagates any result value from that routine back out of that new - scope via *rv. On error *rv is not modified. If rv is NULL then the - Value result of the module init is ignored (destroyed before this - routine returns unless the module stores is somewhere long-lived), - but any integer result code of the init routine is propagated back - to the caller of this function. - - @see s2_module_load() - @see s2_module_extract() -*/ -int s2_module_init( s2_engine * se, - s2_loadable_module const * mod, - cwal_value ** rv); - -/** @def S2_MODULE_DECL - - Declares an extern (s2_loadable_module*) symbol called - s2_module_#\#NAME. - - Use S2_MODULE_IMPL to create the matching implementation - code. - - This macro should be used in the C or H file for a loadable module. - It may be compined in a file with a single S2_MODULE_IMPL1() - declaration with the same name, such that the module can be loaded - both with and without the explicit symbol name. - - @see S2_MODULE_IMPL - -*/ -#define S2_MODULE_DECL(NAME) \ - extern const s2_loadable_module * s2_module_##NAME - -/** @def S2_MODULE_IMPL - - Intended to be used to implement module declarations. If a module - has both C and H files, S2_MODULE_DECL(NAME) should be used in the - H file and S2_MODULE_IMPL() should be used in the C file. If the - DLL has only a C file (or no public H file), S2_MODULE_DECL is - unnecessary. - - Implements a static s2_loadable_module object named - s2_module_#\#NAME#\#_impl and a non-static (s2_loadable_module*) - named s2_module_#\#NAME which points to - s2_module_#\#NAME#\#_impl. (The latter symbol may optionally be - declared in a header file via S2_MODULE_DECL.) - - INIT_F must be a s2_module_init_f() function pointer. That function - is called when s2_module_load() loads the module. - - This macro may be combined in a file with a single - S2_MODULE_IMPL1() declaration using the same NAME value, such that - the module can be loaded both with and without the explicit symbol - name. - - Example usage, in a module's header file, if any: - - @code - S2_MODULE_DECL(cpdo); - @endcode - - (The declaration is not strictly necessary - it is more of a matter - of documentation.) - - And in the C file: - - @code - S2_MODULE_IMPL(cpdo,cpdo_module_init); - @endcode - - If it will be the only module in the target DLL, one can also add - this: - - @code - S2_MODULE_IMPL1(cpdo,cpdoish_install_to_interp); - // _OR_ (every so slightly different): - S2_MODULE_STANDALONE(cpdo,cpdoish_install_to_interp); - @endcode - - Which simplifies client-side module loading by allowing them to - leave out the module name when loading, but that approach only - works if modules are compiled one per DLL (as opposed to being - packaged together in one DLL). - - @see S2_MODULE_DECL - @see S2_MODULE_IMPL1 -*/ -#define S2_MODULE_IMPL(NAME,INIT_F) \ - static const s2_loadable_module \ - s2_module_##NAME##_impl = { #NAME, INIT_F }; \ - const s2_loadable_module * s2_module_##NAME = &s2_module_##NAME##_impl - - -/** @def S2_MODULE_IMPL1 - - Implements a static "v1-style" s2_loadable_module symbol called - s2_module_impl and a non-static (s2_loadable_module*) named - s2_module which points to s2_module_impl - - INIT_F must be a s2_module_init_f. - - This macro must only be used in the C file for a loadable module - when that module is to be the only one in the resuling DLL. Do not - use it when packaging multiple modules into one DLL: use - S2_MODULE_IMPL for those cases (S2_MODULE_IMPL can also be used - together with this macro). - - @see S2_MODULE_IMPL - @see S2_MODULE_DECL - @see S2_MODULE_STANDALONE_IMPL -*/ -#define S2_MODULE_IMPL1(NAME,INIT_F) \ - static const s2_loadable_module \ - s2_module_impl = { #NAME, INIT_F }; \ - const s2_loadable_module * s2_module = &s2_module_impl - -/** @def S2_MODULE_STANDALONE_IMPL - - S2_MODULE_STANDALONE_IMPL() works like S2_MODULE_IMPL1() but is - only fully expanded if the preprocessor variable - S2_MODULE_STANDALONE is defined (to any value). If - S2_MODULE_STANDALONE is not defined, this macro expands to a dummy - placeholder which does nothing (but has to expand to something to - avoid leaving a trailing semicolon in the C code, which upsets the - compiler (the other alternative would be to not require a - semicolon after the macro call, but that upsets emacs' sense of - indentation)). - - This macro may be used in the same source file as S2_MODULE_IMPL. - - The intention is that DLLs prefer this option over - S2_MODULE_IMPL1, to allow that the DLLs can be built as standalone - DLLs, multi-plugin DLLs, and compiled directly into a project (in - which case the code linking it in needs to resolve and call the - s2_loadable_module entry for each built-in module). - - @see S2_MODULE_IMPL1 - @see S2_MODULE_REGISTER -*/ -#if defined(S2_MODULE_STANDALONE) -# define S2_MODULE_STANDALONE_IMPL(NAME,INIT_F) S2_MODULE_IMPL1(NAME,INIT_F) -#else -# define S2_MODULE_STANDALONE_IMPL(NAME,INIT_F) \ - extern void _s2_module_dummy_does_not_exist_() -#endif - -/** - Performs all the necessary setup for a v2-style module, including - declaration and definition. NAME is the name of the module. This is - normally called immediately after defining the plugin's init func - (which is passed as the 2nd argument to this macro). - - See S2_MODULE_IMPL() and S2_MODULE_STANDALONE_IMPL() for - the fine details. -*/ -#define S2_MODULE_REGISTER(NAME,INIT_F) \ - S2_MODULE_IMPL(NAME,INIT_F); \ - S2_MODULE_STANDALONE_IMPL(NAME,INIT_F) - -/** - Functionally equivalent to: - S2_MODULE_REGISTER(NAME, s2_module_init_#\#NAME). -*/ -#define S2_MODULE_REGISTER_(NAME) \ - S2_MODULE_IMPL(NAME,s2_module_init_##NAME); \ - S2_MODULE_STANDALONE_IMPL(NAME,s2_module_init_##NAME) - - -/** - cwal_callback_f() impl which wraps s2_module_load(). - - Script-side usages: - - // For single-module DLLs: - var module = loadModule("filename"); - // Or, for multi-module DLLs: - var module = loadModule("filename", "symbolName"); - - On success it returns the module's value (which can be anything), - or the undefined value if the module returns nothing (which would be - unusual). - - If passed no symbol name, it assumes that the DLL is a - single-module DLL and uses the symbol name "s2_module". If passed a - symbol name, it looks for "s2_module_SYMBOL_NAME". If such a symbol - is found it is assumed to be a (s2_loadable_module const *) and its - init() function is called. - - On success 0 is returned. On error it throws or returns a - lower-level error code (e.g. CWAL_RC_OOM). - - Achtung: there is no module caching going on here, and loading a - module multiple times may be expensive or confusing (the returned - objects from separate calls will, unless the module itself somehow - caches results, be different instances). - - Achtung: this binding requires that s2_engine_from_args() return - non-0 (it will do so if args->engine is managed by an s2_engine - instance). -*/ -int s2_cb_module_load( cwal_callback_args const * args, - cwal_value **rv ); - -#if 0 -/* The FFI API was removed because it's unmaintained: see - mod/_attic/s2_ffi.c */ - -/** - A cwal_callback_f() implementation which allows calls to - near-arbitrary C functions using FFI (Foreign Function Interface: - https://en.wikipedia.org/wiki/Foreign_function_interface). - - Achtung: this is probably the most dangerous thing that a scripting - engine could ever be allowed to do, and is implemented purely for - educational purposes. - - Script-side usage: - - ffiCall("symbolName"); // for void function with no args - ffiCall("symbolName", returnType); // for functions with no args - ffiCall("symbolName", [ returnType, argType... ], args...); - // e.g. - ffiCall("open", [ FFI_INT, FFI_PTR, FFI_INT], "/etc/fstab", 0); - - On success this calls the foreign function and returns its result. - On error it throws and returns an error code. -*/ -int s2_cb_ffi_exec( cwal_callback_args const * args, cwal_value **rv ); -#endif - -/** - Pushes one level of output buffer into se's output buffer stack. - Buffering works similarly to PHP's ob_start() (and friends) support. - While a buffer is active, all output send to cwal_engine_output() - and friends is redirected to a buffer. The various s2_ob_xxx() - functions can be used to: - - - fetch or discard the contents - - push a new buffer onto the stack - - pop the buffer from the stack (discarding its contents) - - When the interpreter is shut down it automatically removes any - pushed buffers, but clients should call s2_ob_pop() once - for each time they call s2_ob_push() - - Returns 0 on success, CWAL_RC_MISUSE if !se, CWAL_RC_RANGE if there - has been no corresponding call to s2_ob_push(). - - Results of the whole s2_ob_XXX() API are undefined if another API - manipulates the contents of the underlying cwal_engine's output - redirection bits (i.e. cwal_engine_vtab::outputer via - cwal_engine::vtab). - - @see s2_ob_pop() - @see s2_ob_get() - @see s2_ob_take() - @see s2_ob_clear() - @see s2_ob_level() - @see s2_ob_flush() -*/ -int s2_ob_push( s2_engine * se ); - -/** - Attempts to reserve at least reserveBufSize bytes of memory for the - current buffering level. This does not change the buffering level. - - Returns 0 on success, CWAL_RC_MISUSE if !se, CWAL_RC_RANGE if - s2_ob_push() has not previously been called, and CWAL_RC_OOM if - allocation of new memory fails. - - @see s2_ob_push() - */ -int s2_ob_reserve( s2_engine * se, cwal_size_t reserveBufSize ); - -/** - Removes the current level of output buffer from ie. - - Returns 0 on success, CWAL_RC_MISUSE if !ie, CWAL_RC_RANGE if there - has been no corresponding call to s2_ob_push(). -*/ -int s2_ob_pop( s2_engine * se ); - -/** - Returns the current buffering level, or 0 if !ie or ie is - not in buffering mode. - - @see s2_ob_push() - @see s2_ob_pop() -*/ -cwal_size_t s2_ob_level( s2_engine * se ); - -/** - Gets a pointer to the raw buffer owned by the current level of - output buffer, assigning it to *tgt. The buffer is owned by the OB - layer and its contents may be modified on any API routines which - end up calling cwal_engine_output() or the other s2_ob_xxx() - APIs. The caller is intended to copy/use the buffer's contents - immediately, and not hold on to it past the current operation. - - Returns 0 on success, CWAL_RC_MISUSE if !ie or !tgt, CWAL_RC_RANGE - if there has been no corresponding call to s2_ob_push(). -*/ -int s2_ob_get( s2_engine * se, cwal_buffer ** tgt ); - -/** - Like s2_ob_get(), but moves the contents of the current - buffer layer into tgt, clearing the OB buffer but leaving - it on the buffer stack for later use. - - Returns 0 on success, CWAL_RC_MISUSE if !se or !tgt, CWAL_RC_RANGE - if there has been no corresponding call to s2_ob_push(). - - tgt must be empty-initialized or the caller must call - cwal_buffer_reserve(..., tgt, 0) before calling this or memory may - leak. On success ownership of the memory in tgt->mem is transfered - to the caller. If tgt was created via cwal_new_buffer() or - cwal_new_buffer_value() then tgt and tgt->mem are owned by se->e. -*/ -int s2_ob_take( s2_engine * se, cwal_buffer * tgt ); - -/** - Clears the contents of the current buffering layer. If - releaseBufferMem is true (non-0) then the buffer memory is - deallocated, otherwise it is just reset for later use by the OB - layer. If it is deallocated, it will be re-allocated later if more - output is buffered. - - Returns 0 on success, CWAL_RC_MISUSE if !ie, CWAL_RC_RANGE - if there has been no corresponding call to s2_ob_push(). -*/ -int s2_ob_clear( s2_engine * se, char releaseBufferMem ); - -/** - Pushes the current contents of the output buffer layer to the next - output destination in the stack and the current level is cleared of - contents (but stays on the stack). If the next outputer is a buffer - then the current buffer is appended to it, otherwise it is sent to - the originally configured output destination. - - Returns 0 on success, CWAL_RC_MISUSE if !ie, CWAL_RC_RANGE - if there has been no corresponding call to s2_ob_push(), - and potentially some other error if flushing to the lower-level - implementation fails. - - @see s2_ob_push() - @see s2_ob_pop() -*/ -int s2_ob_flush( s2_engine * se ); - - -/** - cwal_callback_f() impl wrapping s2_ob_push(). Requires that - args->state be a (s2_engine*). Returns argv->self. - - Accepts an optional integer argument which specifies an amount of - memory to pre-allocate for the buffer (see s2_ob_reserve()). - - On error this function returns with an unchanged buffer level. -*/ -int s2_cb_ob_push( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl wrapping s2_ob_pop(). Requires - that args->state be a (s2_engine*). - - Script signature: - - @code - mixed pop([int takePolicy=0]) - @endcode - - If passed no args or a 0/falsy value, it discards any buffered - output. If passed numeric greater than 0 then it returns (via *rv) - the content as a Buffer. If passed numeric negative then it returns - the contents as a String. - -*/ -int s2_cb_ob_pop( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl wrapping s2_ob_reserve(). Requires - that args->state be a (s2_engine*). Returns argv->self. -*/ -int s2_cb_ob_reserve( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl wrapping s2_ob_get(). Requires - that args->state be a (s2_engine*). - - Assigns *rv to the string contents of the buffer layer. -*/ -int s2_cb_ob_get( cwal_callback_args const * args, cwal_value **rv ); -/** - cwal_callback_f() impl wrapping s2_ob_clear(). Requires - that args->state be a (s2_engine*). Returns argv->self. -*/ -int s2_cb_ob_clear( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl wrapping s2_ob_take(). Requires - that args->state be a (s2_engine*). - - Assigns *rv to the string contents of the buffer layer. - - Design note: the returned string is actually a z-string to avoid - having to make another copy of the data. -*/ -int s2_cb_ob_take_string( cwal_callback_args const * args, cwal_value **rv ); - -/** - Functionally identical to s2_cb_ob_take_string() except that it - returns (via *rv) a cwal_buffer value (owned by args->engine). -*/ -int s2_cb_ob_take_buffer( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl wrapping s2_ob_flush(). Requires - that args->state be a (s2_engine*). Returns argv->self. -*/ -int s2_cb_ob_flush( cwal_callback_args const * args, cwal_value **rv ); - -/** - cwal_callback_f() impl for... - - mixed capture(string|function callback - [, int captureMode=-1 | buffer captureTarget]) - - Which does: - - 1) Push an OB level. - - 2) Runs the given callback. If it's a function, it is call()ed. If it - is a string, it is eval'd. Any other type, including a buffer, triggers - an error. - - 3) If the 2nd argument is a buffer, all captured output is appended - to that buffer and that buffer is returned. If it's not a buffer, - it's interpreted as an integer with the same semantics as pop()'s - argument but with a different default value: if it's negative (the - default) then the captured buffered output is returned as a string, - positive returns the result as a new buffer, and 0 means to simply - discard the result. - - 4) Pops its buffer. - - If the callback leaves the buffer stack count with fewer levels - than than what were active when the callback was triggered, steps - (3) and (4) are skipped and an exception is triggered. If it pushes - extra levels, they are assumed to be part of the output: this - function flushes and pops each one, and captures or discards the - cumulative output. - - The main advantage to this approach to capturing output, over - manually pushing and popping OB levels, is that this function keeps - the levels in sync even in the face of an s2-level - assert/exit/fatal call, cwal/s2 OOM condition, s2_interrupt(), or - similar "flow-control event." -*/ -int s2_cb_ob_capture( cwal_callback_args const * args, cwal_value **rv ); - -/** - Installs the following functions into tgt (which must be a property - container type), all of which correspond to a similarly named - s2_ob_XXX() resp. s2_cb_ob_XXX() function: - - push(), pop(), getString(), takeString(), takeBuffer(), clear(), - flush(), capture() - - Returns 0 on success. On error tgt might have been partially - populated. - - Returns CWAL_RC_MISUSE if !ie or !tgt, CWAL_RC_TYPE if tgt - is not a container type. -*/ -int s2_install_ob( s2_engine * se, cwal_value * tgt ); - - /** - Variant of s2_install_ob() which installs the OB functionallity - into a new object with the given name, and places that object in - tgt. Returns 0 on success. -*/ -int s2_install_ob_2( s2_engine * se, cwal_value * tgt, - char const * name ); - - - /** - Installs various io-related APIs. If name is not NULL and not - empty, then the APIs get installed in a new object named tgt[name], - else the functions are installed directly in tgt (which must be a - container type). - - Returns 0 on success. - - Functions installed by this: - - print(), output(), flush() -*/ -int s2_install_io( s2_engine * se, cwal_value * tgt, - char const * name ); - - /** - Installs various filesystem-related APIs which are largely - platform-dependent. If name is not NULL and not empty, then the - APIs get installed in a new object named tgt[name], else the - functions are installed directly in tgt (which must be a container - type). - - Returns 0 on success. - - Functions installed by this: - - chdir(), getcwd(), mkdir(), realpath(), stat(), fileIsAccessible(), - dirIsAccessible(). - - All of these functions are affected by the s2_disable_get() flags - S2_DISABLE_FS_STAT, and mkdir() is also affected by - S2_DISABLE_FS_WRITE. -*/ -int s2_install_fs( s2_engine * se, cwal_value * tgt, - char const * name ); - -/** - A cwal_callback_f() which expects its first argument to be an - integer. It tries to sleep for at least that many seconds and - returns the number of seconds left to sleep if it is interrupted - (as per sleep(3)). - - @see s2_install_time() - */ -int s2_cb_sleep(cwal_callback_args const * args, cwal_value ** rv); - -/** - A cwal_callback_f() which expects its first argument to be an - integer. It sleeps for that many milliseconds. It throws an - exception if usleep(3) fails or if the library is built without - usleep(3) support. It returns the undefined value. - - @see s2_install_time() -*/ -int s2_cb_mssleep(cwal_callback_args const * args, cwal_value ** rv); - -/** - Installs the following script-side functions: - - sleep(), mssleep(), time(), mstime(), strftime() - - If name is not 0 and *name is not 0 then a new property with that - name, of type object, is installed to tgt, and the functions are - stored there, otherwise the functions are installed directly into - tgt. Returns 0 on success. On error non-0 is returned (a - CWAL_RC_xxx value) and, depending on where the error happened, the - module may or may not have been partially installed in the target - object. The only "likely" (for a given definition of "likely") - error from this function, assuming all arguments are valid, is - CWAL_RC_OOM, indicating that it ran out of memory. Returns - CWAL_RC_MISUSE if either the first or second argument are NULL, and - CWAL_RC_TYPE if !cwal_props_can(tgt). - - @see s2_cb_sleep() - @see s2_cb_mssleep() - @see s2_cb_time() - @see s2_cb_mstime() - @see s2_cb_strftime() -*/ -int s2_install_time( s2_engine * se, cwal_value * tgt, char const * name ); - - - -/** - Flags for use with s2_tmpl_opt::flags. -*/ -enum s2_tmpl_flags_e { -/** - Indicates that the output function header definition - which checks for and optionally defines the function - TMPLOUT (used by the processed template to output - its content) should be elided (i.e. not output). - */ -S2_TMPL_ELIDE_TMPLOUT = 0x01 -}; - -typedef struct s2_tmpl_opt s2_tmpl_opt; -/** - Holds options for the s2_tmpl_to_code() function. Clients - must initialize them by copying either s2_tmpl_opt_empty or - (for const contexts) s2_tmpl_opt_empty_m. */ -struct s2_tmpl_opt { - /** - 0 (for no flags) or a bitmask of values from - the s2_tmpl_flags_e enum. - */ - int flags; - /** - If this is not 0 and not empty then: - - (A) the flag S2_TMPL_ELIDE_TMPLOUT - is implied - - (B) this specifies the script function name which will be - called when the processed template is eval'd, to emit its - output. - - If 0 then "TMPLOUT" is used and (A) does not apply. - */ - char const * outputSymbolPublic; - - /** - In the processed output, the outputSymbolPublic name is only - used in the header and aliased to a shorter symbol (so that the - output will, for non-trivial cases, be shorter). This member - specifies the name it uses after initialization. If it is 0 or - starts with a NUL byte then some unspecified (but short) - default is used. It is recommended that clients (if they use - this) use weird non-ASCII UTF8 character combinations to avoid - any potential symbol collisions. - - If this is not NULL and has a length greater than 0 then this - alias is used in place of the default, and is initialized as - a scope-local variable in the header of the template if - it has not previously been declared in that scope. - - The likely only reason this should be overridden is that - 1-in-a-gazillion chance that a template actually uses a symbol - which collides with this one's default value. - */ - char const * outputSymbolInternal; - - /** - Similar to outputSymbolInternal, this specifies the name of the - processed-template-internal heredoc delimiter. By default (if - this is 0) some cryptic combination of non-ASCII UTF8 character - is used. - */ - char const * heredocId; - - /** - The opening tag for "code" blocks. Default is "". - If set, then tagCodeOpen must also be set. Must differ - from all other tag open/close entries. - */ - char const * tagCodeClose; - - /** - The opening tag for "value" blocks. Default is "<%". - If set, then tagValueClose must also be set. Must differ - from all other tag open/close entries. - */ - char const * tagValueOpen; - - /** - The opening tag for "value" blocks. Default is "%>". - If set, then tagValueOpen must also be set. Must differ - from all other tag open/close entries. - */ - char const * tagValueClose; -}; - -/** - An initialized-with-defaults instance of s2_tmpl_opt, - intended for const-copy initialization. -*/ -#define s2_tmpl_opt_empty_m {0,0,0,0,0,0,0,0} - -/** - An initialized-with-defaults instance of s2_tmpl_opt, - intended for copy initialization. -*/ -extern const s2_tmpl_opt s2_tmpl_opt_empty; - -/** - Implements a very basic text template processing mechanism for - th1ish. - - The e arg must be a valid cwal_engine instance. - - src must be the template source code to process. It is treated as - nearly-opaque text input which may contain markup tags (described - below) to embed either code blocks or values into the output. - - dest is where all output is appended (the buffer is not reset by - this function). - - The opt parameter may be 0 (for default options) or an object which - configures certain parts of the template processing, as described - in the s2_tmpl_opt docs. - - Returns 0 on success, non-0 on error. Error codes include: - - - CWAL_RC_MISUSE if !e, !src, or !dest. - - - CWAL_RC_RANGE if any of the open/close tags specified in the opt - parameter are invalid (empty strings or validate rules described in - the s2_tmpl_opt docs). - - - CWAL_RC_OOM on allocation errors. - - - CWAL_RC_EXCEPTION if it is reporting an error via a cwal - exception. On code generation errors it throws an exception in - the context of e, containing information about the nature and - location (in the original source) of the problem. - - That said, it may not catch many conceivable malformed content - cases and in such cases may generate malformed (as in not - eval'able) code. - - - Template processing... - - (Note that while these docs use fixed tag names, the exact - tags can be configured via the opt parameter.) - - The output starts with a document prefix which sets up output of - the text parts of the page. - - All non-code parts of src are filtered to be output wrapped in - individual HEREDOCs embedded in the output script. All code parts - of src are handled as follows: - - '' tag. This ends any current HEREDOC and passes through - the code as-is to dest. It does not generate any output in the - processed document unless the embedded code generates it. - - '<%' (without the quotes) starts a "value block," which is - processed a little bit differently. The contents between that and - the next '%>' tag are simply passed to the configured output - routine (see below). - - An example input document should clear this up: - - @code - Hi, world! - - x = <%x%>, y = <% y %>, x+y=<% x + y %> - @endcode - - The generated code is an s2 script which, when run (via eval, - scope, catch...), outputs a processed document. All non-script - parts get wrapped in HEREDOCs for output. - - The generated code "should" evaluated in a scope of its own, but it - can be run in the current scope if desired. The code relies on an - output function being defined (resolvable in the evalution scope). - That function, if not specified via the opt parameter, is called - TMPLOUT. No name is specified and the symbol TMPLOUT is undefined - (when the processed template is eval'd), it uses s2out as its - default output function (prior to 20191210 it uses - s2.io.output). The function must accept any number of Value type - parameters and output them "in its conventional string form" - (whatever that is). It must not perform any formatting such as - spaces between the entries or newlines afterwards. It may define - formatting conventions for values passed to it (e.g. it may feel - free to reformat doubles to a common representation). - - The generator outputs some weird/cryptic UTF8 symbols as heredoc - markers. It's conceivable, though very unlikely, that these could - collide with symbols in the document for heredoc processing - purposes. - - Whitespace handling: - - - If the script starts with and blocks retains whitespace to the - left of the openener and right of the closer, so {abc<%x%>def} will - form a single output token (provided 'x' evaluates to such), where - {abc <%x%> def} will generate three. Inside the blocks, all - whitespace is retained. Inside <% %> blocks, the contents are - treated as if they were inside a normal HEREDOC, so their - leading/trailing spaces are stripped BUT they are not significant - - the _result_ of evaluating the <% %> content gets output when - executed, not the content itself. - - TODOs: - - - a variant which takes a cwal_output_f() instead of a buffer. -*/ -int s2_tmpl_to_code( cwal_engine * e, cwal_buffer const * src, - cwal_buffer * dest, s2_tmpl_opt const * opt ); - -/** - A cwal_callback_f() binding for s2_tmpl_to_code(). It - expects one string/buffer argument containing tmplish code and it - returns a new Buffer value containing the processed code. Throws on - error. - - Script usage: - - var compiled = thisFunction(templateSource [, optObject]) - - If optObject is-a Object then the following properties may - influence template processing: - - - valueOpen and valueClose specify the open/close tags for - Value Blocks. - - - codeOpen and codeClose specify the open/close tags for - Code Blocks. - - - outputSymbol sets the name of the symbol the generated tmpl() - code will (when eval'd) use for output. It may be a compound - symbol, e.g. "s2.io.output" or even a function call, e.g. - "proc(){return s2.io.output}()" - anything which is legal as the - right-hand side of an assignment is (syntactically) legal - here. That assignment will be called once each time the resulting - template script is eval'd. -*/ -int s2_cb_tmpl_to_code( cwal_callback_args const * args, cwal_value ** rv ); - -/** - A cwal_callback_f() impl binding the C-standard time(3). - - @see s2_install_time() -*/ -int s2_cb_time( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() impl returning the current time in milliseconds - since the start of the Unix epoch. This requires platform-specific - calls and throws an exception, with code CWAL_RC_UNSUPPORTED, if - built on a platform with the required API. - - @see s2_install_time() -*/ -int s2_cb_mstime( cwal_callback_args const * args, cwal_value **rv ); - -/** - A strftime() implementation. - - dest must be valid memory at least destLen bytes long. The result - will be written there. - - fmt must contain the format string. See the file toys/strftime.s2 - (or strftime.c, if you're more into C) for the complete list of - format specifiers and their descriptions. - - timeptr must be the time the caller wants to format. - - Returns 0 if any arguments are NULL. - - On success it returns the number of bytes written to dest, not - counting the terminating NUL byte (which it also writes). It - returns 0 on any error, and the client may need to distinguish - between real errors and (destLen==0 or !*fmt), both of which could - also look like errors. - - TODOs: - - - Refactor this to take a callback or a cwal_buffer, so that we can - format arbitrarily long output. - - - Refactor it to return an integer error code. - - (i didn't write this implementation - it is derived from public domain - sources dating back to the early 1990's.) - -*/ -cwal_midsize_t s2_strftime(char *dest, cwal_midsize_t destLen, - const char *format, const struct tm *timeptr); - - -/** - A cwal_callback_f() which wraps s2_strftime(). - - Script usage: - - @code - var tm = time(); - var str = strftime("%Y-%m-%d %H:%M:%S", tm); - @endcode - - The default time value, if no second argument is passed in or a - negative value is passed in, is the current time. - - Note that this implementation has a limit on the length of the - result string (because s2_strftime() works that way), and - throws if that length is violated. It's suitable for "usual" - time strings but not for formatting whole sentences. - - This function takes an optional boolean 3rd argument: if truthy, - local time is used, else GMT is used (the default). -*/ -int s2_cb_strftime( cwal_callback_args const * args, cwal_value **rv ); - -/** - Tries to convert an errno value to an equivalent CWAL_RC_xxx value. - First argument must be the current errno value. Returns an - equivalent, or dflt is no sematic equivalent is known. If errNo is - 0 then this function evaluates the global errno in its place. If - both are 0 then this function returns CWAL_RC_OK. -*/ -int s2_errno_to_cwal_rc(int errNo, int dflt); - -/** - A cwal_callback_f() impl when behaves like rand(3). It will call - srand(3), with some pseudo-random seed, the first time it is - called. - -*/ -int s2_cb_rand_int( cwal_callback_args const * args, cwal_value **rv ); - - -/** - cwal_callback_f() implementing fork(). On non-Unix builds this function - triggers an exception. - - Script usage: - - fork(Function) - fork(bool,Function) - - The parent process returns from that call. The child process runs - the given Function and then exits the interpreter as if the 'exit' - keyword had been used. If the first arg is a boolean true then then - child process returns (in its own process) instead of exiting, passing - back the result of the 2nd parameter (the callback function). -*/ -int s2_cb_fork( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() implementation which returns a hashtable value - which maps all of the cwal/s2-defined result code integers - (CWAL_RC_xxx and S2_RC_xxx) to strings in the same form as the - corresponding enum entry. e.g. it maps 0 to "CWAL_RC_OK". It also - holds the reverse mappings, so "CWAL_RC_OK" ==> 0. - - This function creates the hash the first time it is called and - caches its result in the s2_engine which owns args->engine, thus - the second and subsequent calls will return the same value. -*/ -int s2_cb_rc_hash( cwal_callback_args const * args, cwal_value **rv ); - -/** - A cwal_callback_f() implementation for formatting "this" - using cwal_buffer_format (resp. s2.Buffer.appendf()). - - Requires args->argv[0] to be part of a formatting string for - s2.Buffer.appendf(), namely the part after "$1%". It prepends "$1%" - to the arg string and passes that string and the argument to - cwal_buffer_format() to generate the result string. - - Triggers an exception if cwal_buffer_format() returns an error. -*/ -int s2_cb_format_self_using_arg( cwal_callback_args const * args, cwal_value **rv ); - -/** - A callback which creates and returns a new "unique" value, as per - cwal_new_unique(). If args->argc then args->argv[0] is passed to - cwal_new_unique(). -*/ -int s2_cb_new_unique( cwal_callback_args const * args, cwal_value **rv ); - -/** - Experimental! - - Intended to be called by app-level code and be passed its - shared/global s2_engine instance. This function uses global - state and is NOT thread-safe in any way, shape, or form. It - stores a copy of se. - - If se is not NULL then this installs a SIGINT handler which behaves - like s2_interrupt() (but uses a different message string). If se is - NULL then it removes any previous binding (it does not remove its - SIGINT handler, but the handler becomes a no-op if no engine is set - to be interrupted). -*/ -void s2_set_interrupt_handlable( s2_engine * se ); - -/** - This sets se's error state to CWAL_RC_INTERRUPTED, a flag it checks - for at various points during evaluation and which causes the - interpretter to behave essentially as if 'exit' had been used (but - without a result value). Calling this is not a guaranty that the - engine will stop processing its current script - there are corner - cases where the flag can get "lost" during evaluation. - - On success, returns CWAL_RC_INTERRUPTED. On error (an allocation - error generating a message string), it will return another non-0 - code (likely CWAL_RC_OOM). - - This is not strictly thread safe, but "should" be okay to call from - a separate thread (e.g. a UI) in most cases, though (depending on - timing) it might not have an effect. Known potential race - conditions include, but are not necessarily limited to: - - - se is clearing its error state (which will clear the - is-interrupted flag). It resets its error state internally for - "non-error errors" which propagate a ways, like "return" and - "throw", but those keywords "should" catch and propagate this - condition, trumping their own. The lowest-level eval handler - checks at every sensible opportunity. - - - se is cleaning up (inside s2_engine_finalize()), in which case - accessing it might (depending on the timing)lead to an illegal - memory access (if dynamically allocated) or a useless but - harmless[1] access if it's stack-allocated. [1]=so long as the - memory itself is still legal to access (e.g. app-level/static). - - - Third-party bindings may clear se's error state (which includes - this flag) indescriminately, without being aware of this - condition. - - - There are likely others. -*/ -int s2_interrupt( s2_engine * se ); - -/** - Tries to parse a given C-string as an integer or double value. - - srcLen must be the string length of str or a negative value - (in which case cwal_strlen() is used to count the length). - - If the string can be parsed to an integer or double value, *rv is - set to the newly-created value and 0 is returned. If no conversion - can be made, *rv is set to 0 and 0 is returned. On OOM error, - CWAL_RC_OOM is returned. -*/ -int s2_cstr_parse_number( cwal_engine * e, char const * str, - cwal_int_t srcLen, cwal_value ** rv ); - -/** - The reverse of s2_rc_cstr(), this function tries to find a - CWAL_RC_xxx or S2_RC_xxx error code for a string form of an enum - entry's name, e.g. "CWAL_RC_OOM". On success it returns the code - value via *code and returns non-0 (true), else it does not modify - *code and returns 0 (false). - - This is currently an O(1) operation, performing one hash - calculation and (at most) one string comparison. -*/ -char s2_cstr_to_rc(char const *str, cwal_int_t len, int * code); - -/** - Rescopes v (if necessary per the scoping rules) to one scope - up from the current cwal scope. This is almost never what you - want to do. -*/ -void s2_value_upscope( s2_engine * se, cwal_value * v ); - -/** - If cwal_value_is_unique() is true for v then this returns the value - of passing v to cwal_unique_wrapped_get(), else it returns v. - - In s2, the cwal_value_is_unique() type is the type used for enum - entries. - - @see s2_value_cstr() -*/ -cwal_value * s2_value_unwrap( cwal_value * v ); - -/** - Const-friendly brother of s2_value_unwrap(). -*/ -cwal_value const * s2_value_unwrap_c( cwal_value const * v ); - -/** - Works like cwal_value_cstr() unless cwal_value_is_unique(v) is - true, in which case it uses v's wrapped value instead of v - itself. In s2, "unique" values are most often enum entries. - - @see s2_value_unwrap() -*/ -char const * s2_value_cstr( cwal_value const * v, cwal_size_t * len ); - -/** - A helper for callbacks which honor the do-not-set-properties - flag on container values. - - If v is tagged with the flag CWAL_CONTAINER_DISALLOW_PROP_SET then - this function triggers a CWAL_RC_DISALLOW_PROP_SET exception and - returns non-0 (intended to be propagated back out of the - callback). If it does not have that flag, 0 is returned. - - If throwIt is true (non-0), the error is transformed to an exception, - otherwise it is set as a non-exception error. -*/ -int s2_immutable_container_check( s2_engine * se, cwal_value const * v, int throwIt ); - -/** - Functionally identical to s2_immutable_container_check() but extracts the - s2_engine from s2_engine_from_args(args) and always passes true - as the 3rd argument to s2_immutable_container_check(). -*/ -int s2_immutable_container_check_cb( cwal_callback_args const * args, cwal_value const * v ); - -/** - Configures various s2- and cwal-level flags for the given - container-type value. Returns 0 if it sets/clears the flag(s), - CWAL_RC_TYPE if v is not a container (not counting prototypes). - - The options are: - - allowPropSet: if true (the default) then the container mutation - APIs work as normal, otherwise those which set properties will - fail with a CWAL_RC_DISALLOW_PROP_SET error code. - - allowNewProps: if true (the default) new properties are created - normally via the various setter operations. If false, trying to set - a non-existing property will fail with a - CWAL_RC_DISALLOW_NEW_PROPERTIES error code. - - allowGetUnknownProps: if true (the default) then unknown properties - resolve to the undefined value or NULL (depending on the - context). If false, s2_get_v() and friends will trigger a - CWAL_RC_NOT_FOUND error for unknown properties. Note that - properties which resolve through a prototype are still "known" for - this purpose (they must be, or inheritance of methods could - not work). -*/ -int s2_container_config( cwal_value * v, char allowPropSet, - char allowNewProps, - char allowGetUnknownProps ); - -/** - Experimenting with with ideas for things we "could" use - per(-container-type)-Value flag bits (16 of them) in s2. cwal - gives us 16 bits per Container Value instance (not POD types, as we - can't tag those further without increasing their sizeofs, dropping - the built-in constants, or splitting the values into two - allocations, such that we could do a CoW of the built-ins if they - get flagged). See cwal_container_client_flags_get() and - cwal_container_client_flags_set(). -*/ -enum s2_just_thinking_out_loud { - -/** - An alternate encoding might be to have the high (say) 4 (or 8) bits - control the interpretation of the (say) bottom 12. e.g. - - MODE_CLASS = 0x1000, - F_CLASS_CONST = MODE_CLASS | 0x01, - F_CLASS_STATIC = MODE_CLASS | 0x02 - MODE_FUNCTION = 0x2000, - F_FUNC_CTOR = MODE_FUNCTION | 0x01, - ... - - Except that each value could have at most 1 mode. Unless we split - into multiple mode groups: - - MODE_4_MASK = 0x8F00, - MODE_3_MASK = 0x40F0, - MODE_2_MASK = 0x200F, - MODE_1_MASK = 0x1FFF, - - i.e. 3 modes, each with 4 bits, and a fallback mode with 12. Except - that that gains us nothing (or very little). -*/ - -/** - 4 mutually exclusive modes, each with the bottom 8 bits reserved for - itself. -*/ -S2_VAL_F_MODE_MASK = 0xF000U, -S2_VAL_F_MODE_FUNC = 0x1000, -S2_VAL_F_MODE_CLASS = 0x2000, -S2_VAL_F_MODE_DOT = 0x4000, -S2_VAL_F_MODE_4 = 0x8000, -/** - Bits (9-12) are "shared" (independent of the mode). -*/ -S2_VAL_F_COMMON_MASK = 0x0F00, - -/** - Functions with this tag would get special handling when called by - the 'new' keyword. -*/ -S2_VAL_F_FUNC_CTOR = S2_VAL_F_MODE_FUNC | 0x01, - -/** - For potential use in creating property interceptors. -*/ -S2_VAL_F_FUNC_GETTER = S2_VAL_F_MODE_FUNC | 0x02, -S2_VAL_F_FUNC_SETTER = S2_VAL_F_MODE_FUNC | 0x04, -S2_VAL_F_FUNC_INTERCEPTOR = S2_VAL_F_FUNC_SETTER | S2_VAL_F_FUNC_GETTER, - -/** - Objects created via the 'class' keyword. Get special - property lookup treatment. -*/ -S2_VAL_F_CLASS = S2_VAL_F_MODE_CLASS | 0x01, -/* Problem with some words, like const, is that we can only tag container types. - So we'd need to use property-level constness. */ -S2_VAL_F_CLASS_CONST = S2_VAL_F_MODE_CLASS | 0x02, -S2_VAL_F_CLASS_STATIC = S2_VAL_F_MODE_CLASS | 0x04, -/* It's unlikely that s2's engine can currently support the concept of - private/protected vars/properties, but for the sake of bitmask - planning... */ -S2_VAL_F_CLASS_PRIVATE = S2_VAL_F_MODE_CLASS | 0x08, /* or PUBLIC, if we default to private and can enforce it */ -S2_VAL_F_CLASS_PROTECTED = S2_VAL_F_MODE_CLASS | 0x10, - -/** - Objects with this tag (created by passing a S2_VAL_F_CLASS-tagged - Object to the 'new' keyword) might get special property lookup - (and assignment) semantics, e.g. limit them to class-defined - properties. -*/ -S2_VAL_F_CLASS_INSTANCE = S2_VAL_F_MODE_CLASS | 0x20, -/** - Indicates the container is an enum. -*/ -S2_VAL_F_CLASS_ENUM = S2_VAL_F_MODE_CLASS | 0x40, -/** - Hmm. The enum entry type (cwal type "unique") cannot have - flags, so this is apparently unusued. -*/ -S2_VAL_F_CLASS_ENUM_ENTRY = S2_VAL_F_MODE_CLASS | 0x80, - -/** - This client container flag (cwal_container_client_flags_get() and - friends)) causes s2_get_v() and friends to trigger an exception if - a property request on a container with this flag does not find an - entry. - - s2_set_v() does this differently to avoid having to do duplicate - lookups. This feature was added to the core as the - CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES container flag and - the CWAL_RC_DISALLOW_NEW_PROPERTIES result code. -*/ -S2_VAL_F_DISALLOW_UNKNOWN_PROPS = S2_VAL_F_COMMON_MASK & 0x100, - -/** - This client container flag (cwal_container_client_flags_get() and - friends) gets temporarily set on values created by the 'new' - keyword, for the duration of the constructor call. -*/ -S2_VAL_F_IS_NEWING = S2_VAL_F_COMMON_MASK & 0x0200, - -/** - Denotes an enum instance. May be a hash or an object. -*/ -S2_VAL_F_ENUM = S2_VAL_F_CLASS_ENUM | S2_VAL_F_DISALLOW_UNKNOWN_PROPS, - -/** - Means something like: this Value should be treated like an - Object for most purposes. - - We currently (experimentally, 20141202) use this on certain Hash - Table instances. Its interpretation is that the dot op should use - the Value's hash entries instead of its object properties. We - somehow need to accommodate inherited methods, though. So we first - check the hashtable, then object-level properties, then up the - prototype chain. Any prototypes with this flag would of course - apply the same logic. -*/ -S2_VAL_F_DOT_LIKE_OBJECT = S2_VAL_F_MODE_DOT | 0x002, -/* S2_VAL_F_DOT_ARROW_LIKE_DOT = S2_VAL_F_MODE_DOT | 0x004, */ - -/** - A mask of all bits, just as a reminder that we'd be limited to 16 - bits (because cwal has no more space to spare without increasing - the sizeof() for all properties-capable Values). -*/ -S2_VAL_F_MASK = 0xFFFF -}; - -/** - Utility type for use with s2_install_functions(), allowing simple - installation of a series of callbacks in one go. -*/ -struct s2_func_def { - char const * name; - cwal_callback_f callback; - void * state; - cwal_finalizer_f stateDtor; - void const * stateTypeID; - /** - A mask of S2_VAL_F_xxx vars - */ - uint16_t cwalContainerFlags; -}; -#define S2_FUNC6(NAME,CALLBACK,STATE,StateDtor,StateTypeId,CwalContainerFlags) \ - {NAME,CALLBACK,STATE,StateDtor,StateTypeId, CwalContainerFlags} -/** - Convenience macro for initializing an s2_func_def entry. -*/ -#define S2_FUNC5(NAME,CALLBACK,STATE,StateDtor,StateTypeId) \ - S2_FUNC6(NAME,CALLBACK,STATE,StateDtor,StateTypeId, 0) -/** - Convenience macro for initializing an s2_func_def entry. -*/ -#define S2_FUNC2(NAME,CALLBACK) S2_FUNC5(NAME,CALLBACK,0,0,0) -/** - EXPERIMENTAL: don't use. - - Convenience macro for initializing an s2_func_def entry. -*/ -#define S2_FUNC2_INTERCEPTOR(NAME,CALLBACK) S2_FUNC6(NAME,CALLBACK,0,0,0,S2_VAL_F_FUNC_INTERCEPTOR) - -/** - Empty-initialized const s2_func_def struct. -*/ -#define s2_func_def_empty_m {0,0,0,0,0,0} -/** Convenience typedef. */ -typedef struct s2_func_def s2_func_def; - -/** - Installs a list of cwal callback functions into the given target - container value. defs must be an array of s2_func_def objects - terminated by an entry with a NULL name field (most simply, use - s2_func_def_empty_m to intialize the final element). All member - pointers in each entry must be valid, and 0 is (generally speaking) - valid for all but the name and callback fields. - - If propertyFlags is not 0 then each property gets set with those - flags, as per cwal_prop_set_with_flags(). - - If tgt is NULL then the functions are installed into the _current_ - cwal scope. Note that outside of initialization of the engine, the - current scope is very likely not the top-most, and may well - disappear soon (e.g. using this from within a cwal_callback_f() - implementation will only install these for the duration of the - current function call!). (Potential TODO: add - s2_install_functions_in_scope(), which takes a scope pointer) - - Returns 0 on success, a non-0 CWAL_RC_xxx code on error. - - Example: - - @code - const s2_func_def funcs[] = { - S2_FUNC2("myFunc1", my_callback_1), - S2_FUNC2("myFunc2", my_callback_2), - s2_func_def_empty_m // IMPORTANT that the list end with this! - }; - int rc = s2_install_functions(se, myObj, funcs, CWAL_VAR_F_CONST); - @endcode -*/ -int s2_install_functions( s2_engine *se, cwal_value * tgt, - s2_func_def const * defs, - uint16_t propertyFlags ); -/** - A convenience function to install value v into the tgt object, or - into the current scope if tgt is NULL. If nameLen is <0 then - cwal_strlen() is used to calculate its length. propertyFlags may be - 0 or a mask of CWAL_VAR_F_xxx flags. - - Returns 0 on success or a CWAL_RC_xxx code on error. - */ -int s2_install_value( s2_engine *se, cwal_value * tgt, - cwal_value * v, - char const * name, - int nameLen, - uint16_t propertyFlags ); - -/** - A (somewhat) convenience form of s2_install_value() which passes - the (callback, state, stateDtor, stateTypeID) flags to - cwal_new_function() and installs the resulting function int the tgt - container value (or the current scope if tgt is NULL). - - If nameLen is <0 then cwal_strlen() is used to calculate its - length. propertyFlags may be 0 or a mask of CWAL_VAR_F_xxx flags. - - Returns 0 on success or a CWAL_RC_xxx code on error. - */ -int s2_install_callback( s2_engine *se, cwal_value * tgt, - cwal_callback_f callback, - char const * name, - int nameLen, - uint16_t propertyFags, - void * state, - cwal_finalizer_f stateDtor, - void const * stateTypeID ); - -/** - A utility class for creating s2 script-side enums from C. - - When declaring/allocating these, be sure to ensure a sane default - state by copying the global s2_enum_builder_empty object. e.g. - - @code - s2_enum_builder eb = s2_enum_builder_empty; - @endcode - - @see s2_enum_builder_init() - @see s2_enum_builder_append() - @see s2_enum_builder_seal() - @see s2_enum_builder_cleanup() -*/ -struct s2_enum_builder { - /** - The owning/managing s2_engine instance. - */ - s2_engine * se; - /** - Internal flags. - */ - cwal_flags16_t flags; - /** - Current entry count. This only counts the "primary" mappings - (entry name to entry value), not the reverse mappings (value to - name). - */ - cwal_size_t entryCount; - /** - The underlying storage for the enum entries. As of 2020-02-21, - this is a hash (it was previously an object or hash, depending on - how many entries it held, but that proved to be more trouble than - it was worth in downstream code). - - The state of this value is in flux until s2_enum_builder_seal() - is used to "seal" it. - */ - cwal_value * entries; -}; -/** Convenience typedef. */ -typedef struct s2_enum_builder s2_enum_builder; - -/** - An initialized-with-defaults s2_enum_builder instance, intended to - be copied from when initializing instances on the stack. -*/ -extern const s2_enum_builder s2_enum_builder_empty; - -/** - Initializes an s2_enum_builder instance, which MUST have been - initially initialized via copy-construction from - s2_enum_builder_empty (or results are undefined). - - entryCountHint is a hint to the system about how many entries are - to be expected. This allows it to size its storage appropriately. - If passed 0, it makes a conservative estimate. This routine chooses - a prime-number hash table size based on the hint, so the hint - itself need not be prime. (In any case, the storage may be resized - when s2_enum_builder_seal() is used to finalize the construction - process.) - - If typeName is not NULL and has a non-0 length then it will be used - as the generated enum's typename value. If not NULL, typeName MUST - be NUL-terminated. - - If this function returns 0, the s2_enum_builder instance must - eventually be passed to s2_enum_builder_seal() or - s2_enum_builder_cleanup() to free any resources it owns. - - Returns 0 on success and CWAL_RC_OOM if any allocation fails. On - error, eb gets cleaned up if needed. - - A reference is added to eb->entries, and it is made vacuum-proof, - until eb is cleaned up or sealed. HOWEVER... - - ACHTUNG: - - 1) The builder class is intended primarily to be used during setup - of a module or "atomically", e.g. in a single call to a - cwal_callback_f() binding. Specifically, it is not intended to be - run concurrently with script code due to... - - 2) Lifetime considerations: during/after initialization, the - under-construction enum value is initially managed by the cwal - scope which is active at the time the enum is initialized (via this - function). If that scope is popped before the enum is sealed or - cleaned up, the enum's underlying storage (eb->entries) will be - destroyed during the finalization of that scope and will leave - eb->entries pointing to a stale pointer. cwal's recycling - mechanisms may re-use that pointer soon afterwards, thus - eb->entries could point to repurposed memory. i.e. Undefined - Behaviour. Short version: create and seal your enum in the lifetime - of the cwal/s2 scope it is initialized in unless you want to manage - eb->entries' scope manually (tip: you _don't_ want to do that). - - 3) 2020-02-21: the order of the last two arguments was swapped - solely to force client-side breakage, rather than having the change - of semantics for the entryCountHint parameter go unnoticed. - - @see s2_enum_builder_cleanup() - @see s2_enum_builder_append() - @see s2_enum_builder_seal() -*/ -int s2_enum_builder_init( s2_engine * se, s2_enum_builder * eb, - char const * typeName, - cwal_size_t entryCountHint ); - -/** - Every s2_enum_builder which gets passed to s2_enum_builder_init() - must eventually be passed to this routine to free any memory owned - by the builder. If the caller needs to keep eb->entries alive after - this call, they must take a reference to it (see - s2_enum_builder_seal()) and they may need to manually rescope it (a - topic beyond the scope (as it were) of this function's - documentation). - - It is a harmless no-op to call this multiple times on the same - instance and it can safely be called after s2_enum_builder_seal() - (which cleans up the enum in certain circumstances). - - @see s2_enum_builder_seal() - @see s2_enum_builder_append() - @see s2_enum_builder_init() -*/ -void s2_enum_builder_cleanup( s2_enum_builder * eb ); - -/** - Appends a new entry to an under-construction enum. - - eb must have been initialized using s2_enum_builder_init() and must - not yet have been sealed via s2_enum_builder_seal(). - - entryName is the NUL-terminated name of the enum entry. val is the - optional value associated with the entry (it may be NULL). - - Returns 0 on success, CWAL_RC_OOM if any allocation fails, - CWAL_RC_MISUSE if entryName is NULL or eb does not seem to be - properly initialized or has already been sealed (see - s2_enum_builder_seal()). On CWAL_RC_OOM, eb must be considered to - be in an undefined state and must not be used further except to - pass it to s2_enum_builder_cleanup(). - - @see s2_enum_builder_seal() - @see s2_enum_builder_cleanup() - @see s2_enum_builder_init() -*/ -int s2_enum_builder_append( s2_enum_builder * eb, char const * entryName, - cwal_value * val); - -/** - Works just like s2_enum_builder_append() but takes its key as a - cwal_value rather than a c-string. - */ -int s2_enum_builder_append_v( s2_enum_builder * eb, - cwal_value * key, - cwal_value * wrappedVal ); - -/** - "Seals" the given under-construction enum, such that it can no - more entries can be added to it. - - The caller "should" pass a non-NULL rv value, in which case: - - 1) *rv is assigned to the newly-created enum value. It has an - initial refcount of 0, so the client needs to reference/unreference - it (or equivalent). - - 2) eb is cleaned up (see s2_enum_builder_cleanup()) to avoid - further (mis)use. Despite this, it is safe to pass it to - s2_enum_builder_cleanup(), so no special-case code branches - are needed to accommodate this case. - - If rv is NULL then eb does not get cleaned up and the caller - must eventually: - - 1) Take a reference to eb->entries, if needed. - - 2) Pass eb to s2_enum_builder_cleanup(). This will destroy eb->entries - unless the caller has acquired a reference to it. - - The new enum would typically be stored in a cwal scope to keep it - it alive, and it may be destroyed if it is not stored in a - container or scope before its own managing scope is destroyed. (Its - initial managing scope is the one which is active when the - s2_enum_builder_init() is called.) - - Returns 0 on success. On error, CWAL_RC_RANGE if no entries have - been added to the enum (via s2_enum_builder_append()), CWAL_RC_OOM - on allocation error, and CWAL_RC_MISUSE if eb has not been properly - initialized or has already been sealed. On error *rv is not - modified. On error eb must be considered to be in an undefined - state and must not be used further except to pass it to - s2_enum_builder_cleanup(). - - @see s2_enum_builder_append() - @see s2_enum_builder_cleanup() - @see s2_enum_builder_init() -*/ -int s2_enum_builder_seal( s2_enum_builder * eb, cwal_value **rv ); - -/** - Toggles the S2_VAL_F_DOT_LIKE_OBJECT flag on the given value, - which is assumed to be a hash table. The second parameter determines - whether the flag is toggled on or off. -*/ -void s2_hash_dot_like_object( cwal_value * hash, int dotLikeObj ); - -/** - Returns true (non-0) if key's string value is the string "value". -*/ -char s2_value_is_value_string( s2_engine const * se, cwal_value const * key ); - -/** - Returns true (non-0) if key's string value is the string "prototype". -*/ -char s2_value_is_prototype_string( s2_engine const * se, cwal_value const * key ); - - -/** - ACHTUNG: only lightly tested and known to break with certain - script constructs. - - "Minifies" s2 source code in the given source, appending it to the - given destination. This strips out all "junk" tokens and most - newlines, as well as runs of contiguous spaces/tabs. - - This does not do any semantic analysis on the input - it only - tokenizes the input. Unknown token types, unmatched - braces/parens/quotes, etc. will cause it to fail. - - Returns 0 on success. - - There's not yet a guaranty that minified code can be eval'd... it's - a learning process. - - src and dest _must_, on error, return a non-0 code from the - CWAL_RC_xxx family of codes, e.g. CWAL_RC_IO might be appropriate - (falling back to CWAL_RC_ERROR if there's no better match). -*/ -int s2_minify_script( s2_engine * se, cwal_input_f src, void * srcState, - cwal_output_f dest, void * destState ); -/** - Equivalent to s2_minify_script(), using src as the input and dest - as the output destination. src must not be dest. dest gets appended - to, so be sure to cwal_buffer_reset() it, if needed, when looping - over a single output buffer. -*/ -int s2_minify_script_buffer( s2_engine * se, cwal_buffer const * src, - cwal_buffer * dest ); - -/** - A cwal_callback_f() implementation binding s2_minify_script(). It - expects either 1 or 2 arguments: (srcStringOrBuffer) or - (srcStringOrBuffer, destBuffer). It returns, via *rv, its second - argument or a new Buffer instance. -*/ -int s2_cb_minify_script( cwal_callback_args const * args, cwal_value ** rv ); - -/** - A convenience routine to bind a callback function as a constructor - for use with s2's "new" keyword. - - Installs method as a hidden/const property named "__new" in the - given container. The method parameter is assumed to conform to the - "new" keyword's constructor conventions. - - This has the same effect as setting the property oneself except - that this routine uses a cached copy of the key string and sets the - property to hidden/const. - - Returns 0 on success, else: - - - CWAL_RC_MISUSE if any argument is NULL. - - - CWAL_RC_OOM on allocation error. - - - CWAL_RC_TYPE if !cwal_props_can(container). - - - CWAL_RC_CONST_VIOLATION if container already has a constructor - property and it is const (otherwise it is overwritten). - - An example of customizing the constructor with state: - - @code - int rc; - cwal_function * f; - cwal_value * fv; - f = cwal_new_function( se->e, my_callback_f, my_callback_state, - my_callback_state_finalizer_f, my_type_id ); - if(!f) return CWAL_RC_OOM; - fv = cwal_function_value(fv); - assert(f == cwal_value_get_function(fv)); // will always be the case if f is valid - cwal_value_ref(fv); - rc = s2_ctor_method_set( se, myContainer, f ); - cwal_value_unref(fv); - return rc; - @endcode - - In such a setup, from inside the my_callback() implementation, - cwal_args_state(args->engine, my_type_id) can be used to - (type-safely) fetch the my_callback_state pointer. The s2_engine - instance associated with the call can be fetched via - s2_engine_from_args(args). - - @see s2_ctor_callback_set() -*/ -int s2_ctor_method_set( s2_engine * se, cwal_value * container, cwal_function * method ); - -/** - A convenience form of s2_ctor_method_set() which instantiates a - cwal_function from the its 3rd argument using cwal_new_function(). - Returns CWAL_RC_MISUSE if any argument is NULL, else returns as for - s2_ctor_method_set(). -*/ -int s2_ctor_callback_set( s2_engine * se, cwal_value * container, cwal_callback_f method ); - -/** - Invokes a "constructor" function in the same manner as the 'new' keyword - does. If ctor is NULL then s2 looks in operand (which must be a container - type) for a property named "__new". If that property is not found or - is not a Function, se's error state is set and non-0 is returned. - - All arguments except args and ctor must be non-NULL. - - This routine abstractly does the following: - - - creates a new Object. - - - sets operand to be the new object's prototype. - - - calls the ctor, passing on the new object as the "this" and any - arguments in the args array (which may be NULL or empty). - - - if that ctor returns successfully: if it returns a cwal_value - other than cwal_value_undefined() then that value is assigned to - *rv, otherwise the newly-created Object is set to *rv. Thus a - "return this", "return undefined", "return" and an implicit return - all cause the newly-created object to be returned. The intention - is to allow constructors to return a different object which should - then be substituted in the original's place (this is what "new" - does with it). - - This all happens in an internally-pushed scope, but *rv will be - rescoped to the caller's scope (for lifetime management purposes) - if necessary. - - ACHTUNG: make sure you have a ref point on all parameters, else - this function might end up cleaning any one of them up. If passed, - e.g. an args array with no reference point, the passed-in array - may be cleaned up by this function. - - On error, non-0 is returned and *rv will be assigned to 0. - - TODO: a variant of this which takes a C array of cwal_value - pointers and the length of that array, as that's generally easier - to work with in client code (but an array-based impl is what the - "new" keyword needs). We "could" expose that array directly from a - cwal public API but we don't because manipulating it from client - code could be disastrous. -*/ -int s2_ctor_apply( s2_engine * se, cwal_value * operand, - cwal_function * ctor, cwal_array * args, - cwal_value **rv ); - -/** - Returns true (non-0) if v is legal for use as an operand do the - "new" keyword. This only returns true if v is a container which - holds (not counting properties inherited from prototypes) a key - named "__new" with a value which is-a/has-a Function. -*/ -char s2_value_is_newable( s2_engine * se, cwal_value * v ); - -/** - Returns true (non-0) if the given value is currently being - initialized as the "this" value of a constructor function via s2's - "new" keyword. e.g. it can be used in the context of a C-native - constructor to figure out if the function was used in a constructor - context or not (if that makes any difference to how it functions). -*/ -char s2_value_is_newing(cwal_value const * v); - -/** - Returns true if this function believes that mem (which must be at - least len bytes of valid memory long) appears to have been - compressed by s2_buffer_compress() or equivalent. This is not a - 100% reliable check - it could potentially have false positives on - certain inputs, but that is thought to be unlikely (at least for - text data). A false positive has never been witnessed in many - thousands of tests in the libfossil tree. Knock on wood. - - Returns 0 if mem is NULL. - - @see s2_buffer_compress() - @see s2_buffer_uncompress() - @see s2_uncompressed_size() -*/ -char s2_is_compressed(unsigned char const * mem, cwal_size_t len); - -/** - Equivalent to s2_is_compressed(buf->mem, buf->used), except that - it returns 0 if !buf. - - @see s2_buffer_compress() - @see s2_buffer_uncompress() -*/ -char s2_buffer_is_compressed(cwal_buffer const *buf); - -/** - If s2_is_compressed(mem,len) returns true then this function - returns the uncompressed size of the data, else it returns - (uint32_t)-1. (Remember that an uncompressed size of 0 is legal!) -*/ -uint32_t s2_uncompressed_size(unsigned char const *mem, - cwal_size_t len); - -/** - The cwal_buffer counterpart of s2_uncompressed_size(). -*/ -uint32_t s2_buffer_uncompressed_size(cwal_buffer const * b); - -/** - If built without zlib or miniz support, this function always - returns CWAL_RC_UNSUPPORTED without side-effects. - - Compresses the first pIn->used bytes of pIn to pOut. It is ok for - pIn and pOut to be the same blob. - - pOut must either be the same as pIn or else a properly - initialized buffer. Any prior contents will be freed or their - memory reused. - - Results are undefined if any argument is NULL. - - Returns 0 on success, CWAL_RC_OOM on allocation error, and - CWAL_RC_ERROR if the lower-level compression routines fail. - - Use s2_buffer_uncompress() to uncompress the data. - - The data is encoded with a big-endian, unsigned 32-bit length as - the first four bytes, and then the data as compressed by a - "zlib-compatible" mechanism (which may or may not be zlib or - miniz). - - After returning 0, pOut->used will hold the new, compressed size - and s2_buffer_uncompressed_size() can be passed pOut to get the - original size. - - Special cases: - - - If s2_buffer_is_compressed(pIn) and (pIn != pOut), then pIn is - simply copied to pOut, replacing any existing content. - - - If s2_buffer_is_compressed(pIn) and (pIn == pOut), it has - no side effects and returns 0. - - - Minor achtung: the underlying compression library (which is - specified as being "zlib-compatible", without saying it's actually - zlib) allocates and frees memory from outside of e's memory - management system, which means that it is not accounted for in, - e.g. cwal-level metrics and memory capping. - - @see s2_buffer_uncompress() - @see s2_buffer_is_compressed() -*/ -int s2_buffer_compress(cwal_engine * e, cwal_buffer const *pIn, - cwal_buffer *pOut); - -/** - If built without zlib or miniz support, this function always - returns CWAL_RC_UNSUPPORTED without side-effects. - - Uncompresses buffer pIn and stores the result in pOut. It is ok for - pIn and pOut to be the same buffer, in which case the old contents - will, on success, be destroyed. Returns 0 on success. On error pOut - is not modified (whether or not pIn==pOut). - - pOut must be either cleanly initialized/empty or the same as pIn. - - Results are undefined if any argument is NULL or its memory is - invalid. - - If (pIn == pOut) and !s2_buffer_is_compressed(pIn) then this - function returns 0 without side-effects. - - Returns 0 on success, CWAL_RC_OOM on allocation error, and - CWAL_RC_ERROR if the lower-level decompression routines fail. - - @see s2_buffer_compress() - @see s2_buffer_compress2() - @see s2_buffer_is_compressed() -*/ -int s2_buffer_uncompress(cwal_engine * e, cwal_buffer const *pIn, - cwal_buffer *pOut); - - -/** - Swaps left/right's contents. It retains (does not swap) the - left->self/right->self pointers (swapping those _will_ corrupt - their memory at some point). Results are undefined if (left==right) - or if either argument is NULL or points to invalid memory. -*/ -void s2_buffer_swap( cwal_buffer * left, cwal_buffer * right ); - -/** - (Mostly) internal debugging tool which dumps out info about v (may - be NULL), with an optional descriptive message. Expects file, func and - line to be the __FILE__, __func__ resp. __LINE__ macros. Use the - s2_dump_val() macro to simplify that. - - Reminder: v cannot be const b/c some types go through JSON output, - which requires non-const so that it can catch cycles. -*/ -void s2_dump_value( cwal_value * v, char const * msg, char const * file, - char const * func, int line ); - -/** - Equivalent to s2_dump_value(V, MSG, __func__, __LINE__). -*/ -#define s2_dump_val(V, MSG) s2_dump_value((V), (MSG), __FILE__, __func__, __LINE__) - -/** - Prints out a cwal_printf()-style message to stderr and exit()s - the application. Does not return. Intended for use in place of - assert() in test code. The library does not use this internally. -*/ -void s2_fatal( int code, char const * fmt, ... ); - -/** - Exists only to avoid adding an s2-internal-API dep in s2sh. - - Returns sizeof(s2_func_state). -*/ -unsigned int s2_sizeof_script_func_state(void); - - -/** - Removes the specially-propopagating "return" value (if any) - from its special propagation mode, effectively transferring - to the caller (with all the usual caveats about refcounts, - unknowable ownership, etc.) See cwal_propagating_take() - for details. - - In s2, the following keywords use specially-propagating values: - return, break, exit (, fatal???). Exceptions use a separate - propagation slot dedicated to the currently-thrown exception. -*/ -cwal_value * s2_propagating_take( s2_engine * se ); - -/** - s2 proxy for cwal_propagating_get() for details. -*/ -cwal_value * s2_propagating_get( s2_engine * se ); - -/** - s2 proxy for cwal_propagating_set() for details. -*/ -cwal_value * s2_propagating_set( s2_engine * se, cwal_value * v ); - - -/** - Might or might not cwal_engine_sweep() or cwal_engine_vacuum() on - se->e, depending on the state of se's various counters and - guards. In any case, this function may increase a counter internal - to the s2_engine instance. - - Returns 0 on success and "should" never fail. - - In debug builds, this routine asserts for any sort of problems. In - non-debug builds it returns CWAL_RC_MISUSE if se has no current - scope. It's conceivable that cwal_engine_vacuum() fails (returns - non-0), but that can only happens if it detects memory corruption - caused by mismanagement of Values, in which case an assert() is is - triggered in debug builds. That said, a "real" failure of vacuum, - at a time where a vacuum should be legal, has never been witnessed, - so handling of a non-0 result code is largely a hypothetical - problem. Feel free to ingore the result code. -*/ -int s2_engine_sweep( s2_engine * se ); - - -/** - Intended to be used (if at all, then) for storing prototype - values long-term in an s2_engine. - - Stores the given value in se's stash using the given name as a - suffix for some larger unique key reserved for the various base - prototypes. This implicitly moves proto into the global scope (for - lifetime purpose) and makes it vacuum-proof. - - This "really" should only to be used by the various - s2_prototype_xxx() functions, but libfossil currently also makes - use of it, so it can't currently be made an internal API. -*/ -int s2_prototype_stash( s2_engine * se, char const * typeName, - cwal_value * proto ); - -/** - If the given name string was used to stash a prototype with - s2_prototype_stash(), this function returns that value, else it - returns 0. Ownership of the returned value is not modified. -*/ -cwal_value * s2_prototype_stashed( s2_engine * se, char const * typeName ); - -/** - Sets the __typename property on the given container to the given - value, with the (CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN) flags set. - This is more efficient than directly setting that property because - s2_engine caches the __typename key. -*/ -int s2_typename_set_v( s2_engine * se, cwal_value * container, cwal_value * name ); - -/** - The C-string counterpart of s2_typename_set_v(), differing - only in that it takes the type name in the form - of the first nameLen bytes of name. -*/ -int s2_typename_set( s2_engine * se, cwal_value * container, - char const * name, cwal_size_t nameLen ); - - -/** - If a byte-exact match of needle is found in haystack, its offset in - the haystack is returned, else 0 is returned. -*/ -char const * s2_strstr( char const * haystack, cwal_size_t hayLen, - char const * needle, cwal_size_t needleLen ); - - -/** - Evaluates the given script code in a new scope and stores the - result of that script in the given container, using the given - property name (which must be propNameLen bytes long). This is - intended to simplify installation of small/embedded - script-implemented functions from C code. - - If srcLen is negative, cwal_strlen() is used to calculate - src's length. - - If the script triggers a 'return' then this routine captures - that return'd value as the result. - - Returns 0 on success, a CWAL_RC_xxx or S2_RC_xxx value on error, - and can be anything script evaluation could return. -*/ -int s2_set_from_script( s2_engine * se, char const * src, - int srcLen, cwal_value * addResultTo, - char const * propName, - cwal_size_t propNameLen ); - -/** - Works identically to s2_set_from_script() except that it takes - its property name as a cwal_value. -*/ -int s2_set_from_script_v( s2_engine * se, char const * src, - int srcLen, cwal_value * addResultTo, - cwal_value * propName ); - -/** - A proxy for getcwd(2) which appends the current wording directory's - name to the end of the given buffer. This function expands buffer - by some relatively large amount to be able to handle long - paths. Because of this, it's recommended that a shared/recycled - buffer be used to handle calls to this function - (e.g. s2_engine::buffer). - - On success, tgt gets appended to, updating tgt's members as - appropriate, and 0 is returned. On error, non-0 is returned (the - exact code may depend on the errno set by getcwd(2)). - - ACHTUNG: this function is only implemented for Unix systems. On - Windows builds it returns CWAL_RC_UNSUPPORTED because i don't have - a Windows system to implement this on. -*/ -int s2_getcwd( cwal_engine * e, cwal_buffer * tgt ); - -/** - A cwal_callback_f() implementation wrapping s2_getcwd(). On - success, it returns (via *rv) the current wording directory's name - as a string. If script passed an argument from script code, it is - interpreted as a boolean: if true, the directory separator is - appended to the result, else it is not. The default is not to - append the directory separator to the result. -*/ -int s2_cb_getcwd( cwal_callback_args const * args, cwal_value ** rv ); - -/** - Works like mkdir(2). Returns 0 on success, else a CWAL_RC_xxx value - approximating the underlying errno result. If errMsg is not NULL - then on error, *errMsg will point to an error message string owned - by the C library. Its contents may be modified by calls to - strerror(3), so must be copied if it should be retained. - - LIMITATIONS: - - 1) Returns CWAL_RC_UNSUPPORTED on builds which don't have mkdir(2). - - 2) Does not create intermediate directories, so the parent dir - of the new directory must already exist. See s2_mkdir_p(). - - 3) The message string returned by strerror() may be modified by - calls to that function from other threads, so the errMsg argument - is only known to be useful for single-threaded clients. - - @see s2_mkdir_p() -*/ -int s2_mkdir( char const * name, int mode, char const ** errMsg ); - -/** - A convenience wrapper around s2_mkdir() which creates parent - directories, if needed, for the target directory name. e.g. if - passed "a/b/c" and "a" and/or "b" do not exist, they are created - before creating "c". Fails if creation of any part of the path - fails. - - It requires a well-formed Unix-style directory name (relative or - absolute). Returns 0 on success, else non-0 and an error message, - as documented for s2_mkdir(). - - @see s2_mkdir() -*/ -int s2_mkdir_p( char const * name, int mode, char const ** errMsg ); - -/** - A cwal_callback_f() implementation wrapping s2_mkdir(). It mkdir() is - not available in this built, throws an exception with code - CWAL_RC_UNSUPPORTED. - - Script-side signatures: - - (string dirName [, bool makeParentDirs=false [, int mode = 0750]]) - (string dirName [, int mode = 0750]) - - Noting that the access mode may be modified by the OS, e.g. to - apply the umask. - - If a directory with the given name already exists, this function - has no side-effects. Note that the access mode is ignored for - purposes of that check. - - Throws a CWAL_RC_ACCESS exception if s2_disable_check() for the - flag S2_DISABLE_FS_WRITE | S2_DISABLE_FS_STAT fails. - - Throws on error. On success, returns the undefined value. -*/ -int s2_cb_mkdir( cwal_callback_args const * args, cwal_value ** rv ); - -/** - File/directory types for use with s2_fstat_t. -*/ -enum s2_fstat_types { -S2_FSTAT_TYPE_UNKNOWN = 0, -S2_FSTAT_TYPE_REGULAR = 0x01, -S2_FSTAT_TYPE_DIR = 0x02, -S2_FSTAT_TYPE_LINK = 0x04, -S2_FSTAT_TYPE_BLOCK = 0x08, -S2_FSTAT_TYPE_CHAR = 0x10, -S2_FSTAT_TYPE_FIFO = 0x20, -S2_FSTAT_TYPE_SOCKET = 0x40 -}; -/** - Filesystem entry info for use with s2_fstat(). -*/ -struct s2_fstat_t { - /** - The type of a given entry. - */ - enum s2_fstat_types type; - /** - Change time (includes metadata changes, e.g. permissions and - such). - */ - uint64_t ctime; - /** - Modification time (does not account for metadata changes, - e.g. permissions and such). - */ - uint64_t mtime; - /** Size, in bytes. */ - uint64_t size; - /** Unix permissions. */ - int perm; -}; -typedef struct s2_fstat_t s2_fstat_t; -/** - Intended to be used as a copy-construction source for local s2_fstat_t copies, - to ensure a clean state. - */ -extern const s2_fstat_t s2_fstat_t_empty; -/** - Cleanly-initialized s2_fstat_t entry, intended for const copy - initialization. -*/ -#define s2_fstat_t_empty_m {0,0,0,0,0} - -/** - Wrapper for stat(2) and lstat(2). The filename is taken from the - first fnLen bytes of the given filename string. (We take the length - primarily because cwal X-strings and Z-strings need not be - NUL-terminated.) If tgt is not NULL then the results of the stat - are written there. If derefSymlinks is 1 then stat() is used, else - lstat() is used, which fetches information about a symlink, rather - than the file the symlink points to. - - On success, returns 0 and, if tgt is not NULL, populates tgt. On - error, tgt is not modified. - - If stat() is not available in this build, CWAL_RC_UNSUPPORTED - is returned. - - If lstat() is not available in this build, it returns - CWAL_RC_UNSUPPORTED if derefSymlinks is false. - - If tgt is NULL and 0 is returned, it means that stat(2) succeeded. - - If stat(2) or lstat(2) fail, non-0 is returned (a CWAL_RC_xxx value - approximating the errno from the failed call). - - Note that this function does not (cannot) honor - s2_disabled_features flags because it has no s2_engine to check - against. -*/ -int s2_fstat( char const * filename, - cwal_size_t fnLen, - s2_fstat_t * tgt, - char derefSymlinks ); - -/** - A cwal_callback_f() implementation wrapping s2_fstat(). - - Script signatures: - - 1) object stat( string filename, [derefSymlinks=true] ) - - Returns an object representing the stat() info (see - s2_fstat_to_object() for the structure. Throws an exception if - stat() fails. - - 2) bool stat(string filename, undedfined [, derefSymlinks=true]) - - If the argument count is greater than 1 and the the second argument - has the undefined value then this function returns a boolean - instead of an object. In that case, it returns false, instead of - throwing, if stat() fails. That's admittedly a horribly awkward way - to distinguish between the two return modes, and it may well be - changed at some point. -*/ -int s2_cb_fstat( cwal_callback_args const * args, cwal_value ** rv ); - -/** - Converts a populated s2_fstat_t struct to an Object with properties describing - the s2_fstat_t values: - - @code - { - ctime: integer, // state change time (includes perms changes) - mtime: integer, // modification time - perm: integer, // Unix permissions bits - size: integer, // size, in bytes - type: string // "unknown", "file", "dir", "link", "block", "char", "fifo", "socket" - } - @endcode - - On success, assigns *rv to the newly-created object and returns 0. On error - *rv is not modified and returns non-zero (likely CWAL_RC_OOM). - - Just FYI: this function caches the keys (via s2_stash_set()) used - for the returned object's properties, so it's not quite as - expensive as it may initially seem. -*/ -int s2_fstat_to_object( s2_engine * se, s2_fstat_t const * fst, cwal_value ** rv ); - -/** - A wrapper around chdir(2). The first dirLen bytes of dir are used - as a directory name passed to chdir(2). Returns 0 on success, non-0 - (a CWAL_RC_xxx code) on error. If this build does not have chdir(2) - then CWAL_RC_UNSUPPORTED is returned. - */ -int s2_chdir( char const * dir, cwal_size_t dirLen ); - -/** - A cwal_callack_f() impl wrapping s2_chdir(). - - Script signature: - - void chdir(string dirname) - - Throws on error. -*/ -int s2_cb_chdir( cwal_callback_args const * args, cwal_value **rv ); - -/** - Glob policies for use with s2_glob_matches_str(). -*/ -enum s2_glob_style { -/** - Match glob using conventional wildcard semantics. -*/ -S2_GLOB_WILDCARD = -1, -/** - Match glob using SQL LIKE (case-insensitive) semantics. -*/ -S2_GLOB_LIKE_NOCASE = 0, -/** - Match glob using SQL LIKE (case-sensitive) semantics. -*/ -S2_GLOB_LIKE = 1 -}; - -/** - Return true (non-zero) if the NUL-terminated string z matches the - NUL-terminated glob pattern zGlob, or zero if the pattern does not - match. Always returns 0 if either argument is NULL. Supports a - subset of common glob rules, very similar to those supported by - sqlite3 (via sqlite3_strglob(), from which this code derives). - - Globbing rules for policy S2_GLOB_WILDCARD: - - '*' Matches any sequence of zero or more characters. - - '?' Matches exactly one character. - - [...] Matches one character from the enclosed list of - characters. - - [^...] Matches one character not in the enclosed list. - - With the [...] and [^...] matching, a ']' character can be included - in the list by making it the first character after '[' or '^'. A - range of characters can be specified using '-'. Example: - "[a-z]" matches any single lower-case letter. To match a '-', make - it the last character in the list. - - - Matching rules for policies S2_GLOB_LIKE and S2_GLOB_LIKE_NOCASE: - - '%' Matches any sequence of zero or more characters - - '_' Matches any one character - - The difference between those policies is that S2_GLOB_LIKE_NOCASE - is case-insensitive. - -*/ -char s2_glob_matches_str(const char *zGlob, - const char *z, - enum s2_glob_style globStyle); - -/** - cwal_callback_f() wrapper for s2_glob_matches_str(). - - Script-side usage: - - bool glob( string glob, string haystack [, int policy=-1] ) - - Glob matching policies (3rd glob() param): - - <0 = (default) wildcard-style (case sensitive) - - 0 = SQL LIKE style (case insensitive) - - >0 = SQL LIKE style (case sensitive) -*/ -int s2_cb_glob_matches_str( cwal_callback_args const * args, - cwal_value ** rv ); - - -/** - A cwal_callback_f() implementation which tokenizes its input into - an array of s11n tokens, but supporting only a subset of its core - token types: numbers, strings (literals, heredocs, and, for - historical reasons, {blocks}), and the built-in constanst - bool/null/undefined values. - - Its intended purpose is to tokenize single lines of "commands" for - use in command-based dispatching in scripts. - - Script usage: - - array tokenizeLine(string input) - - e.g.: - - tokenizeLine('1 2.3 "hi there" true null') - - would result in an array: [1, 2.3, 'hi there', true, null] - - This function does not parse higher-level constructs like objects, - arrays, or functions, and cannot do so without significant - reworking of the evaluation engine (where the logic for such - constructs is embedded). - - For historical reasons, {blocks} are internally strings, which - means that {a b c} will be tokenized as the string 'a b c', except - that: 1) it will not get unescaped like a string literal and 2) its - leading/trailing spaces will be removed. - - If the input string is empty, it resolves to the undefined value, - not an empty array (though that decision is up for reconsideration, - depending on what pending experience suggests). -*/ -int s2_cb_tokenize_line(cwal_callback_args const * args, cwal_value ** rv); - -/** - Works just like the C-standard fopen() except that if zName is "-" - then it returns either stdin or stdout, depending on whether zMode - contains a "w", "a", or "+" (meaning stdout). Neither argument may - be NULL. - - Returns a newly-opened file handle on success, else NULL. The - handle should eventually be passed to s2_fclose() to close it. It - may optinoally be passed to fclose() instead, but that routine will - unconditionally close the handle, whereas this one is a no-op for - the standard streams. - - @see s2_fclose() -*/ -FILE *s2_fopen(const char *zName, const char *zMode); - -/** - If the given FILE handle is one of (stdin, stdout, stderr), this is - a no-op, else it passes the file handle to fclose(). - - @see s2_fopen() -*/ -void s2_fclose(FILE *); - -/** - Reads the given file (synchronously) until EOF and streams its - contents (in chunks of an unspecified size) to cwal_output() using - the given cwal_engine. Returns 0 on success, non-0 (likely - CWAL_RC_IO) on error. Does not modify ownership of the passed-in - pointers. -*/ -int s2_passthrough_FILE( cwal_engine * e, FILE * file ); - -/** - A convenience wrapper around s2_passthrough_FILE() which uses - s2_fopen() to open the given filename and (on success) stream that - file's contents to cwal_output(). Returns CWAL_RC_IO if the fopen() - fails, else returns as documented for s2_passthrough_FILE(). -*/ -int s2_passthrough_filename( cwal_engine * e, char const * filename ); - - -/** - This odd function is intended to assist in certain types of - bindings which want to store, e.g. a custom Prototype somewhere - other than in s2_prototype_stash(). This function creates a new key - using cwal_new_unique() and uses it store 'what' in 'where' - with the CWAL_VAR_F_HIDDEN and CWAL_VAR_F_CONST flags. This binding - will ensure that 'what' gets referenced and rescoped as necessary - to keep it alive along with 'where'. One caveat: if - cwal_props_clear(where) is called, it will delete that binding, - possibly leading to unexpected behaviour in other places which are - expecting 'what' to be alive. - - A concrete example: custom modules often need a custom prototype, - but don't really have a good place to store it. One option is to - attach it as state to the Function which acts as that type's - constructor (because the prototype is typically only invoked, at - the C level, from there). However, Function state is of type - (void*), and thus is not "Value-aware" and cannot be rescoped as - needed. One workaround to keep that prototype alive is to store it - as a Value in the Function. Because the key is unknown, it cannot - be looked up, but it can (with a little extra work) be accessed via - cwal_function_state_get() and friends. The binding created by - _this_ function keeps that state alive so long as the property does - not get removed. Several of the modules in the s2 source tree - demonstrate this function's use. - - Reminder to self: it might be interesting to add a new - CWAL_VAR_F_xxx flag which tells even cwal_props_clear() not to - remove the property. That would be somewhat more invasive and - special-case than i'm willing to hack right now, though. :/ -*/ -int s2_stash_hidden_member( cwal_value * where, cwal_value * what ); - -/** - This functions provides cwal_callback_f() implementations the - possibility of cleanly exiting the current script, as if the "exit" - keyword had been triggered. Its result is intended to be returned - from such a callback. - - It uses the given value as the script's exit result. The value may - be NULL, which is interpreted as cwal_value_undefined(). - - This function replaces any pending propagating result (from any - in-progress return/break/continue/exit) with the given result - value. Be aware that if you need the old propagating value then you - must, before calling this, s2_propagating_take() it or - s2_propagating_get() it and cwal_value_ref() it. - - This function always returns CWAL_RC_EXIT, which gets interpreted - by the eval engine like the 'exit' keyword. (Note that simply - returning CWAL_RC_EXIT from a callback will fail in other ways, - possibly triggering an assert() in s2, because setting up the - 'exit' involves more than just this result code.) - - Design note: this function takes a cwal_callack_args instead of a - cwal_engine (or s2_engine) only to emphasize that it's only - intended to be called from cwal_callback_f() implementations. (It - also relies on s2's specific interpretation of the CWAL_RC_EXIT - result code.) - - Example: - - @code - int my_callback(cwal_callback_args const * args, cwal_value ** ){ - ... do something ...; - return s2_trigger_exit(args, 0); - } - @endcode -*/ -int s2_trigger_exit( cwal_callback_args const * args, cwal_value * result ); - -/** - If passed a container-type value, it either "seals" or "unseals" - the value by setting resp. unsetting the - CWAL_CONTAINER_DISALLOW_PROP_SET and - CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flags on it. When set, the - engine will refuse to set properties or change prototypes on the - object, and will error out with CWAL_RC_DISALLOW_PROP_SET - resp. CWAL_RC_DISALLOW_PROTOTYPE_SET if an attempt is made. This - only affects future property/prototype operations, not existing - values. - - If passed a truthy 2nd argument, the container is sealed, else it - is unsealed. Note that unsealing is considered to be a rare - corner-case, and is not intended to be used willy-nilly. - - Returns 0 on succes, CWAL_RC_MISUSE if v is NULL, and CWAL_RC_TYPE - if the value is not a container. - - The "seal" restrictions do not currently (20191210) apply to - array/tuple indexes, but probably should (noting that tuples - currently have no flags which would allow us to mark them as - sealed). That behaviour may change in the future. -*/ -int s2_seal_container( cwal_value * v, char sealIt ); - -/** - A script-bindable callback which expects to be passed one or more - container-type values. For each one it calls s2_seal_container(), - passing true as the 2nd argument (so it supports sealing, but not - unsealing, as unsealing would allow script code to do unsightly - things with enums and other sealed containers). - - This callback throws if passed no arguments or if any argument is - not a container type. - - On success it returns, via *rv, the last container it modifies. -*/ -int s2_cb_seal_object( cwal_callback_args const * args, cwal_value ** rv ); - -/** - A cwal_callback_f() implementation which tokenizes conventional - PATH-style strings. Script-side usage: - - array f( string path [, array target] ) - - Each element in the input path is appended to the target array (or - a new array if no array is passed in) and that array is returned. - - It triggers an exception if passed any invalid arguments, noting - that an empty input string is not an error but will cause an empty - list to be returned (resp. no entries to be added to the target - list). -*/ -int s2_cb_tokenize_path( cwal_callback_args const * args, cwal_value **rv ); - -/** - Tokenizes a conventional PATH-style string into an array. - - *tgt must either be NULL or point to an array owned by the - caller. If it is NULL, this function creates a new array and (on - success) assigns it to *tgt. Each entry in the path is added to the - array. - - None of the pointer-type arguments, including tgt (as opposed to - *tgt), may be NULL. - - The 4th argument must be the length, in bytes, of the given path - string. If it is negative, the equivalent of strlen() is used to - calculate its length. If the argument, or its resulting calculated - value, is 0 then the result will be that the target array will be - created, if needed, but will get no entries added to it. - - On success, 0 is returned and 0 or more entries will have been - added to the target array. If *tgt was NULL when this function was - called, *tgt will be assigned to a new array, with a refcount of 0, - which is owned by the caller. - - Assuming all arguments are valid, the only plausible error this - function will return is CWAL_RC_OOM, indicating that allocation of - the target array, an entry for the array, or space within the array - for an entry, failed. On error, if *tgt was NULL when this function - was called, *tgt is not modified, otherwise *tgt may have been - modified before the allocation error occurred. - - Example: - - @code - cwal_array * ar = 0; - int rc = s2_tokenize_path_to_array(e, &ar, "foo;bar", -1); - @code - - On success, ar will be non-NULL and owned by that code. - Contrast with: - - @code - int rc; - cwal_array * ar = cwal_new_array(e); - if(!ar) return CWAL_RC_OOM; - rc = s2_tokenize_path_to_array(e, &ar, "foo;bar", -1); - @code - - In that case, any entries in the given path string will be appended - to the array provided by the caller. - - - @see s2_path_toker - @see s2_path_toker_init() - */ -int s2_tokenize_path_to_array( cwal_engine * e, cwal_array ** tgt, - char const * path, - cwal_int_t pathLen ); - -/** - Never, ever use this unless you are testing s2's internals and - happen to know that it's potentially useful. -*/ -int s2_cb_internal_experiment( cwal_callback_args const * args, cwal_value **rv ); - -/** - Returns the path to s2's "home directory," or NULL if it is - unknown. - - Currently, the home directory is specified via the S2_HOME - environment variable. If that environment variable is set, this - function returns its value in memory which will live as long as the - current app instance. If that variable is not set, NULL is - returned. - - If non-NULL is returned and len is not NULL, *len is assigned the - length of the string, in bytes. - - In the future there might or might not be other ways to specify the - s2 home, perhaps on a per-engine-instance basis (and thus the - s2_engine parameter for this function, though it is currently - unused). -*/ -char const * s2_home_get(s2_engine * se, cwal_size_t * len); - -/** - Installs a user-defined pseudo-keyword into the given engine, - granting fast acess to the value and a lifetime as long as the - engine is running. - - The given string must be a legal s2 identifier with a length equal - to the 3rd argument. Its bytes are copied, so it need not have a - well-defined lifetime. If the given length is negative, - cwal_strlen() is used to calculate the key's length. - - "User Keywords," or UKWDs, as they're colloquially known, use s2's - keyword infrastructure for their lookups, meaning that they bypass - all scope-level lookups and have an amortized faster search time - than var/const symbols. The determination of whether a given symbol - is a UKWD an average O(log n) operation (n=number of UKWDs), and - then finding the associated value for that requires a hashtable - lookup (average O(1)). - - These are not "real" keywords, in that they cannot interact - directly with s2's parser. They simply resolve to a single value of - an arbitrary type which can be quickly resolved in the - keyword-lookup phase of script evaluation. - - Returns: - - - 0 on success. - - - CWAL_RC_RANGE if name is empty. - - - CWAL_SCR_SYNTAX if name is not strictly an identifier token. - - - CWAL_RC_ACCESS if a real keyword with that name exists. - - - CWAL_RC_ALREADY_EXISTS if an entry with that name already exists - - - CWAL_RC_RANGE if the name is empty or "too long" (there is a - near-arbitrary upper limit). - - - CWAL_RC_UNSUPPORTED if v is NULL or is the undefined value, both - of which are reserved for *potential* "undefine" support. - - - CWAL_RC_OOM on allocation error. - - On error, se's error state is updated with an explanation of the - problem. - - Caveats: - - 1) Installing these takes up memory (more than a simple property - mapping), so don't go crazy with 100 of them. Likewise, adding many - keyword changes the duration of that "1" in "O(1)". Not by terribly - much, but keyword lookups happen *all the time* internally (every - time an identifier is seen), so it adds up. - - 2) They cannot be re-set or uninstalled once they have been - installed. -*/ -int s2_define_ukwd(s2_engine * se, char const * name, - cwal_int_t nameLen, cwal_value * v); - -/** - A set of *advisory* flags for features which well-behaved s2 APIs - should honor. The flag names listed for each entry are for use with - s2_disable_set_cstr(). - - This is generally only intended to apply to script-side APIs, not - their equivalent C APIs. e.g. s2_eval_filename() does not honor - these flags, but its script-binding counterparts, - s2_cb_import_script() and the import keyword, do. -*/ -enum s2_disabled_features { -/** - The disable-nothing flag. - - Flag name: "none" -*/ -S2_DISABLE_NONE = 0, -/** - Disables stat()'ing of files, as well as checking/changing the - current directory, in compliant APIs. - - Flag name: "fs-stat" -*/ -S2_DISABLE_FS_STAT = 1, - -/** - Disables opening/reading of files in compliant APIs. - - Note that APIs must also normally (but not always) OR this with - S2_DISABLE_FS_STAT, else a file may be opened for reading without - stat()'ing it first (i.e. without checking whether it exists). - - Flag name: "fs-read" - - Reminder to self: we may want a separate flag which indicates that - JSON files are an exception to this limitation. JSON is known to - not allow execution of foreign code nor loading of non-JSON - content, so it's "safer" that generic filesystem-read access. -*/ -S2_DISABLE_FS_READ = 1 << 1, - -/** - Disables opening/writing and creation of new files in compliant - APIs. - - Flag name: "fs-write" -*/ -S2_DISABLE_FS_WRITE = 1 << 2, - -/** - Flag name: "fs-io" -*/ -S2_DISABLE_FS_IO = S2_DISABLE_FS_READ | S2_DISABLE_FS_WRITE, - -/** - Flag name: "fs-all" -*/ -S2_DISABLE_FS_ALL = S2_DISABLE_FS_IO | S2_DISABLE_FS_STAT -}; - -/** - Assigns the set of API-disabling flags on se. - - @see s2_disable_get() - @see s2_disable_check() -*/ -void s2_disable_set( s2_engine * se, cwal_flags32_t flags ); - -/** - A variant of s2_disable_set() which accepts a - comma-and/or-space-separated list of flag names to set as its 2nd - argument. The 3rd argument specifies the length of the 2nd, in - bytes. If the 3rd argument is negative, cwal_strlen() is used to - determine the 2nd argument's length. A length of 0 causes the flags - to be set to 0. - - On success, 0 is returned and if result is not NULL, the result of - the flags is assigned to *result, as well as being applied to se. - - On error, non-0 is returned and neither se nor *result are modified - other than to set se's error state. An unknown flag is treated as - an error. - - The flag names are listed in the docs for the s2_disabled_features - enum entries. Each entry gets OR'd to the result, with one - exception: the "none" flag resets the flags to 0. -*/ -int s2_disable_set_cstr( s2_engine * se, char const * str, cwal_int_t strLen, - cwal_flags32_t * result ); - -/** - Returns the set of API-disabling flags from se - - @see s2_disable_set() - @see s2_disable_check() -*/ -cwal_flags32_t s2_disable_get( s2_engine const * se ); - -/** - If any of se's advisory feature-disable flags match the given - flags, se's error state is set and non-0 is returned, else se is - not modified and 0 is returned. If this returns non-0, its - state can be converted to an exception with s2_throw_err(), - passing it 0 for all arguments after the first. - - @see s2_disable_set() - @see s2_disable_get() - @see s2_disable_check_throw() - @see s2_cb_disable_check() -*/ -int s2_disable_check( s2_engine * se, cwal_flags32_t f ); - -/** - Equivalent to s2_disable_check(), but transforms any error state - to an exception. -*/ -int s2_disable_check_throw( s2_engine * se, cwal_flags32_t f ); - -/** - Convenience form of s2_disable_check_throw(), intended for use in - cwal_callback_f() implementations, which checks the given flags - against the s2_engine returned by s2_engine_from_args(). -*/ -int s2_cb_disable_check( cwal_callback_args const * args, cwal_flags32_t f ); - -/** - "Should" be called ONE TIME from the application's main() in order - to perform certain static initialization, e.g. install a - cwal_rc_cstr_f fallback for s2-related result codes. - - If it is not called beforehand, it will be called the first time an - s2_engine instance is initialized, but that involves a very narrow - window for a mostly-harmless race condition. -*/ -void s2_static_init(void); - -/** - S2_TRY_INTERCEPTORS: _HIGHLY EXPERIMENTAL_ property interceptor - support. DO NOT USE THIS. It's not known to work, and will very - likely be removed because of its performance implications. -*/ -#define S2_TRY_INTERCEPTORS 0 - -#if S2_TRY_INTERCEPTORS -cwal_function * s2_value_is_interceptor( cwal_value const * v ); -#endif - -#if S2_TRY_INTERCEPTORS -/** - _HIGHLY EXPERIMENTAL_. Do not use. While these basically work, they - are not going to be enabled because the performance hit is too high - for non-interceptor cases (i.e. the vast majority). -*/ -int s2_cb_add_interceptor( cwal_callback_args const * args, cwal_value **rv ); -#endif - -#ifdef __cplusplus -}/*extern "C"*/ -#endif - -#endif -/* include guard */ -/* end of file s2.h */ -/* start of file s2_internal.h */ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/** - License: same as cwal. See cwal.h resp. cwal_amalgamation.h for details. - - This file holds s2-internal declarations and types. These are not - intended for client-side use. -*/ -#ifndef NET_WANDERINGHORSE_CWAL_S2_INTERNAL_H_INCLUDED_ -#define NET_WANDERINGHORSE_CWAL_S2_INTERNAL_H_INCLUDED_ -/** @file */ /* for doxygen */ - -#if !defined(DOXYGEN) -#ifdef __cplusplus -extern "C" { -#endif - -/** @internal - - S2_UNUSED_VAR exists only to squelch, in non-debug builds, warnings - about the existence of vars which are only used in assert() - expressions (and thus get filtered out in non-debug builds). -*/ -#define S2_UNUSED_VAR __attribute__((__unused__)) -/** - S2_UNUSED_ARG is used to squelch warnings about interface-level - function parameters which are not used in a given implementation: - - @code - int some_interface(type1 arg1, type2 arg2){ - S2_UNUSED_ARG arg1; - ... - return 0; - } - @code -*/ -#define S2_UNUSED_ARG (void) - -typedef struct s2_op s2_op; - -/** @internal - - Internal state for Object.eachProperty() and similar functions. -*/ -struct s2_kvp_each_state { - /** Interpreter engine for the visitor. */ - cwal_engine * e; - /** The 'this' for the visitor. */ - cwal_value * self; - /** The function to call for each iteration of the visit. */ - cwal_function * callback; - /** If true, the callback gets the value as the first arg. */ - char valueArgFirst; -}; -typedef struct s2_kvp_each_state s2_kvp_each_state; -/** - An empty-initialized s2_kvp_each_state instance, intended for - copying over new instances to cleany initialize their state. - */ -extern const s2_kvp_each_state s2_kvp_each_state_empty; - - -/** @internal - Allocates a new token using se's cwal-level allocator. The value - must eventually be cleaned up using s2_stoken_free(). Returns 0 on - allocation error (e.g. if !se or !se->e). -*/ -s2_stoken * s2_stoken_alloc( s2_engine * se ); - -/** @internal - Convenience form of s2_stoken_alloc() which sets a token's type - and value. type may be any value and v may be NULL. -*/ -s2_stoken * s2_stoken_alloc2( s2_engine * se, int type, cwal_value * v ); - -/** @internal - "frees" the given token. If allowRecycle is false, or if se's - token recycling feature is disabled or has reached its capacity, - then t is immediate freed using se's allocator, otherwise t will - be placed into a recycling bin for later re-use via - s2_stoken_alloc(). - - TODO: see if we really use that 3rd argument, and remove it if we - don't. -*/ -void s2_stoken_free( s2_engine * se, s2_stoken * t, char allowRecycle ); - -/** @internal - Pushes t to the given stack and transfers ownership of t - to ts. -*/ -void s2_stoken_stack_push( s2_stoken_stack * ts, s2_stoken * t ); - -/** @internal - If ts has any tokens, the top-most one is removed and that token - is returned. Ownership of the returned value is transfered to the - caller. Returns 0 on error (!ts or ts is empty). -*/ -s2_stoken * s2_stoken_stack_pop( s2_stoken_stack * ts ); - -/** @internal - Frees all entries owned by ts by popping each one and passing it - to s2_stoken_free(se, theToken, allowRecycle). See s2_stoken_free() - for the semantics of the se and allowRecycle parameters. -*/ -void s2_stoken_stack_clear( s2_engine * se, s2_stoken_stack * ts, char allowRecycle ); - -/** @internal - Works like s2_stoken_stack_clear(), but operates on both stacks owned by st. -*/ -void s2_estack_clear( s2_engine * e, s2_estack * st, char allowRecycle ); - -/** @internal - Swaps the contents of lhs and rhs (neither may be NULL). -*/ -void s2_estack_swap( s2_estack * lhs, s2_estack * rhs ); - -/** @internal - Pushes the given token to se's value stack and transfers its - ownership to se. The only error condition is if either argument is - 0 or points to invalid memory. - - Reminder to self: this API does not internally add/remove refs to - s2_stokens. This works, but only accidentally. It "should" be - refactored to take a ref when pushing a value, unhand(?) when - popping a value at the client's request, and unref when cleaning up - on its own without the client having taken ahold of - s2_stoken::value. -*/ -void s2_engine_push_valtok( s2_engine * se, s2_stoken * t ); - -/** @internal - The operator-stack counterpart of s2_engine_push_valtok(). -*/ -void s2_engine_push_op( s2_engine * se, s2_stoken * t ); - -/** @internal - Creates a new token with the given type, appends it to se, - and returns it. Return 0 on allocation error. -*/ -s2_stoken * s2_engine_push_ttype( s2_engine * se, int i ); - -/** @internal - Pushes a new token of type S2_T_Value to se, and assigns v to - that token's value. It does not change the reference count or - ownership of v. - - Returns 0 on allocation error or if se or v are 0. On success - it returns the token pushed onto the stack. -*/ -s2_stoken * s2_engine_push_val( s2_engine * se, cwal_value * v ); - -/** @internal - Creates a new cwal_value value of type integer and pushes it onto - se as described for s2_engine_push_val(). Returns 0 on allocation - error, or the pushed token on success. -*/ -s2_stoken * s2_engine_push_int( s2_engine * se, cwal_int_t i ); - -/** @internal - Pushes a new token onto VALUE stack, with the given type and - the given value (which may be NULL but likely should not be). -*/ -s2_stoken * s2_engine_push_tv( s2_engine * se, int ttype, cwal_value * v ); - -/** @internal - If se's stack contains any tokens, the top-most token is removed. - If returnEntry is false then the token is placed in se's recycle - bin and 0 is returned. If returnEntry is true, the token is - returned to the caller, as is ownership of that token. If se has - no stack entries, 0 is returned. - -*/ -s2_stoken * s2_engine_pop_token( s2_engine * se, char returnEntry ); - - -/** @internal - Similar to s2_engine_pop_token(), this destroys the top-most - token and returns its value (if any), transfering ownership (or - stewardship) of that value to the caller. -*/ -cwal_value * s2_engine_pop_value( s2_engine * se ); - -/** @internal - The operator-stack counterpart of s2_engine_pop_token(). -*/ -s2_stoken * s2_engine_pop_op( s2_engine * se, char returnEntry ); - -/** @internal - Returns the top-most token in se's token stack, without modifying - the stack. Returns 0 if se's token stack is empty. -*/ -s2_stoken * s2_engine_peek_token( s2_engine * se ); - -/** @internal - Equivalent to s2_engine_peek_token(se)->value, but returns 0 - (instead of segfaulting) if the value stack is empty. Does not - modify ownership of the returned value. -*/ -cwal_value * s2_engine_peek_value( s2_engine * se ); - -/** @internal - Swaps the contents of se->st with st. Neither argument - may be NULL. -*/ -void s2_engine_stack_swap( s2_engine * se, s2_estack * st ); - -/** @internal - Clears all entries from se->st, recycling them if possible, - freeing them if not. -*/ -void s2_engine_reset_stack( s2_engine * se ); - -/** @internal - - Runs cwal_engine_vacuum() on se->e. DO NOT CALL THIS - unless you are 100% absolutely sure you want to and that it is safe - to do so (which is very dependent on local conditions). -*/ -void s2_engine_vacuum( s2_engine * se ); - -/** @internal - The op-stack counterpart of s2_engine_peek_token(). -*/ -s2_stoken * s2_engine_peek_op( s2_engine * se ); - -/** @internal - Pushes t onto either the token stack or (if it represents an - operator) the operator stack. - - @see s2_ttype_op() - @see s2_stoken_op() -*/ -void s2_engine_push( s2_engine * se, s2_stoken * t ); - -/** @internal - Processes the top-most operator on the stack and pushes the - result value back onto the stack. - - [i don't think this is true anymore:] A small number of "marker" - operators do not generate a result, and do not push a result onto - the stack, but leave the value stack as they found it. It is not - yet clear whether or not this API needs to provide a way for - clients to know about that. Currently those operators are handled - directly by the higher-level parser code or are handled as part of - another operation (e.g. the S2_T_MarkVariadicStart token marks the - end of N-ary call ops). - - Returns 0 on success, non-0 on error. On error, the stack state - is not well-defined unless CWAL_RC_TYPE is returned (indicating - that the top token is not an operator). -*/ -int s2_process_top( s2_engine * se ); - -/** @internal - A lower-level form of s2_process_top(), this variant does not - modify the operator stack. It is assumed that op is one which was - just popped from it by the caller, or "would have" become the top - of the stack but the push is being elided as an optimization. - - It is up to the caller to ensure that se's stack is set up - appropriately for the given op before calling this. - - Returns 0 on success, a non-0 CWAL_RC_xxx value on error. -*/ -int s2_process_op( s2_engine * se, s2_op const * op ); - -/** @internal - Equivalent to s2_process_op(se, s2_ttype_op(ttype)), except that - it returns CWAL_RC_TYPE if ttype is not an operator. -*/ -int s2_process_op_type( s2_engine * se, int ttype ); - -/** @internal - Clears any pending tokens from se's stack(s). -*/ -void s2_engine_reset_stack( s2_engine * se ); - -/** @internal - - Post-processor for converting a single "opener" token into - a larger token containing the whole braced group, writing - the resulting token to the given output token pointer. - - Requires st->token to be a S2_T_SquigglyOpen, S2_T_BraceOpen, or - S2_T_ParenOpen token. It "slurps" the content part, from the - starting brace up to (and include) the ending brace. It uses - s2_next_token() to parse the group, meaning that it must - contain tokenizable code. We cannot simply do a byte-scan through - it because doing so would exclude any constructs (e.g. strings) - which might themselves contain tokens which would look like - closing tokens here. - - If !out, then st->token is used as the output destination. If out - is not NULL then after returning, st->token and st->pbToken will - have their pre-call state (regardless of error or success). - - Returns 0 on success. On error it will set se's error state. - - On success: - - - out->ttype is set to one of: S2_T_SquigglyBlock, - S2_T_ParenGroup, S2_T_BraceGroup. - - - [out->begin, out->end) will point to the whole token, including - its leading and trailing opener/closer tokens. That range will - have a length of at least 2 (one each for the opening and closing - token). [out->adjBegin, out->adjEnd) will point to the range - encompassing the body of the open/close block, stripped of any - leading or trailing whitespaces. That range may be empty. - -*/ -int s2_slurp_braces( s2_engine *se, s2_ptoker * st, - s2_ptoken * out ); - - -/** @internal - - Post-processor for S2_T_HeredocStart tokens. se is the interpreter, st is - the current tokenizer state. tgt is where the results are written. If tgt - is NULL, st->token is used. - - Requires that st->token be a S2_T_HeredocStart token. This function - scans st for the opening and closing heredoc tokens. - - On error a non-0 CWAL_RC_xxx value is returned and se's error - state will be updated. On error st->token is in an unspecified - state. - - On success tgt->ttype is set to S2_T_SquigglyBlock and - [out->begin, out->end) will point to the whole token proper, - including its leading and trailing opener/closer tokens. That - range will have a length of at least 2 (one each for the opening - and closing token). [out->adjBegin, out->adjEnd) will point to - the range encompassing the body of the open/close block, stripped - of any leading or trailing spaces, as specified below. That range - may be empty. - - Returns 0 on success and sets se's error state on error. - - Syntax rules: - - Assuming that the S2_T_HeredocStart token is '<<<', heredocs - can be constructed as follows: - - <<currentScript is used. If se->currentScript is also 0, or no - error location is recorded somewhere in se->opErrPos or the - script chain then this function has no side effects. - - Returns 0 for a number of not-strictly-error conditions: - - - !pr && !se->currentScript - - - if there is no pending exception, or if that exception value - may not have properties (it is not a container type) - - - OR if the pending exception already contains location - information. -*/ -int s2_exception_add_script_props( s2_engine * se, s2_ptoker const * pr ); - -/** @internal - - Like s2_add_script_props2(), but calculates the script name, line, and column - as follows: - - - The name comes from s2_ptoker_name_top() - - - The script code position (presumably an error location) comes - from s2_ptoker_err_pos_first(). - -*/ -int s2_add_script_props( s2_engine * se, cwal_value * v, s2_ptoker const * pr ); - -/** @internal - - If v is a container, this adds the properties "script", "line", - and "column" to v, using the values passed in. If scriptName is - NULL or empty, it is elided. Likewise, the line/column - properties are only set if (line>0). - - If se has stack trace info, it is also injected into v. Potential - TODO: separate this routine into a couple different bits. The - current behaviour assumes that v is (or will be in a moment) an - Exception. - - @see s2_count_lines() -*/ -int s2_add_script_props2( s2_engine * se, cwal_value * v, - char const * scriptName, int line, int col); - -/** @internal - - Internal cwal_value_visitor_f() implementation which requires - state to be a (cwal_array*). This function appends v to that array - and returns the result. -*/ -int s2_value_visit_append_to_array( cwal_value * v, void * state ); - - -/** @internal - State for script-side functions. Each script function gets - one of these attached to it. -*/ -struct s2_func_state { - /** - The source code for this function. For "empty" functions this - will be NULL. It gets rescoped along with the containing Function - value. - */ - cwal_value * vSrc; - - /** - An Object which holds all "imported" symbols associated with this - function via the "using" pseudo-keyword when defining a function - or via Function.importSymbols(). When this function gets called, - all of these symbols get imported into the call()'s scope. - - 20190911: it "might be interesting" to add a keyword, or even a - local variable, to access this list from inside a - function. Slightly tricky would be to only resolve that symbol - from inside the body of the function it applies to (similar to - how the break keyword only works from within a loop). - - 20191209: the using() keyword now provides access to this. Part - of that change is that this value's prototype is removed. We've - never needed it in native code and remove it now so that script - code does not inadvertently do anything silly with it or trip - over inherited properties. - */ - cwal_value * vImported; - - /** - The name of the function, if any, gets defined as a scope-local - symbol when the function is called, just like this->vImported - does. - */ - cwal_value * vName; - - /** - A hashtable key for the script's name. This key points to a - string in se->funcStash, and is shared by all functions which - have the same script name. Unfortunately, those names must - currently stay in the hash for the life of the interpreter, but - it is not as though that that poses any real problem other than - (potentially) unused script names being kept in in the hash. The - problem is that we don't know when it's safe to remove the entry - (don't know when the last function-held ref to it is gone, and - string interning can confuse the matter). A proper fix (i.e. - which does not keep these strings permanently owned by the global - scope) requires keeping the name and a counter (number of - functions using that name). - - TODO?: now that we have rescopers for functions, we could have - each function store/rescope this itself, without stashing it. - String interning might make this cheap (and might not - filenames - can be long and long strings aren't interned). That could get - expensive for utilities which return lots of small functions - (e.g. utils which bind values for later use). - */ - cwal_value * keyScriptName; - - /** - For the recycling subsystem: this is the next item in the - recycling list. - */ - s2_func_state * next; - - /** - The line (1-based) of the function declaration. Used for - adjusting script location information. - */ - uint16_t line; - - /** - The column (0-based) of the function declaration. Used for - adjusting script location information. - */ - uint16_t col; - - /* Internal flags. */ - cwal_flags16_t flags; - -}; - -/** - Empty-initialized s2_func_state instance. Used as a type ID - in a few places. -*/ -extern const s2_func_state s2_func_state_empty; - -/** @internal - - Internal, experimental, and incomplete. - - Intended to be called at various (many) points during s2's - evaluation process, and passed the (CWAL_RC_xxx) result code of the - local, just-run operation. If that code is a fatal one, it returns - that code without side effects. If it is not a fatal one then this - function returns CWAL_RC_INTERRUPTED _if_ s2_interrupt() has been - called, else it returns localRc. - - If passed 0 and it returns non-0 then s2_interrupt() was called. - - i.e. a "really bad" result code trumps both localRc and - the is-interrupted check, and the is-interrupted check trumps - localRc. - - The "really bad" codes (which are returned as-is) include: - CWAL_RC_FATAL, CWAL_RC_EXIT, CWAL_RC_ASSERT, CWAL_RC_OOM, - CWAL_RC_INTERRUPTED. - - TODO (2021-06-26): strongly consider moving the is-interrupted - check into the cwal core. -*/ -int s2_check_interrupted( s2_engine * se, int localRc ); - -/** @internal - - If f is an s2 script function then its s2 state part - is returned. The state is owned by s2 and MUST NOT - be modified or held for longer than f is. -*/ -s2_func_state * s2_func_state_for_func(cwal_function * f); - -/** @internal - - Looks for a constructor function from operand, _not_ looking in - prototypes (because ctors should generally not be inherited). - - If pr is not NULL then it is assumed to be the currently-evaluating - code and will be used for error reporting purposes if errPolicy != - 0. - - errPolicy determines error handling (meaning no ctor found): - - 0 = return error code but set no error state - - <0 = return error code after throwing error state (so the result - will be CWAL_RC_EXCEPTION unless a lower-level (allocation) failure - trumps it). - - >0 = return error code after setting non-exception error state. - - - If a valid ctor is found: - - - if rv is not NULL, *rv is set to the ctor (in all other cases - *rv is not modified). - - - 0 is returned. - - The error code returned when no viable ctor found is found is - CWAL_RC_NOT_FOUND, but the errorPolicy may translate that to - an exception, returning CWAL_RC_EXCEPTION. -*/ -int s2_ctor_fetch( s2_engine * se, s2_ptoker const * pr, - cwal_value * operand, cwal_function **rv, - int errPolicy ); - - - -/** @internal - - Saves some of se's internal state to the 2nd parameter and - resets it to its initial state in se. If this is called, the - caller is obligated to call s2_engine_subexpr_restore(), - passing the same two parameters, before control returns to - an outer block. -*/ -void s2_engine_subexpr_save(s2_engine * se, s2_subexpr_savestate * to); - -/** @internal - - Restores state pushed by s2_engine_subexpr_save(). -*/ -void s2_engine_subexpr_restore(s2_engine * se, s2_subexpr_savestate const * from); - -/** @internal - - Flags for use with s2_scope_push_with_flags(). They must fit in a - uint16_t. -*/ -enum s2_scope_flags { -/** - Sentinel entry. Must be 0. -*/ -S2_SCOPE_F_NONE = 0, -/** - This tells s2_scope_push_with_flags() to retain any current - s2_engine::ternaryLevel, rather than saving/resetting it when - pushing and restoring it when popping. It is intended for "inlined" - (non-block) expressions which push a scope, so that in-process - ternary op handling can DTRT with a ':' in the RHS of such - expressions. e.g. (foo ? catch someFunc() : blah). Without this - flag, the catch expression will try to consume the ':' and fail - because it doesn't see that there's a ternary-if being - processed. This flag makes scope/catch/etc. aware that a ':' is - part of a pending ternary op. Note that (foo ? catch - {someFunc():blah()}), where the ":" is inside {...} still triggers - a syntax error. -*/ -S2_SCOPE_F_KEEP_TERNARY_LEVEL = 0x0001, - -/** - Not yet quite sure what this is for. It might go away. -*/ -S2_SCOPE_F_IS_FUNC_CALL = 0x0002 -}; - -/** @internal - - This is equivalent to calling cwal_scope_push(), then setting - the given flags on the new s2_scope which gets added to the - stack via the cwal_engine's scope push hook. - - Returns 0 on success. On error non-0 is returned (a CWAL_RC_xxx - code) then pushing of the scope has failed (likely CWAL_RC_OOM) - and something is Seriously Wrong. -*/ -int s2_scope_push_with_flags( s2_engine * se, cwal_scope * tgt, uint16_t flags ); - - -/** @internal - - Returns se's current s2-level scope, or 0 if no scope is - active. ACHTUNG: the returned pointer can be invalidated by future - pushes/pops of the cwal stack, so it MUST NOT be held on to across - push/pop boundaries. Rather than holding an (s2_scope*) across - push/pop boundaries, call this as needed to get the current - pointer. - - @see cwal_scope_push() - @see cwal_scope_pop() - @see cwal_scope_pop2() -*/ -s2_scope * s2_scope_current( s2_engine const * se ); - - -/** @internal - - Returns the s2-level scope for the current cwal scope level, or - NULL if level is out of bounds. cwal scoping starts at level 1 and - increases by 1 for each new scope popped on the stack. i.e passing - this a level of 1 will return the top-most scope. The current - scoping level can be found in s2_engine::scopes::level or - via the 'level' member of the cwal_scope object returned by - cwal_scope_current_get(). - - Note that the scopes have very strict lifetime rules, and clients - must never modify their state or clean them up or anything silly - like that. - - The cwal_scope counterpart of the returned object can be obtained - via its cwalScope member. - - Scope levels are managed by cwal, but s2 keeps its own scopes - (s2_scope) for holding s2-specific scope-level state. These are - kept in sync via the cwal-level scope push/pop hooks. - - IMPORTANT: the returned pointer can be invalidated by future pushes - onto, and pops from, the scope stack, and thus it must never be - held across scope-push/pop boundaries. The poitner should be - fetched as needed and then discard. - - @see cwal_scope_push() -*/ -s2_scope * s2_scope_for_level( s2_engine const * se, cwal_size_t level ); - -/** @internal - - Sets up se's internals so that it knows the most recent dot-op - self/lhs/key (or is not, if passed NULLs for the last 3 - arguments). If self is not NULL then self is considered to be - "this" in the corresponding downstream handling (i.e. it's a - property access which binds "this"), else it does not. self is - always lhs or 0. lhs is either the container for a property lookup - (if self!=0) or a scope var lookup. key is the associated property - key. - - The property/scope variable operations set these up and several - places in the eval engine (namely function calls) consume and/or - clear/reset them (to avoid leaving stale entries around). - - Pass all 0's to reset it. - - Reminder to self: before calling this, MAKE SURE you've got ref to - any local values which are about to returned/passed along. If - needed, ref before calling this and unhand afterwards. If a value - about to be returned/upscoped is one of - se->dotOpThis/dotOp.key/dotOp.lhs then that ref will save the app's - life. -*/ -void s2_dotop_state( s2_engine * se, cwal_value * self, - cwal_value * lhs, cwal_value * key ); - - -/** @internal - - An internal operator/keyword-level helper to be passed the result - code from the s2_set() family of funcs, to allow those routines to - report error information, including location, for the failure - point. Returns rc or a modified form of it when moving - s2_engine::err state to an exception. - - If rc is not 0 then s2_throw_err_ptoker() may (with a small few - exceptions) may be called to propagate any error info, in which - case se->err is assumed to have been set up by s2_set() (and - friends). Some few error codes are simply returned as-is, without - collecting error location info: CWAL_RC_OOM, CWAL_RC_EXCEPTION, - CWAL_RC_NOT_FOUND. - - If !pr then se->currentScript is used. If both are NULL, or !rc, - this is a no-op. -*/ -int s2_handle_set_result( s2_engine * se, s2_ptoker const * pr, int rc ); - -/** @internal - - The getter analog of s2_handle_set_result(). -*/ -int s2_handle_get_result( s2_engine * se, s2_ptoker const * pr, int rc ); - - -/** @internal - Callback function for operator implementations. - - The framework will pass the operator, the underlying engine, the - number of operatand it was given (argc). - - Preconditions: - - - se's token stack will contain (argc) tokens intended - for this operator. - - - The top of se operator stack will not be this operator - (or not this invocation of it). - - - rv will not be NULL, but may point to a NULL pointer. - - Implementation requirements: - - - Implementations MUST pop EXACTLY (argc) tokens from se's - token stack before returning or calling into any API which - might modify se's token stack further. They must treat those - values as if they are owned by the caller. - - - A result value, if any, must be assigned to *rv. If the - operation has no result, assign it to 0. The API will never pass - a NULL rv to this routine. If the operation creates a scope and - receives *rv from that scope then it must be sure to upscope *rv - before returning, to ensure that *rv is still valid after the - scope is popped. As a rule, the result value is ignored/discarded - if non-0 (error) is returned. - - - Must return 0 on success, and a non-0 CWAL_RC_xxx code on - error. - - - If se->skipLevel is greater than 0, then the operator must pop - all arguments from the stack, do as little work as possible - (e.g. no allocations or calculations, and no visible side effects), - assign *rv to cwal_value_undefined(), and return 0. This is used - for implementing quasi-short-circuit logic, in that we allow the - operators to run, but skip-mode indicates that we really are only - interested in getting past the operator and its arguments, without - having side-effects like creating new values. - - Certain result codes will be treated specially by the framework and - have certain pre- and post-conditions (not described in detail here): - - CWAL_RC_OOM triggers a fatal OOM error. - - CWAL_RC_EXCEPTION means the function triggered an exception - and set the engine's exception state. - - CWAL_RC_EXIT, CWAL_RC_FATAL, and CWAL_RC_INTERRUPTED trigger and - end of the current evaluation and set the engine's error state. -*/ -typedef int (*s2_op_f)( s2_op const * self, s2_engine * se, - int argc, cwal_value **rv ); - -/** @internal - Represents a stack machine operation. Each operation in s2 equates - to a shared/const instance of this class. -*/ -struct s2_op { - /** - Generic symbolic representation, not necessarily what appears - in script code. Need not be unique, either. Primarily intended - for use in debugging and error messages. - */ - char const * sym; - - /** - Operator type ID. Must be one of the s2_token_types - values. - */ - int id; - - /** - Number of expected operands. Negative value means any number, and the - syntax will have to determine where to stop looking for operands. - */ - int8_t arity; - - /** - Associativity: - - <0 = left - - 0 = non-associative - - >0 = right - */ - int8_t assoc; - - /** - Precedence. Higher numbers represent a higher precedence. - */ - int8_t prec; - - /** - Describes where the operator "sits" in relation to its - operand(s). - - <0 = has no operands or sits on the left of its - operand(s). Arity must be >=0. - - 0 = sits between its 2 operands. arity must == 2. - - >0 = sits on the right of its 1 operand. arity must == 1. - - */ - int8_t placement; - - /** - The operator's implementation function. - */ - s2_op_f call; - - /** - Not yet used. - - An experiment in inferring compound comparison operators from - simpler operators, e.g. infering '<' and '==' from '<=' and using - them if the latter op is not available but the former two are. - - inferLeft is the LHS op id, inferRight is the RHS id. - */ - int inferLeft; - /** - Not yet used. - */ - int inferRight; - - /** - Not currently used. Intended to replace the inferLeft/inferRight - bits (which also aren't currently used). - - For "assignment combo" operators (+=, -=, etc). s2 "should" be - able to derive an implementation if the non-assignment part of - that has been overloaded (e.g. if operator+ is overloaded, s2 - should be able to derive operator+= from that). We could (in - theory) safely always evaluate such ops to their own "this" - value, and wouldn't(?) even need to perform the actual assignment - because such overloads always need to return their own "this" in - order to work properly (and the overload presumably applies the - operator to itself, as opposed to a new copy, which would break - assignments). If someone really wants to return non-this, they - could overload it explicitly, which would of course trump a - derived version. - - Anyway... for assignment combo ops this "should" be the s2_op::id - of the related non-assignment operator which "could" be used to - automatically implement the combo op. - */ - int derivedFromOp; -}; - -/** @internal - Empty-initialized s2_op structure, intended for - const-copy initialization. Only used internally. -*/ -#define s2_op_empty_m { \ - 0/*sym*/, 0/*id*/, 0/*arity*/, \ - 0/*assoc*/, 0/*prec*/, \ - 0/*placement*/, 0/*call()*/, \ - 0/*inferLeft*/, 0/*inferRight*/, \ - 0/*derivedFromOp*/ \ - } - -#if 0 -/** @internal - Empty-initialized s2_op structure, intended for - copy initialization. -*/ -extern const s2_op s2_op_empty; -#endif - -/** @internal - If ttype (a s2_token_types value) represents a known Operator - then that operator's shared/static/const instance is returned, - otherwise NULL is returned. -*/ -s2_op const * s2_ttype_op( int ttype ); - -/** @internal - Equivalent to s2_stoken_op(t->type). -*/ -s2_op const * s2_stoken_op( s2_stoken const * t ); - -int s2_op_is_math( s2_op const * op ); -int s2_op_is_expr_border( s2_op const * op ); -int s2_op_is_unary_prefix( s2_op const * op ); - -/** @internal - Equivalent to s2_ttype_short_circuits(op ? op->id : 0). -*/ -int s2_op_short_circuits( s2_op const * op ); - -/** @internal - - Rescopes, if needed, v to lhs's scope. Intended for use just after - v is created and v is held on to by a native value associated with - lhs (e.g. when lhs is bound to s2_func_state and v is one of - s2_func_state::vSrc, vImported, or vName). When creating such - values, it's almost always critical to adjust the new value's - scope just after creating it. Once the underlying binding is in - place, the native-level rescoper is responsible for future - rescoping of the value. -*/ -void s2_value_to_lhs_scope( cwal_value const * lhs, cwal_value * v); - - -/** @internal - Intended to be passed the result of s2_set_v() (or similar). - This routine upgrades certain result codes to engine-level - error state and returns rc or some other error code. If !rc, - it always returns rc. -*/ -int s2_handle_set_result( s2_engine * se, s2_ptoker const * pr, int rc ); - - -/** @internal - Intended to be passed the result of s2_get_v() (or similar). - This routine upgrades certain result codes to engine-level - error state and returns rc or some other error code. If !rc, - it always returns rc. -*/ -int s2_handle_get_result( s2_engine * se, s2_ptoker const * pr, int rc ); - -/** @internal - - Fetches fst's "imported symbols" holder object, initializing it if - needed (which includes rescoping it to theFunc's scope). theFunc - _must_ be the Function Value to which fst is bound. - - If this function creates the imported symbols holder, its prototype - is removed. - - Is highly intolerant of NULL arguments. - - Returns NULL on OOM, else the properties object (owned by fst - resp. theFunc). -*/ -cwal_value * s2_func_import_props( s2_engine * se, - cwal_value const * theFunc, - s2_func_state * fst ); - - -/** @internal - - A kludge/workaround for the eval engine's lack of references on - its being-eval'd values (string interning, in particular, can bite - us, leading to cwal-level assert() failures when lifetime rules - are violated). VERY LONG STORY made very short: this routine adds - v (if it's not NULL and not a builtin value) to - s2->currentScope->evalHolder (if that list is not NULL) to keep a - reference to them. - - Returns 0 if it does nothing or if adding v to the list succeeds, - non-0 on serious error (almost certainly CWAL_RC_OOM, as that's - the only realistic error result). - See s2_scope::evalHolder for more details. -*/ -int s2_eval_hold( s2_engine * se, cwal_value * v ); - -/** @internal - - Flags for s2_func_state::flags. -*/ -enum s2_func_state_flags_t { -S2_FUNCSTATE_F_NONE = 0, -/** - Indicates that the function's parameter list contains no non-junk - tokens, so it need not be evaluated at call()-time. -*/ -S2_FUNCSTATE_F_EMPTY_PARAMS = 0x01, -/** - Indicates that the function's body contains no non-junk tokens, so - it need not be evaluated at call()-time. -*/ -S2_FUNCSTATE_F_EMPTY_BODY = 0x02, -/** - This flag indicates that the "using" clause of a function - definition was tagged to indicate that any imported symbols (via - "using" or Function.importSymbols()) should not be declared as - call-local variables. Instead, they may be accessed in the function - via the "using" keyword. e.g.: - - proc()using.{a: 1}{ - assert !typeinfo(islocal a); - s2out << using.a << '\n'; - }; - - Without the "." modifier, that assert would fail. - - Mnemonic: "using." is exactly how the symbols nee -*/ -S2_FUNCSTATE_F_NO_USING_DECL = 0x04, -/** - Reserved. -*/ -S2_FUNCSTATE_F_CLASS = 0x08, -/** - EXPERIMENTAL and likely to go away: - - When a function has this flag, if its body contains the - string s2_engine::cache::keyInterceptee then this flag - gets set. -*/ -S2_FUNCSTATE_F_INTERCEPTEE = 0x10 -}; - -typedef struct s2_keyword s2_keyword; - -/** @internal - - Keyword handler signature. - -*/ -typedef int (*s2_keyword_f)( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv); - -/** @internal - - Holds state for a single keyword. -*/ -struct s2_keyword { - int /* non-const needed internally */ id; - char const * word; - unsigned short /*non-const needed for ukwd */ wordLen; - s2_keyword_f call; - /** - If true, then LHS invocations of this keyword will, if they terminate - in a {block}, treat an EOL immediately following as an EOX. This is - to make 'if' and friends easier on the eyes, as nobody wants to be forced - to add a semicolon to: - - if(...){ - - }; - */ - char allowEOLAsEOXWhenLHS; -#if 0 - /** - An internal detail of the ukwd bits. Doh - adding this would - require editing the inlined initialization structs of all the - built-in keywords :/. Time to go macro-fy that, it seems. - */ - cwal_value * ukwd; -#endif -}; - -/** - Structure for holding a list of "user keywords" (UKWDs, as they're - colloquially known). -*/ -struct s2_ukwd { - /** - List of keyword object. - */ - s2_keyword * list; - /** - Hash of UKWD names to their values. - */ - cwal_hash * h; - /** - Number of entries in this->list which are currently in use. - */ - cwal_size_t count; - /** - Number of entries allocated for this->list. - */ - cwal_size_t alloced; -}; -typedef struct s2_ukwd s2_ukwd; - -/** @internal - - Frees any memory held by se->ukwd, including se->ukwd itself. -*/ -void s2_ukwd_free(s2_engine * se); - -/** @internal - - A "perfect hasher" for s2 keywords. This algo uses a simple hashing - mechanism which is known to produce collision-free hashes for the - s2 built-in keywords and typeinfo/pragma operators. - - It hashes the first n bytes of src and returns the hash. - - We have a hash code limit of 32 bits because these values are used - in switch/case statements, and we cannot portably use values >32 - bits as a case value. - - It returns 0 if the input length is 0 or the input contains any - characters with a value less than '$' or greater than 'z', which - includes all non-ASCII UTF8, noting that UKWDs are not hashed this - way, so they may still contain any s2-legal identifier characters. - - Maintenance WARNING: this algo MUST be kept in sync with the one in - s2-keyword-hasher.s2, as that script generates the C code - for our keyword/typeinfo/pragma bits. -*/ -uint32_t s2_hash_keyword( char const * src, cwal_size_t n ); - -#ifdef __cplusplus -}/*extern "C"*/ -#endif -#endif /* DOXYGEN */ - -#endif -/* include guard */ -/* end of file s2_internal.h */ -#endif/*!defined(WANDERINGHORSE_NET_CWAL_S2_AMALGAMATION_H_INCLUDED)*/ DELETED bindings/s2/shell2.c Index: bindings/s2/shell2.c ================================================================== --- bindings/s2/shell2.c +++ bindings/s2/shell2.c @@ -1,177 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/** - s2sh2 - a cleanup of s2sh. - - s2sh2 is functionally *nearly* identical to s2sh, the primary - difference being that s2sh2 has more conventional/cleaned-up CLI - argument handling. Many of the flags which are toggles in s2sh have - only a single form in s2sh2, to turn something on or off, but not - toggle it. - - Other minor differences: - - - $2 is a UKWD referring to the global s2 object. -*/ - -#define S2SH_VERSION 2 -#include "shell_common.c" - -/** - gcc bug: if this function is marked static here, it does - not recognize that shell_common.c uses it and declares that - it's unused. -*/ -static CliAppSwitch const * s2sh_cliapp_switches(){ - static const CliAppSwitch cliAppSwitches[] = { - /* {opaque, dash, key, value, brief, details, callback, reserved} */ - /** Fills out most of a CliAppSwitch entry */ -#define F6(IDFLAGS,DASHES,KEY,VAL,BRIEF,PFLAGS) \ - {IDFLAGS, DASHES, KEY, VAL, BRIEF, 0, 0, PFLAGS} - /** Continues the help text for the previous entry. */ -#define FCONTINUE(LABEL) {SW_CONTINUE, 0, 0, 0, LABEL, 0, 0, 0} - /** Adds an "additional info" line, not indented. */ -#define FINFOLINE(LABEL) {SW_INFOLINE, 0, 0, 0, LABEL, 0, 0, 0} - /** -short | --long[=VALUE]*/ -#define FX(IDFLAGS,SHORT,LONG,VALUE,BRIEF,PFLAGS) \ - F6(IDFLAGS,2,LONG,VALUE,BRIEF,PFLAGS), \ - F6(IDFLAGS,1,SHORT,VALUE,0,PFLAGS) - /** -short, --long on/off switches ...*/ -#define F01(IDFLAGS,SHORT,LONG,BRIEF) \ - {IDFLAGS+1, 2, LONG, 0, "Enable " BRIEF, 0, 0, 0}, \ - {IDFLAGS+1, 1, SHORT, 0, "Enable " BRIEF, 0, 0, 0}, \ - {IDFLAGS, 2, "no-"LONG, 0, "Disable " BRIEF, 0, 0, 0}, \ - {IDFLAGS, 1, "no"SHORT, 0, "Disable " BRIEF, 0, 0, 0} - /** Start the next group (for --help grouping). */ -#define GROUP(GID,DESC) {GID, 0, 0, 0, DESC, 0, 0, 0} - - /***********************************************************************/ - GROUP(SW_GROUP_0,0), - - /** - Keep these arranged so that flags with short and long forms are - ordered consecutively, long one first. The help system uses that to - collapse the flags into a single listing. In such cases, the help - text from the first entry is used and the second is ignored. - */ - FX(SW_HELP, "?", "help", 0, - "Send help text to stdout and exit with code 0.", 0), - FCONTINUE("The -v flag can be used once or twice to get more info."), - - FX(SW_VERBOSE,"v","verbose",0, - "Increases verbosity level by 1.", 0), - - FX(SW_VERSION,"V","version",0, - "Show version info and exit with code 0. ", 0), - FCONTINUE("Use with -v for more details."), - - FX(SW_ESCRIPT,"e","eval","SCRIPT", - "Evaluates the given script code in the global scope. " - "May be used multiple times.", CLIAPP_F_SPACE_VALUE), - - FX(SW_INFILE, - "f","file","infile", - "Evaluates the given file. The first non-flag argument is used " - "by default.", CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE), - - FX(SW_OUTFILE, "o", "output", "outfile", - "Sets cwal's output channel to the given file. Default is stdout.", - CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE), - - /***********************************************************************/ - GROUP(SW_GROUP_1, "Less-common options:"), - - FX(SW_ASSERT_1,"A", "assert", 0, "Increase assertion trace level by one: " - "1=trace assert, 2=also trace affirm.",0), - - FX(SW_INTERACTIVE,"i", "interactive", 0, - "Force-enter interactive mode after running -e/-f scripts.",0), - - FX(SW_AUTOLOAD_0,"I","no-init-script", 0, - "Disables auto-loading of the init script.",0), - FCONTINUE("Init script name = same as this binary plus \".s2\" "), - FCONTINUE("or set via the S2SH_INIT_SCRIPT environment variable."), - - /***********************************************************************/ - GROUP(SW_GROUP_2, "Esoteric options:"), - - FX(SW_METRICS, "m", "metrics", 0, - "Show various cwal/s2 metrics before shutdown.",0), - FCONTINUE("Use -v once or twice for more info."), - - F01(SW_MOD_INIT_0, "mi", "module-init", - "automatic initialization of static modules."), - - F6(SW_SHELL_API_1,2,"s2.shell",0, - "Forces installation of the s2.shell API unless trumped by --cleanroom.",0), - FCONTINUE("By default s2.shell is only installed in interactive mode."), - - F6(SW_TRACE_CWAL,2,"trace-cwal", 0, - "Enables tremendous amounts of cwal_engine " - "tracing if it's compiled in.",0), - - F01(SW_SCOPE_H0, "hash", "scope-hashes", - "hashes for scope property storage (else objects)."), - - FX(SW_HIST_FILE_1, - "h", "history", "filename", - "Sets the interactive-mode edit history filename.", - CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE), - - F6(SW_HIST_FILE_0,2, "no-history", 0, - "Disables interactive-mode edit history.", 0), - F6(SW_HIST_FILE_0,1, "noh", 0, 0, 0), - - F6(SW_DISABLE, 2, "s2-disable", "comma,list", - "Disables certain s2 API features by name.",0), - FCONTINUE("Use a comma-and/or-space-separated list with any of " - "the following entries:"), - FCONTINUE("fs-stat, fs-read, fs-write, fs-io, fs-all"), - - F6(SW_CLEANROOM,2,"cleanroom",0, - "Only core language functionality with no prototype-level methods.",0), - FCONTINUE("Disables the s2/$2 global object and auto-loading of the " - "init script."), - - FINFOLINE("Recycling-related options:"), - F01(SW_RE_V0,"rv", "recycle-values", "recycling of value instances."), - F01(SW_RE_C0,"rc","recycle-chunks", "the memory chunk recycler."), - F01(SW_RE_S0,"si","string-interning", "string interning."), - - FINFOLINE("Memory-tracking/capping options:"), - FX(SW_MCAP_TOTAL_ALLOCS, "cap-ta", "memcap-total-allocs", "N", - "* Limit the (T)otal number of (A)llocations to N.", - CLIAPP_F_SPACE_VALUE), - FX(SW_MCAP_TOTAL_ALLOCS, "cap-tb", "memcap-total-bytes", "N", - "* Limit the (T)otal number of allocated (B)ytes to N.", - CLIAPP_F_SPACE_VALUE), - FX(SW_MCAP_TOTAL_ALLOCS, "cap-ca", "memcap-concurrent-allocs", "N", - "Limit the (C)oncurrent number of (A)llocations to N.", - CLIAPP_F_SPACE_VALUE), - FX(SW_MCAP_TOTAL_ALLOCS, "cap-cb", "memcap-concurrent-bytes", "N", - "Limit the (C)oncurrent number of allocated (B)ytes to N.", - CLIAPP_F_SPACE_VALUE), - FX(SW_MCAP_TOTAL_ALLOCS, "cap-sb", "memcap-single-alloc", "N", - "Limit the size of any (S)ingle allocation to N (B)ytes.", - CLIAPP_F_SPACE_VALUE), - FINFOLINE("The allocator starts failing (returning NULL) when a capping constraint is violated."), - FCONTINUE("The 'tb' and 'ta' constraint violations are permanent (recovery requires resetting the engine)."), - FCONTINUE("The 'ca' and 'cb' constraints are recoverable once some cwal-managed memory gets freed."), - FCONTINUE("Capping only applies to memory allocated by/via the scripting\n" - "engine, not 3rd-party APIs."), - - F6(SW_MEM_FAST1,2,"fast",0,"(F)orce (A)lloc (S)ize (T)racking.",0), - FCONTINUE("Enables more precise tracking and reuse of recycled memory."), - FCONTINUE("Several memcap options enable --fast implicitly."), - -#undef GROUP -#undef F01 -#undef F6 -#undef FX -#undef FPLUS -#undef FPLUSX -#undef FINFOLINE - CliAppSwitch_sentinel /* MUST be the last entry in the list */ - }; - return cliAppSwitches; -} DELETED bindings/s2/shell_common.c Index: bindings/s2/shell_common.c ================================================================== --- bindings/s2/shell_common.c +++ bindings/s2/shell_common.c @@ -1,2075 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ - -/************************************************************************ - -This file is intended to be included directly into shell.c -(a.k.a. s2sh) and shell2.c (a.k.a. s2sh2), not compiled by itself. It -contains all of the code which is common to both versions of the shell -(which is everything but their --help bits). - -************************************************************************/ - -#ifndef S2SH_VERSION -# error define S2SH_VERSION to 1 or 2 before including this file. -#elif (S2SH_VERSION!=1) && (S2SH_VERSION!=2) -# error S2SH_VERSION must bet to 1 or 2. -#endif - -#include - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#if defined(S2_AMALGAMATION_BUILD) || 2==S2SH_VERSION -# include "s2_amalgamation.h" -#else -# include "s2.h" -#endif -/* ^^^^ may include config stuff used by system headers */ - -#if !defined(S2SH_FOR_UNIT_TESTS) -/* This option enables options which are only in place for - purposes of s2's own unit test suite. e.g. to communicate - compile-time options which may change test paths. -*/ -# define S2SH_FOR_UNIT_TESTS 0 -#endif - -#include "cliapp.h" - -#if defined(S2_OS_UNIX) -# include /* isatty() and friends */ -#endif -#include -#include -#include -#include /* setlocale() */ - -/* Must be defined by shell.c/shell2.c in a way compatible - with cliApp.switches. */ -static CliAppSwitch const * s2sh_cliapp_switches(); - -/** - ID values and flags for CLI switches. Some are only used by one of - s2sh or s2sh2. -*/ -enum s2sh_switch_ids { -/** Mask of the bits used as flag IDs. The remainder are flag/modifier - bits. */ -SW_ID_MASK = 0xFFFF, -SW_ID_MASK_bits = 16, -#define ID(X) (X<< SW_ID_MASK_bits) - -/** - An entry flagged as SW_INFOLINE is intended to be a standalone line - of information unrelated to a specific flag. If it needs to span - lines, it must embed \n characters. --help outputs its "brief" - member. -*/ -SW_INFOLINE = ID(1), -/** - An entry flagged with SW_CONTINUE must immediately follow either an - SW_INFOLINE, a switch entry (SW_ID_MASK), or another entry with - this flag. --help outputs its "brief" member, indented or not, - depending on the previous entry. -*/ -SW_CONTINUE = ID(2), - -/** - SW_GROUP_x are used for grouping related elements into tiers. -*/ -SW_GROUP_0 = ID(4), -SW_GROUP_1 = ID(8), -SW_GROUP_2 = ID(0x10), -SW_GROUPS = SW_GROUP_0 | SW_GROUP_1 | SW_GROUP_2, - -/** - Switches with this bit set are not output by --help. - (Workaround for s2sh -help/--help/-? having 3 options!) -*/ -SW_UNDOCUMENTED = ID(0x20), - -#undef ID - -SW_switch_start = 0, -/* - Entries for individual CLI switches... - - DO NOT bitmask the SW_GROUP entries directly with the - switch-specific entries. They are handled in a separate step. Long - story. - - The SW_ID_MASK bits of the switch ID values need not be bitmasks. - i.e. they may have bits which overlap with other entries, provided that - the SW_ID_MASK part is unique. - - All switches which *require* a value, as opposed to optionally - accept a value, should, for consistency, have their pflags member - OR'd with CLIAPP_F_SPACE_VALUE. - - All switches which may only be provided once must have their pflags - field OR'd with CLIAPP_F_ONCE. -*/ -SW_CLAMPDOWN/*v1. No longer allowed.*/, -SW_CLEANROOM, -SW_DISABLE, -SW_ESCRIPT, -SW_HELP, -SW_INFILE, -SW_INTERACTIVE, -SW_INTERNAL_FU, -SW_METRICS, -SW_OUTFILE, -SW_SHOW_SIZEOFS, -SW_SWEEP_INTERVAL, -SW_VAC_INTERVAL, -SW_VERBOSE, -SW_VERSION, - -/** - The entries ending with 0 or 1 are toggles. The #1 part of each pair - MUST have a value equal to the corresponding #0 entry plus 1. -*/ -SW_MEM_FAST0, SW_MEM_FAST1, -/** SW_RE_xxx = recycling-related options */ -/* [no-]recycle-chunks */ -SW_RE_C0, SW_RE_C1, -/* [no-]string-interning */ -SW_RE_S0, SW_RE_S1, -/* [no-]recycle-values */ -SW_RE_V0, SW_RE_V1, -/* [no-]scope-hashes */ -SW_SCOPE_H0, SW_SCOPE_H1, -/* --T/-T */ -SW_TRACE_STACKS_0, SW_TRACE_STACKS_1, -/* v1: --W/-w, v2 */ -SW_TRACE_SWEEP_0, SW_TRACE_SWEEP_1, - -/* v1: --s2.shell/-s2.shell, v2: na/--s2.shell */ -SW_SHELL_API_0, SW_SHELL_API_1, -/* v1: --M/-M, v2: --no-module-init/na */ -SW_MOD_INIT_0, SW_MOD_INIT_1, -/** v1: --A/-A */ -SW_ASSERT_0, SW_ASSERT_1, -/** v1: --h/-h */ -SW_HIST_FILE_0, SW_HIST_FILE_1, -/** v1: --a/-a, v2: na/-I -*/ -SW_AUTOLOAD_0, SW_AUTOLOAD_1, - -SW_TRACE_CWAL, - -/* Memory-capping options... */ -SW_MCAP_TOTAL_ALLOCS, -SW_MCAP_TOTAL_BYTES, -SW_MCAP_CONC_ALLOCS, -SW_MCAP_CONC_BYTES, -SW_MCAP_SINGLE_BYTES, - -SW_end /* MUST be the last entry */ -}; - - -#if !defined(S2_SHELL_EXTEND_FUNC_NAME) -# define S2_SHELL_EXTEND_FUNC_NAME s2_shell_extend -#endif -/* - If S2_SHELL_EXTEND is defined, the client must define - s2_shell_extend() (see shell_extend.c for one implementation) and - link it in with this app. It will be called relatively early in the - initialization process so that any auto-loaded scripts can make use - of it. It must, on error, return one of the non-0 CWAL_RC_xxx error - codes (NOT a code from outside the range! EVER!). An error is - treated as fatal to the shell. - - See shell_extend.c for a documented example of how to use this - approach to extending this app. -*/ -#if defined(S2_SHELL_EXTEND) -extern int S2_SHELL_EXTEND_FUNC_NAME(s2_engine * se, cwal_value * mainNamespace, - int argc, char const * const * argv); -#endif - -/* - MARKER((...)) is an Internally-used output routine which - includes file/line/column info. -*/ -#define MARKER(pfexp) \ - if(1) cliapp_print("%s:%d: ",__FILE__,__LINE__); \ - if(1) cliapp_print pfexp -#define MESSAGE(pfexp) cliapp_print pfexp -#define WARN(pfexp) \ - if(1) cliapp_warn("%s: ",App.appName); \ - if(1) cliapp_warn pfexp -#define VERBOSE(LEVEL,pfexp) if(App.verbosity >= LEVEL) { cliapp_print("verbose: "); MESSAGE(pfexp); }(void)0 - -/** - S2_AUTOINIT_STATIC_MODULES tells the shell whether to initialize, or - not, statically-linked-in modules by default. -*/ -#if !defined(S2_AUTOINIT_STATIC_MODULES) -# define S2_AUTOINIT_STATIC_MODULES 1 -#endif - -/** - S2SH_MAX_SCRIPTS = max number of scripts to run, including the -e - scripts and the main script filename, but not the autoload script. -*/ -#define S2SH_MAX_SCRIPTS 10 - -/** - Global app-level state. -*/ -static struct { - char const * appName; - char enableArg0Autoload; - int enableValueRecycling; - /** - Max chunk size for mem chunk recycler. Set to 0 to disable. Must - allocate sizeof(void*) times this number, so don't set it too - high. - */ - cwal_size_t maxChunkCount; - int enableStringInterning; - int showMetrics; - int verbosity; - int traceAssertions; - int traceStacks; - int traceSweeps; - char scopesUseHashes; - char const * inFile; - char const * outFile; - char const * const * scriptArgv; - int scriptArgc; - int addSweepInterval; - int addVacuumInterval; - /** - Name of interactive most history file. Currently - looks in/writes to only the current dir. - */ - char const * editHistoryFile; - /** - Default prompt string for the line-reading functions. - */ - char const * lnPrompt; - /** - 0=non-interactive mode, >0=interactive mode, <0=as-yet-unknown. - */ - int interactive; - /** - This is the "s2" global object. It's not generally kosher to hold - global cwal_values, but (A) we're careful in how we do so and (B) - it simplifies a couple of our implementations. - */ - cwal_value * s2Global; - /** - When shellExitFlag!=0, interactive readline looping stops. - */ - int shellExitFlag; - - /** - If true, do not install prototypes or global functionality. Only - "raw s2" is left. - */ - int cleanroom; - - /** - Flags for use with s2_disable_set_cstr(). - */ - char const * zDisableFlags; - - /** - If true AND if "static modules" have been built in, this - initializes those modules. - */ - int initStaticModules; - - /** - A flag used to tell us not to save the history - we avoid saving - when the session has no commands, so that we don't get empty - history files laying around. Once s2sh_history_add() is called, - this flag gets set to non-0. - - */ - int saveHistory; - - /** - A flag to force installation of the s2.shell API, even in non-interactive - mode. <0 = determine automatically. 0 = force off. >0 = force on unless - -cleanroom trumps it. - */ - int installShellApi; - - /** - Don't look at this unless you know _exactly_ what you're doing - with s2's internals. It's for my eyes only. - */ - int enableInternalFu; - - /** - Memory-capping configuration. Search this file for App.memcap for - how to set it up. - */ - cwal_memcap_config memcap; - - /** - Scripts for the -e/-f flags. - */ - struct { - int nScripts /*current # of entries in this->e*/; - struct { - /* True if this->src is a filename, else it's script code. */ - int isFile; - /* Filename or script code. */ - char const * src; - } e[S2SH_MAX_SCRIPTS]; - } scripts; - int32_t cwalTraceFlags; -} App = { -0/*appName*/, -1/*enableArg0Autoload*/, -1/*enableValueRecycling*/, -50/*maxChunkCount*/, -1/*enableStringInterning*/, -0/*showMetrics*/, -0/*verbosity*/, -0/*traceAssertions*/, -0/*traceStacks*/, -0/*traceSweeps*/, -0/*scopesUseHashes*/, -0/*inFile*/, -0/*outFile*/, -0/*scriptArgv*/, -0/*scriptArgc*/, -0/*addSweepInterval*/, -0/*addVacuumInterval*/, -"s2sh.history"/*editHistoryFile*/, -#if 1==S2SH_VERSION -"s2sh> "/*lnPrompt*/, -#else -"s2sh2> "/*lnPrompt*/, -#endif --1/*interactive*/, -0/*s2Global*/, -0/*shellExitFlag*/, -0/*cleanroom*/, -0/*zDisableFlags*/, -S2_AUTOINIT_STATIC_MODULES/*initStaticModules*/, -0/*saveHistory*/, --1/*installShellApi*/, -0/*enableInternalFu*/, -cwal_memcap_config_empty_m/*memcap*/, -{0/*nScripts*/, {/*e*/{0,0}}}, -0/*cwalTraceFlags*/ -}; - - -static cwal_engine * printfEngine = 0; -static void s2sh_print(char const * fmt, ...) { - va_list args; - va_start(args,fmt); - if(printfEngine){ - cwal_outputfv(printfEngine, fmt, args); - }else{ - cliapp_print(fmt,args); - } - va_end(args); -} - -/** - Called by cwal_engine_init(). This does the lower-level (pre-s2) - parts of the cwal_engine configuration. This part must not create - any values, as s2 will destroy them right after this is called so - that it can take over ownership of the top-most scope. Thus only - configurable options are set here, and new functionality is added - during the s2 part of the initialization. -*/ -static int s2sh_init_engine(cwal_engine *e, cwal_engine_vtab * vtab){ - int rc = 0; - int32_t featureFlags = 1 - ? 0 - : CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP; - - cwal_engine_trace_flags(e, App.cwalTraceFlags); - if(App.enableStringInterning){ - featureFlags |= CWAL_FEATURE_INTERN_STRINGS; - } - if(App.scopesUseHashes){ - /* If enabled, scopes will use hashtables for property storage. - This are potentially more costly, in terms of memory, but - are much faster. */ - featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH; - if(vtab){/*avoid unused param warning*/} - }else{ - featureFlags &= ~CWAL_FEATURE_SCOPE_STORAGE_HASH; - } - cwal_engine_feature_flags(e, featureFlags); - - { - /* Configure the memory chunk recycler... */ - cwal_memchunk_config conf = cwal_memchunk_config_empty; - conf.maxChunkCount = App.maxChunkCount; - conf.maxChunkSize = 1024 * 32; -#if 16 == CWAL_SIZE_T_BITS - conf.maxTotalSize = 1024 * 63; -#else - conf.maxTotalSize = 1024 * 256; -#endif - conf.useForValues = 0 /* micro-optimization: true tends towards - sub-1% reduction in mallocs but - potentially has aggregate slower value - allocation for most cases */ ; - rc = cwal_engine_memchunk_config(e, &conf); - assert(!rc); - } - -#define REMAX(T,N) cwal_engine_recycle_max( e, CWAL_TYPE_ ## T, (N) ) - REMAX(UNDEF,0) /* disables recycling for all types */; - if(App.enableValueRecycling){ - /* A close guess based on the post-20141129 model... List them in - "priority order," highest priority last. Lower prio ones might - get trumped by a higher prio one: they get grouped based on the - platform's sizeof() of their concrete underlying bytes. - */ - REMAX(UNIQUE,20)/* will end up being trumped by integer (32-bit) - and/or double (64-bit) */; - REMAX(KVP,80) /* guaranteed individual recycler */; - REMAX(WEAK_REF,30) /* guaranteed individual recycler */; - REMAX(STRING,50) /* guaranteed individual recycler */; - REMAX(EXCEPTION,3); - REMAX(HASH,15) /* might also include: function, native, buffer */; - REMAX(BUFFER,20) /* might also include: function, native, buffer, hash */ ; - REMAX(XSTRING,20 /* also Z-strings and tuples, might also include doubles */); - REMAX(NATIVE,20) /* might also include: function, hash */; - REMAX(DOUBLE,50)/* might also include z-/x-strings, - integer (64-bit), unique (64-bit)*/; - REMAX(FUNCTION,50) /* might include: hash, native, buffer */; - REMAX(ARRAY,30); - REMAX(OBJECT,30) /* might include: buffer */; - REMAX(INTEGER,80) /* might include: double, unique */; - REMAX(TUPLE,30) /* also x-/z-strings, might include: double */; - } -#undef REMAX - /* - Reminder to self: we cannot install functions from here because - the s2_engine's Function prototype will not get installed by that - time, so the functions we added would not have an 'undefined' - prototype property. We must wait until the s2_engine is set up. - - s2 will also nuke the top scope so that it can push one of its - own, so don't allocate any values here. - */ - return rc; -} - -/** String-is-internable predicate for cwal_engine. */ -static bool cstr_is_internable( void * state, - char const * str_, - cwal_size_t len ){ - enum { MaxLen = 32U }; - unsigned char const * str = (unsigned char const *)str_; - assert(str); - assert(len>0); - if(len>MaxLen){ - if(state){/*avoid unused param warning*/} - return 0; - }else if(1==len){ - /*if(*str<32) return 0; - else - */ - return (*str<=127) ? 1 : 0; - } - /* else if(len<4) return 1; */ - else{ - /* Read a UTF8 identifier... */ - char const * zEnd = str_+len; - char const * tail = str_; - s2_read_identifier(str_, zEnd, &tail); - if((tail>str_) - && (len==(cwal_size_t)(tail-str_)) - ){ - /* MARKER(("internable: %.*s\n", (int)len, str_)); */ - return 1; - } - /* MARKER(("Not internable: %.*s\n", (int)len, str_)); */ - return 0; - } -} - -static int s2_cb_s2sh_line_read(cwal_callback_args const * args, cwal_value ** rv){ - char const * prompt = 0; - char * line = 0; - if(args->argc){ - if(!(prompt = cwal_value_get_cstr(args->argv[0], 0))){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a string or no arguments."); - } - }else{ - prompt = "prompt >"; - } - if(!(line = cliapp_lineedit_read(prompt))){ - *rv = cwal_value_undefined(); - return 0; - }else{ - *rv = cwal_new_string_value(args->engine, line, cwal_strlen(line)); - cliapp_lineedit_free(line) - /* if we knew with absolute certainty that args->engine is - configured for the same allocator as the readline bits, we - could use a Z-string and save a copy. But we don't, generically - speaking, even though it's likely to be the same in all but the - weirdest configurations. Even so, cwal may "massage" memory a - bit to add some metadata, so we cannot pass it memory from - another allocator, even if we know they both use the same - underlying allocator. - */; - return *rv ? 0 : CWAL_RC_OOM; - } -} - -/** - Callback implementation for s2_cb_s2sh_history_(load|add|save)(). -*/ -static int s2_cb_s2sh_history_las(cwal_callback_args const * args, - cwal_value ** rv, - char const * descr, - int (*func)(char const * str) ){ - char const * str = args->argc - ? cwal_value_get_cstr(args->argv[0], 0) - : 0; - if(!str){ - return s2_cb_throw(args, CWAL_RC_MISUSE, - "Expecting a string argument."); - } - else{ - int rc = (*func)(str); - if(rc){ - rc = s2_cb_throw(args, rc, - "Native %s op failed with code %d/%s.", - descr, rc, cwal_rc_cstr(rc)); - }else{ - *rv = cwal_value_undefined(); - } - return rc; - } -} - -static int s2_cb_s2sh_history_save(cwal_callback_args const * args, cwal_value ** rv){ - return s2_cb_s2sh_history_las( args, rv, "history-save", cliapp_lineedit_save ); -} - -static int s2_cb_s2sh_history_load(cwal_callback_args const * args, cwal_value ** rv){ - return s2_cb_s2sh_history_las( args, rv, "history-load", cliapp_lineedit_load); -} - -static int s2_cb_s2sh_history_add(cwal_callback_args const * args, cwal_value ** rv){ - return s2_cb_s2sh_history_las( args, rv, "history-add",cliapp_lineedit_add); -} - -/** - cwal_value_visitor_f() impl. Internal helper for s2_dump_metrics(). -*/ -static int s2sh_metrics_dump_funcStash_keys( cwal_value * v, void * state ){ - cwal_size_t sz = 0; - char const * n = cwal_value_get_cstr(v, &sz); - if(state){/*unused*/} - s2sh_print("\t%.*s\n", (int)sz, n); - return 0; -} - -static void s2_dump_metrics(s2_engine * se){ - printfEngine = se->e; -#define OUT(pfexp) s2sh_print pfexp - if(App.verbosity){ - OUT(("\n")); - OUT(("cwal-level allocation metrics:\n")); - cwal_dump_allocation_metrics(se->e); - OUT(("\n")); - if(App.verbosity>2 && App.enableStringInterning){ - cwal_dump_interned_strings_table(se->e, 1, 32); - } - } - if(se->metrics.tokenRequests){ - OUT(("s2-side metrics:\n")); - OUT(("Peak cwal_scope level: %d with %d s2_scopes pushed/popped\n", - se->metrics.maxScopeDepth, - se->metrics.totalScopesPushed)); - OUT(("%d s2_scopes (sizeof %u) were alloc'd across " - "%d (re)alloc(s) with a peak of %d bytes " - "(counted among cwal_engine::memcap::currentMem).\n", - (int)se->scopes.alloced, - (unsigned int)sizeof(s2_scope), - se->metrics.totalScopeAllocs, - (int)(se->scopes.alloced * sizeof(s2_scope)))); - OUT(("Peak eval depth: %d\n", - se->metrics.peakSubexpDepth)); - OUT(("Total s2_stokens (sizeof=%u) requested=%u, " - "allocated=%u (=%u bytes), alive=%u, peakAlive=%u, " - "currently in recycle bin=%d\n", - (unsigned)sizeof(s2_stoken), se->metrics.tokenRequests, - se->metrics.tokenAllocs, (unsigned)(se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken)), - se->metrics.liveTokenCount, se->metrics.peakLiveTokenCount, - se->recycler.stok.size)); - OUT(("Total calls to s2_next_token(): %u\n", - se->metrics.nextTokenCalls)); - if(se->metrics.tokenAllocs < se->metrics.tokenRequests){ - OUT(("Saved %u bytes and %u allocs via stack token recycling :D.\n", - (unsigned)((se->metrics.tokenRequests * (unsigned)sizeof(s2_stoken)) - - (se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken))), - se->metrics.tokenRequests - se->metrics.tokenAllocs - )); - } - } - OUT(("Scratch buffer (used by many String APIs): %u byte(s)\n", - (unsigned)se->buffer.capacity)); - - if(se->metrics.ukwdLookups){ - OUT(("se->metrics.ukwdLookups=%u, " - "se->metrics.ukwdHits=%u\n", - se->metrics.ukwdLookups, - se->metrics.ukwdHits)); - } - if(se->metrics.funcStateAllocs){ - OUT(("Allocated state for %u of %u script-side function(s) " - "(sizeof %u), using %u bytes.\n", - se->metrics.funcStateAllocs, - se->metrics.funcStateRequests, - s2_sizeof_script_func_state(), - se->metrics.funcStateMemory)); - } - if(se->funcStash){ - OUT(("Function script name hash: hash size=%d, entry count=%d. " - "Re-used name count: %d\n", - (int)cwal_hash_size(se->funcStash), - (int)cwal_hash_entry_count(se->funcStash), - (int)se->metrics.totalReusedFuncStash - )); - if(App.verbosity>2){ - OUT(("Function script name hash entries:\n")); - cwal_hash_visit_keys(se->funcStash, - s2sh_metrics_dump_funcStash_keys, 0); - } - } - if(se->stash){ - cwal_hash const * const h = cwal_value_get_hash(se->stash); - OUT(("s2_engine::stash hash size=%d, entry count=%d\n", - (int)cwal_hash_size(h), (int)cwal_hash_entry_count(h))); - } - if(se->metrics.assertionCount - && (App.showMetrics || App.traceAssertions) - ){ - OUT(("script-side 'assert' checks: %u\n", - se->metrics.assertionCount)); - } - OUT(("(end of s2 metrics)\n")); - printfEngine = 0; -#undef OUT -} - -static int s2sh_cb_dump_metrics(cwal_callback_args const * args, cwal_value ** rv){ - s2_engine * se = s2_engine_from_args(args); - int const oldVerbosity = App.verbosity; - int newVerbose = args->argc - ? (int)cwal_value_get_integer(args->argv[0]) - : 0; - App.verbosity = newVerbose; - s2_dump_metrics(se); - App.verbosity = oldVerbosity; - *rv = cwal_value_undefined(); - return 0; -} - -static int s2sh_report_engine_error( s2_engine * se ){ - int rc = 0; - cwal_error * const err = &se->e->err; - if(err->code || se->flags.interrupted){ - char const * msg = 0; - rc = s2_engine_err_get(se, &msg, 0); - if(err->line>0){ - fprintf(stderr, - "s2_engine says error #%d (%s)" - " @ script [%s], line %d, column %d: %s\n", - rc, cwal_rc_cstr(rc), - (char const *)err->script.mem, - err->line, err->col, msg); - }else{ - fprintf(stderr, - "s2_engine says error #%d (%s)%s%s\n", - rc, cwal_rc_cstr(rc), - (msg && *msg) ? ": " : "", - (msg && *msg) ? msg : ""); - } - } - return rc; -} - -/** - General-purpose result reporting function. rc should be the rc from - the function we got *rv from. This function takes over - responsibility of *rv from the caller (who should not take a - reference to *rv). rv may be 0, as may *rv. - - After reporting the error, s2_engine_sweep() is run, which _might_ - clean up *rv (along with any other temporary values in the current - scope). -*/ -static int s2sh_report_result(s2_engine * se, int rc, cwal_value **rv ){ - char const * label = "result"; - cwal_value * rvTmp = 0; - char showedMessage = 0; - if(!rv) rv = &rvTmp; - switch(rc){ - case 0: break; - case CWAL_RC_RETURN: - *rv = cwal_propagating_take(se->e); - assert(*rv); - break; - case CWAL_RC_EXCEPTION: - label = "EXCEPTION"; - *rv = cwal_exception_get(se->e); - assert(*rv); - cwal_value_ref(*rv); - cwal_exception_set(se->e, 0); - cwal_value_unhand(*rv); - break; - case CWAL_RC_EXIT: - rc = 0; - *rv = s2_propagating_take(se); - break; - case CWAL_RC_BREAK: - label = "UNHANDLED BREAK"; - *rv = s2_propagating_take(se); - break; - case CWAL_RC_CONTINUE: - label = "UNHANDLED CONTINUE"; - *rv = 0; - break; - case CWAL_RC_FATAL: - label = "FATAL"; - *rv = s2_propagating_take(se); - assert(*rv); - assert(se->e->err.code); - CWAL_SWITCH_FALL_THROUGH; - default: - ; - if(s2sh_report_engine_error(se)){ - showedMessage = 1; - }else{ - WARN(("Unusual result code (no error details): %d/%s\n", - rc,cwal_rc_cstr2(rc))); - } - break; - } - if(*rv){ - assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv)) - && "Seems like we've cleaned up too early."); - if(rc || App.verbosity){ - if(rc && !showedMessage){ - fprintf(stderr,"rc=%d (%s)\n", rc, cwal_rc_cstr(rc)); - } - s2_dump_value(*rv, label, 0, 0, 0); - } - /* We are uncertain of v's origin but want to clean it up if - possible, so... */ -#if 1 - /* - Disable this and use -W to see more cleanup in action. This - ref/unref combo ensures that if it's a temporary, it gets - cleaned up by this unref, but if it's not, then we effectively - do nothing to it (because someone else is holding/containing - it). - */ - cwal_refunref(*rv); -#endif - *rv = 0; - } - s2_engine_err_reset2(se); - s2_engine_sweep(se); - return rc; -} - -static int s2_cb_s2sh_exit(cwal_callback_args const * args, cwal_value ** rv){ - if(args){/*avoid unused param warning*/} - App.shellExitFlag = 1; - *rv = cwal_value_undefined(); - return 0; -} - -static int s2_install_shell_api( s2_engine * se, cwal_value * tgt, - char const * name ){ - cwal_value * sub = 0; - int rc = 0; - if(!se || !tgt) return CWAL_RC_MISUSE; - else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; - - if(name && *name){ - sub = cwal_new_object_value(se->e); - if(!sub) return CWAL_RC_OOM; - if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){ - cwal_value_unref(sub); - return rc; - } - }else{ - sub = tgt; - } - - { - s2_func_def const funcs[] = { - /* S2_FUNC2("experiment", s2_cb_container_experiment), */ - S2_FUNC2("tokenizeLine", s2_cb_tokenize_line), - S2_FUNC2("readLine", s2_cb_s2sh_line_read), - S2_FUNC2("historySave", s2_cb_s2sh_history_save), - S2_FUNC2("historyLoad", s2_cb_s2sh_history_load), - S2_FUNC2("historyAdd", s2_cb_s2sh_history_add), - S2_FUNC2("exit", s2_cb_s2sh_exit), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, sub, funcs, 0); - } -#undef FUNC2 -#undef SET - return 0; -} - -static int s2sh_run_file(s2_engine * se, char const * filename){ - cwal_value * xrv = 0; - int rc = s2_eval_filename(se, 1, filename, -1, &xrv); - return s2sh_report_result(se, rc, &xrv); -} - -#if 0 -cwal_value * s2_shell_global(){ - return App.s2Global; -} -#endif - -/** - Given a module name (via *name) and target object, this function - creates any parent objects of that name, if needed. Parents are - specified by using a underscore-delimited name. If the name - contains a delimiter, each entry except that last one is treated as - a parent, and they are created (if needed) recursively. - - On success: - - - *name is set to point to the tail part of the module name. - e.g. the "foo" in "foo" and "bar" in "foo_bar". - - - *newTgt is set to new target container for the tail part of the - module name (which this routine does not install - that's up to the - caller). - - - 0 is returned. - - The only errors are allocation-related, so a non-0 return should be - considered fatal. -*/ -static int s2_shell_install_mod_parents( s2_engine * se, - char const * separators, - char const ** name, - cwal_value * tgt, - cwal_value **newTgt){ - int rc = 0; - s2_path_toker pt = s2_path_toker_empty; - char const * token = 0; - int count = 0, i; - cwal_size_t nT = 0, nName = cwal_strlen(*name); - s2_path_toker_init(&pt, *name, (cwal_int_t)nName); - pt.separators = separators; - while(0==s2_path_toker_next(&pt, &token, &nT)){ - ++count; - } - assert(count>0); - if(count>1){ - token = 0; - nT = 0; - s2_path_toker_init(&pt, *name, (cwal_int_t)nName); - pt.separators = separators; - } - for(i = 0; i < count-1; ++i ){ - cwal_value * sub = 0; - s2_path_toker_next(&pt, &token, &nT); - sub = cwal_prop_get(tgt, token, nT); - if(!sub){ - sub = cwal_new_object_value(se->e); - if(!sub) return CWAL_RC_OOM; - cwal_value_ref(sub); - rc = cwal_prop_set(tgt, token, nT, sub); - cwal_value_unref(sub); - if(rc) return rc; - } - tgt = sub; - } - if(i==count-1){ - s2_path_toker_next(&pt, &token, &nT); - } - *name = token; - *newTgt = tgt; - return 0; -} - -/** - Internal helper for s2_setup_static_modules(). s2_module_init()'s - the given module then sets its result (or wrapper object, for v1 - modules) to tgt[name]. Returns the result of the init call. - - If installName is not NULL and not the same as the given module - name then it is assumed to be a dot-delimited list of properties - (with 0 or more dots) as which the module should be installed - in/under the given target. e.g. a name of "fooBarBaz" and - installName of "foo.bar.baz" would install module fooBarBaz as - tgt.foo.bar.baz. If the names are the same, or installName is NULL, - the module is installed as tgt[name]. - - As a compatibility crutch, if the installName and name differ, and - installName installs sub-properties, this routine *also* installs - the module as s2[name] for compatibilty with the numerous scripts - which assuming a conventional module mapping of s2.moduleName. -*/ -static int s2_shell_mod_init( s2_engine * se, - s2_loadable_module const * const mod, - char const * name, - char const * installName, - cwal_value * tgt){ - cwal_value * mrv = 0; - int rc = 0; - char const * originalName; - char const * propName; - char const * initLabel = "initialization"; -#if 1 == S2SH_VERSION - /* See comments below */ - cwal_value * const originalTgt = tgt; -#endif - if(!name) name = mod->name; - if(!installName) installName=name; - originalName = name; - propName = installName; - if(strcmp(propName,name)){ - rc = s2_shell_install_mod_parents(se, ".", &propName, tgt, &tgt); - } - if(!rc) rc = s2_module_init(se, mod, &mrv); - cwal_value_ref(mrv); -#if 1 == S2SH_VERSION - top: -#endif - if(!rc){ - rc = s2_set( se, tgt, propName, cwal_strlen(propName), - mrv ? mrv : cwal_value_undefined() ); - }else{ - assert(!mrv && "Module init must not return non-NULL on error."); - } - VERBOSE(2,("static module [%s%s%s] %s: %s\n", - originalName, - propName==installName ? "" : " ==> ", - propName==installName ? "" : installName, - initLabel, - rc ? "FAILED" : "OK")); -#if 1 == S2SH_VERSION - /** - 2020-01-31: kludge/crutch: we have many scripts which expect - modules to be installed as s2[moduleName], but this code now - supports installing modules as sub-properties, e.g. s2.regex.js - instead of s2.regex_js. To keep the various scripts working, this - check will install the module to its historical s2[moduleName] - location if that was not where the first run-through of this - function installed it. - */ - if(!rc && originalTgt!=tgt){ - initLabel = "alias"; - installName = propName = name; - tgt = originalTgt; - goto top; - } -#endif - cwal_value_unref(mrv); - return rc; -} - -/** - This holds a list of all module names which are being initialized - via the "static module" process. We use this to give us a way to - disable activation of built-in APIs which are being installed via - their static module form. - - The names used here must be the canonical name of the module, not - the "installation name". e.g. "regex_js" instead of "regex.js". -*/ -static char const * const AppStaticModNames[] = { -#if defined(S2SH_MODULE_INITS) -# define M(X) # X, -# define M2(X,Y) " " # X, -S2SH_MODULE_INITS -# undef M -# undef M2 -#endif -0 /* list sentinel */ -}; - -/** - A single-string form of AppModNames. It's exists only for - showing the user in verbose mode. -*/ -static char const * const AppStaticModNamesString = "" -#if defined(S2SH_MODULE_INITS) -#define M(X) " " # X -#define M2(X,Y) " " # Y - S2SH_MODULE_INITS -#undef M -#undef M2 -#endif - ; - -/** - Returns true (non-0) if a static module with the name n was - compiled in, else false (0). -*/ -static int s2sh_has_static_mod( char const * n ){ - char const * const * p = AppStaticModNames; - for( ; *p; ++p ){ - if(!strcmp(n,*p)) return 1; - } - return 0; -} - -/** - Initializes any static modules which this build knows about. Each - one gets installed as tgt[X], where X is the module's name. - Returns 0 on success. If App.initStaticModules is 0 then this - is a no-op. -*/ -static int s2_setup_static_modules( s2_engine * se, cwal_value * tgt ){ - int rc = 0; - if(!App.initStaticModules) return 0; - if(0){ - s2_shell_mod_init(0,0,0,0,tgt) - /* We need a ref to this func in order for the func - to be static, and there is otherwise no ref when building - without statically-imported modules. */; - } - /** - Initializes the statically-linked module assocatiated with - S2SH_MODULE_INIT_##MODNAME, installing it under the property - named #INSTNAME. - - INSTNAME should either be the same as MODNAME or a - "reformulation" which tells s2sh to install it under a different - name, possibly somewhere other than the root of s2. e.g. passing - (foo,bar.foo) would set up the installation of module foo as - s2.bar.foo. INSTNAME must be a non-string token (this macro - stringifies it). - */ -#define S2SH_MODULE_INIT2(MODNAME,INSTNAME) \ - { \ - extern s2_loadable_module const * s2_module_ ## MODNAME; \ - rc = s2_shell_mod_init(se, s2_module_ ## MODNAME, #MODNAME, #INSTNAME, tgt); \ - if(rc) goto end; \ - } -#define S2SH_MODULE_INIT(MODNAME) \ - S2SH_MODULE_INIT2(MODNAME,MODNAME) - -#if defined(S2SH_MODULE_INITS) - /** - Yet another approach to registering modules... - - Define S2SH_MODULE_INITS (note the trailing 'S') to a list - in this form: - - M(module1) M(module2) ... M(moduleN) - - e.g.: - - cc -c shell.c ... '-DS2SH_MODULE_INITS=M(cgi) M(sqlite3)' - - Each M(X) will get translated to S2SH_MODULE_INIT(X), setting - up registration of that module. - - Likewise, M2(X,Y) will be transformed the same way except that Y - (which must not a quoted string (because we apparently can't(?) - squeeze the required quotes through Make)) will be used for the - property name, and a dot-delimited Y will cause the module to be - installed in a sub-property of s2. e.g. M2(X,a.b.c) will install - module X as s2.a.b.c, rather than s2.X. - - It is up to the user to link in the appropriate objects/libs for - each module, as well as provide any linker flags or 3rd-party - libraries those require. - */ -# define M S2SH_MODULE_INIT -# define M2 S2SH_MODULE_INIT2 - S2SH_MODULE_INITS -# undef M -# undef M2 -#endif - -#undef S2SH_MODULE_INIT -#undef S2SH_MODULE_INIT2 - - if(rc) goto end /* avoid unused goto when no modules are built in */; - end: - if(rc){ - cwal_value * dummy = 0; - s2sh_report_result(se, rc, &dummy) - /* We do this to report any pending exception - or se->err state. */; - } - return rc; -} - -#if 0 -static int s2_cb_dump_val(cwal_callback_args const * args, cwal_value ** rv){ - int i = 0; - for( ; i < args->argc; ++i ){ - s2_dump_val(args->argv[i], "dump_val"); - } - *rv = cwal_value_undefined(); - return 0; -} -#endif - -static int s2sh_setup_s2_global( s2_engine * se ){ - int rc = 0; - cwal_value * v = 0; - cwal_engine * const e = se->e; - cwal_value * s2 = 0; -#define VCHECK if(!v){rc = CWAL_RC_OOM; goto end;} (void)0 -#define RC if(rc) goto end - - /* - Reminder to self: we're not "leaking" anything on error here - it - will be cleaned up by the scope when it pops during cleanup right - after the error code is returned. - */ - - v = cwal_new_function_value( e, s2_cb_print, 0, 0, 0 ); - VCHECK; - /*s2_dump_val(App.s2Global, "App.s2Global");*/ - cwal_value_ref(v); -#if 0 - if(2==S2SH_VERSION){ - rc = s2_define_ukwd(se, "if", 2, v); - assert(CWAL_RC_ACCESS==rc); - rc = s2_define_ukwd(se, "print", 5, v); - RC; - rc = s2_define_ukwd(se, "print", 5, v); - assert(CWAL_RC_ALREADY_EXISTS==rc); - rc = 0; - }else -#endif - { - rc = cwal_var_decl(e, 0, "print", 5, v, 0); - RC; - } - cwal_value_unref(v); - - /* Global s2 object... */ - s2 = v = cwal_new_object_value(e); - VCHECK; - cwal_value_ref(v); - rc = cwal_var_decl(e, 0, "s2", 2, v, CWAL_VAR_F_CONST); - if(!rc && 2==S2SH_VERSION){ - rc = s2_define_ukwd(se, "S2", 2, v); - } - cwal_value_unref(v); - RC; - App.s2Global = v; - -#define SETV(K) \ - VCHECK; cwal_value_ref(v); \ - rc = cwal_prop_set( s2, K, cwal_strlen(K), v ); \ - cwal_value_unref(v); \ - RC; - - v = s2_prototype_buffer(se); - SETV("Buffer"); - v = s2_prototype_hash(se); - SETV("Hash"); - - v = s2_prototype_tuple(se); - SETV("Tuple"); -#undef SETV - - rc = s2_install_pf(se, s2); - RC; - - if(AppStaticModNames[0]){ - VERBOSE(2,("Built-in static module list:%s\n", - AppStaticModNamesString)); - } - - { - /* Global functions must come before static module init because - script-implemented modules often affirm/assert that a certain - API is in place. */ - s2_func_def const funcs[] = { - /*S2_FUNC2("newUnique", s2_cb_new_unique),*/ - /*S2_FUNC2("getResultCodeHash", s2_cb_rc_hash),*/ - /*S2_FUNC2("isCallable", s2_cb_is_callable),*/ - /*S2_FUNC2("isDerefable", s2_cb_is_derefable),*/ - S2_FUNC2("sealObject", s2_cb_seal_object), - S2_FUNC2("loadModule", s2_cb_module_load), - S2_FUNC2("minifyScript", s2_cb_minify_script), -#if 1==S2SH_VERSION - /* s2.import is no longer needed but we very likely have scripts - which still expect it. */ - S2_FUNC2("import", s2_cb_import_script), -#endif - S2_FUNC2("glob", s2_cb_glob_matches_str), - S2_FUNC2("getenv", s2_cb_getenv), - /*S2_FUNC2("fork", s2_cb_fork),*/ - /*S2_FUNC2("dumpVal", s2sh_cb_dump_val),*/ - S2_FUNC2("dumpMetrics", s2sh_cb_dump_metrics), - /*S2_FUNC2("compare", s2_cb_value_compare),*/ - S2_FUNC2("cwalBuildInfo", cwal_callback_f_build_info), - s2_func_def_empty_m /* end-of-list sentinel */ - }; - if( (rc = s2_install_functions(se, s2, - funcs, CWAL_VAR_F_CONST)) ) { - goto end; - } - } - - /* - Jump through some hoops to ensure that we don't install a built-in - API which is getting statically linked in as a loadable module... - - For a module/API named N, if AppModNames contains N, we skip - installation of that API because its module registration (which - installs the same API) is pending. If AppModNames has no matching - entry, we install the API with F(se, s2, N). - */ -#define MCHECK(N,F) \ - if(!s2sh_has_static_mod(N)){ \ - if((rc = F(se, s2, N))) goto end; \ - }else{ \ - VERBOSE(2,("Skipping install of [%s] API b/c " \ - "there's a module with the same name.\n", N)); \ - } - MCHECK("fs", s2_install_fs); - MCHECK("io", s2_install_io); - MCHECK("json",s2_install_json); - MCHECK("ob", s2_install_ob_2); - MCHECK("time",s2_install_time); -#undef MCHECK -#define MCHECK(N,F) \ - if(!s2sh_has_static_mod(N)){ \ - if((rc = s2_install_callback(se, s2, F, N, -1, \ - CWAL_VAR_F_CONST, 0, 0, 0))) goto end; \ - }else{ \ - VERBOSE(2,("Skipping install of [%s] API b/c " \ - "there's a module with the same name.\n", N)); \ - } - MCHECK("tmpl", s2_cb_tmpl_to_code); -#undef MCHECK - - if(App.enableInternalFu){ - rc = s2_install_callback(se, s2, s2_cb_internal_experiment, - "s2InternalExperiment", -1, - CWAL_VAR_F_CONST, 0, 0, 0); - if(rc) goto end; - } - - /* Some script-implemented modules rely on s2.ARGV for configuration, - so it must be set up before modules are initialized. */ - v = cwal_new_array_value(e); - cwal_value_ref(v); - rc = cwal_parse_argv_flags(e, App.scriptArgc, App.scriptArgv, &v); - if(rc){ - cwal_value_unref(v); - goto end; - }else{ - assert(v); - rc = cwal_prop_set(s2, "ARGV", 4, v); - } - cwal_value_unref(v); - v = 0; - if(rc){ - goto end; - } - assert(!rc); - - if(1){ - rc = s2_setup_static_modules(se, s2); - if(rc) goto end; - }else{ - cwal_value * mod = cwal_new_object_value(se->e); - if(!mod){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_ref(mod); - rc = s2_set(se, s2, "mod", 3, mod); - cwal_value_unref(mod); - if(!rc) rc = s2_setup_static_modules(se, mod); - if(rc) goto end; - } - -#if 0 - /* - EXPERIMENTAL: i've always avoided making scope-owned variable - storage directly visible to script code, but we're going to - try this... - - Add global const __GLOBAL which refers directly to the variable - storage of the top-most scope. - - If we like this, we'll change it to a keyword, which will make it - many times faster to process. - */ - v = cwal_scope_properties(cwal_scope_top(cwal_scope_current_get(e))) - /* Cannot fail: we've already declared vars in this scope, so it's - already allocated. */; - assert(cwal_value_get_object(v)); - assert(cwal_value_refcount(v)>0 && "Because its owning scope holds a ref."); - assert(0==cwal_value_prototype_get(e, v) && "Because cwal does not install prototypes for these!"); - cwal_value_prototype_set( v, s2_prototype_object(se) ); - if(cwal_value_is_hash(v)){ - s2_hash_dot_like_object(v, 1); - } - rc = cwal_var_decl(e, 0, "__GLOBAL", 8, v, CWAL_VAR_F_CONST) - /* that effectively does: global.__GLOBAL = global */; - RC; -#endif - -#if 0 - { /* testing s2_enum_builder... */ - s2_enum_builder eb = s2_enum_builder_empty; - cwal_value * v; - char nameBuf[20] = {'e','n','t','r','y',0,0}; - cwal_int_t i; - rc = s2_enum_builder_init(se, &eb, 17, "TestEnum"); - assert(!rc); - RC; - assert(cwal_value_is_vacuum_proof(eb.entries)); - for(i = 0; i < 3; ++i ){ - nameBuf[5] = '1' + i; - v = cwal_new_integer(se->e, (i+1)); - VCHECK; - cwal_value_ref(v); - rc = s2_enum_builder_append(&eb, nameBuf, v); - cwal_value_unref(v); - v = 0; - } - rc = s2_enum_builder_seal(&eb, &v); - assert(!rc); - RC; - assert(!eb.se); - assert(!eb.entries); - assert(v); - assert(!cwal_value_is_vacuum_proof(v)); - assert(!cwal_value_refcount(v)); - cwal_value_ref(v); - rc = cwal_var_decl(e, 0, "testEnum", 8, v, 0); - cwal_value_unref(v); - RC; - MARKER(("testEnum (s2_enum_builder) installed! Try:\n" - "foreach(testEnum=>k,v) print(k,v,v.value,testEnum[v])\n")); - } -#endif - -#undef FUNC2 -#undef VCHECK -#undef RC - end: - return rc; -} - -/** - Callback for cliapp_repl(). -*/ -static int CliApp_repl_f_s2sh(char const * line, void * state){ - static cwal_hash_t prevHash = 0; - cwal_size_t const nLine = cwal_strlen(line); - if(nLine){ - s2_engine * const se = (s2_engine*)(state); - cwal_value * v = 0; - cwal_hash_t const lineHash = cwal_hash_bytes(line, nLine); - int rc = 0; - if(prevHash!=lineHash){ - /* Only add the line to the history if it seems to be different - from the previous line. */ - cliapp_lineedit_add(line); - prevHash = lineHash; - } - s2_set_interrupt_handlable( se ); - rc = s2_eval_cstr( se, 0, "shell input", line, (int)nLine, &v ); - s2_set_interrupt_handlable( 0 ) - /* keeps our ctrl-c handler from interrupting line reading or - accidentially injecting an error state into se. */; - s2sh_report_result(se, rc, &v); - s2_engine_err_reset(se); - if(App.shellExitFlag) return S2_RC_CLIENT_BEGIN; - } - return 0; -} - -/** - Enters an interactive-mode REPL loop if it can, otherwise it - complains loudly about the lack of interactive editing and returns - CWAL_RC_UNSUPPORTED. -*/ -static int s2sh_interactive(s2_engine * se){ - int rc = 0; - if(!cliApp.lineread.enabled){ - rc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED, - "Interactive mode not supported: " - "this shell was built without " - "line editing support."); - s2sh_report_result(se, rc, 0); - return rc; - } - - cliapp_lineedit_load(NULL) /* ignore errors */; - cwal_outputf(se->e, "s2 interactive shell. All commands " - "are run in the current scope. Use your platform's EOF " - "sequence on an empty line to exit. (Ctrl-D on Unix, " - "Ctrl-Z(?) on Windows.) Ctrl-C might work, too.\n\n"); - rc = cliapp_repl( CliApp_repl_f_s2sh, &App.lnPrompt, 0, se ); - if(S2_RC_CLIENT_BEGIN==rc){ - /* Exit signal from the repl callback. */ - rc = 0; - } - cliapp_lineedit_save(NULL); - s2_engine_sweep(se); - cwal_output(se->e, "\n", 1); - cwal_output_flush(se->e); - return rc; -} - - -static int s2sh_main2( s2_engine * se ){ - int rc = 0; - int i; -#if 0 - /* just testing post-init reconfig */ - { - /* Configure the memory chunk recycler... */ - cwal_memchunk_config conf = cwal_memchunk_config_empty; - conf.maxChunkCount = 0; - conf.maxChunkSize = 1024 * 32; - conf.maxTotalSize = 1024 * 256; - conf.useForValues = 0; - rc = cwal_engine_memchunk_config(se->e, &conf); - assert(!rc); - } -#endif - - if(!App.cleanroom){ - if(App.installShellApi>0 - || (App.interactive>0 && App.installShellApi<0)){ - assert(App.s2Global); - if((rc = s2_install_shell_api(se, App.s2Global, "shell"))){ - return rc; - } - } - } - - assert(App.scripts.nScripts <= S2SH_MAX_SCRIPTS); - for( i = 0; i < App.scripts.nScripts; ++i ){ - char const * script = App.scripts.e[i].src; - /*VERBOSE(2,("Running script #%d: %s\n", i+1, script));*/ - if(!*script) continue; - if(App.scripts.e[i].isFile){ - rc = s2sh_run_file(se, script); - cwal_output_flush(se->e); - /* s2_engine_sweep(se); */ - if(rc) break; - }else{ - cwal_value * rv = 0; - rc = s2_eval_cstr( se, 0, "-e script", - script, cwal_strlen(script), - App.verbosity ? &rv : NULL ); - s2sh_report_result(se, rc, &rv); - if(rc) break; - } - } - /*MARKER(("Ran %d script(s)\n", i));*/ - if(!rc && App.interactive>0){ - rc = s2sh_interactive(se); - } - return rc; -} - -/** - If App.enableArg0Autoload and a file named $S2SH_INIT_SCRIPT or - (failing that) the same as App.appName, minus any ".exe" or ".EXE" - extension, but with an ".s2" extension, that script is loaded. If - not found, 0 is returned. If loading or evaling the (existing) file - fails, non-0 is returned and the error is reported. -*/ -static int s2sh_autoload(s2_engine * se){ - int rc = 0; - char const * scriptName = 0; - if(!App.enableArg0Autoload || !App.appName || !*App.appName) return 0; - else if(S2_DISABLE_FS_READ & s2_disable_get(se)){ - VERBOSE(1,("NOT loading init script because the --s2-disable=fs-read " - "flag is active.\n")); - return 0; - } - else if( !(scriptName = getenv("S2SH_INIT_SCRIPT")) ){ - /* - See if $0.s2 exists... - - FIXME: this doesn't work reliably if $0 is relative, e.g. - found via $PATH. We need to resolve its path to do this - properly, but realpath(3) is platform-specific. - */ - enum { BufSize = 1024 * 3 }; - static char fn[BufSize] = {0}; - cwal_size_t slen = 0; - char const * exe = 0; - char const * arg0 = App.appName; - assert(arg0); - exe = strstr(arg0, ".exe"); - if(!exe) exe = strstr(arg0, ".EXE"); - if(!exe) exe = strstr(arg0, ".Exe"); - slen = cwal_strlen(arg0) - (exe ? 4 : 0); - rc = sprintf(fn, "%.*s.s2", (int)slen, arg0); - assert(rcallocStamp : (se->allocStamp == e)); - - if((rc = s2_engine_init( se, e ))) goto end; - if(App.cleanroom){ - VERBOSE(1,("Clean-room mode: NOT installing globals/prototypes.\n")); - }else{ - if((rc = s2_install_core_prototypes(se))) goto end; - else if( (rc = s2sh_setup_s2_global(se)) ) goto end; - } - if(App.zDisableFlags){ - rc = s2_disable_set_cstr(se, App.zDisableFlags, -1, 0); - if(rc) goto end; - } - se->sweepInterval += App.addSweepInterval; - se->vacuumInterval += App.addVacuumInterval; - se->flags.traceTokenStack = App.traceStacks; - se->flags.traceAssertions = App.traceAssertions; - se->flags.traceSweeps = App.traceSweeps; - s2_set_interrupt_handlable( se ) - /* after infrastructure, before the auto-loaded script, in case it - contains an endless loop or some such and needs to be - interrupted. - */; -#if defined(S2_SHELL_EXTEND) - if(!App.cleanroom){ - if( (rc = S2_SHELL_EXTEND_FUNC_NAME(se, App.s2Global, argc, argv)) ) goto end; - }else{ - VERBOSE(1,("Clean-room mode: NOT running client-side shell extensions.\n")); - } -#endif - - if(App.cleanroom){ - VERBOSE(1,("Clean-room mode: NOT auto-loading init script.\n")); - }else{ - rc=s2sh_autoload(se); - } - s2_set_interrupt_handlable( 0 ); - if(rc) goto end; - - rc = s2sh_main2(se); - App.s2Global = 0; - - end: - s2_set_interrupt_handlable( 0 ); - if(App.showMetrics){ - s2_dump_metrics(se); - } -#if 1 - s2sh_report_engine_error(se); -#endif - assert(!se->st.vals.size); - assert(!se->st.ops.size); - s2_engine_finalize(se); - return rc; -} - - -/** - Pushes scr to App.scripts if there is space for it, else outputs an - error message and returns non-0. isFile must be true if scr refers - to a filename, else it is assumed to hold s2 script code. -*/ -static int s2sh_push_script( char const * scr, int isFile ){ - const int n = App.scripts.nScripts; - if(S2SH_MAX_SCRIPTS==n){ - MARKER(("S2SH_MAX_SCRIPTS (%d) exceeded: give fewer scripts or " - "rebuild with a higher S2SH_MAX_SCRIPTS.\n", - S2SH_MAX_SCRIPTS)); - return CWAL_RC_RANGE; - } - App.scripts.e[n].isFile = isFile; - App.scripts.e[n].src = scr; - VERBOSE(3,("Pushed %s #%d: %s\n", isFile ? "file" : "-e script", - n+1, App.scripts.e[n].src)); - ++App.scripts.nScripts; - return 0; -} - -static void s2sh_help_header_once(){ - static int once = 0; - if(once) return; - once = 1; - cliapp_print("s2 scripting engine shell #%d\n", - S2SH_VERSION); -} - -static void s2sh_show_version(int detailedMode){ - s2sh_help_header_once(); - cliapp_print("cwal/s2 version: %s\n", - cwal_build_info()->versionString); - cliapp_print("Project site: https://fossil.wanderinghorse.net/r/cwal\n"); - if(detailedMode){ - cwal_build_info_t const * const bi = cwal_build_info(); - cliapp_print("\nlibcwal compile-time info:\n\n"); -#define STR(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM) - /*STR(versionString);*/ - STR(cppFlags); - STR(cFlags); - STR(cxxFlags); -#undef STR - -#define SZ(MEM) cliapp_print("\t%s = %"CWAL_SIZE_T_PFMT"\n", #MEM, (cwal_size_t)bi->MEM) - SZ(size_t_bits); - SZ(int_t_bits); - SZ(maxStringLength); -#undef SZ - -#define BUL(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM ? "true" : "false") - BUL(isJsonParserEnabled); - BUL(isDebug); -#undef BUL - - cliapp_print("\n"); - } -} - -/** - Parses and returns the value of an unsigned int CLI switch. Assigns - *rc to 0 on success, non-0 on error. -*/ -static cwal_size_t s2sh_parse_switch_unsigned(CliAppSwitch const * s, int * rc){ - cwal_int_t i = 0; - if(!s2_cstr_parse_int(s->value, -1, &i)){ - WARN(("-cap-%s must have a positive integer value.\n", s->key)); - *rc = CWAL_RC_MISUSE; - return 0; - }else if(i<0){ - WARN(("-cap-%s must have a positive value (got %d).\n", s->key, (int)i)); - *rc = CWAL_RC_RANGE; - return 0; - }else{ - *rc = 0; - return (cwal_size_t)i; - } -} - - -/** - The help handler for a single CliAppSwitch. It requires that all - switches are passed to it in the order they are defined at the app - level, as it applies non-inuitive semantics to certain entries, - e.g. collapsing -short and --long-form switches into a single - entry. -*/ -static int s2sh_help_switch_visitor(CliAppSwitch const *s, - void * /* ==>int* */ verbosity){ - static CliAppSwitch const * skip = 0; - CliAppSwitch const * next = s+1 - /* this is safe b/c the list ends with an empty sentinel entry */; - int group = 0; - static int prevContinue = 0; - if(s->opaque & SW_UNDOCUMENTED){ - return 0; - }else if(skip == s){ - skip = 0; - return 0; - } - switch(s->opaque & SW_GROUPS){ - case SW_GROUP_0: break; - case SW_GROUP_1: group = 1; break; - case SW_GROUP_2: group = 2; break; - } - assert(s->dash>=-1 && s->dash<=2); - assert(group>=0 && group<3); - /*MARKER(("group %d (%d) %s\n", group, *((int const*)verbosity), s->key));*/ - if(group>*((int const*)verbosity)){ - return CWAL_RC_BREAK; - } - if(s->opaque & SW_GROUPS){ - /* Use s->brief as a label for this section then return. */ - if(s->brief && *s->brief){ - char const * splitter = "==============================="; - cliapp_print("\n%s\n%s\n%s\n", splitter, s->brief, splitter); - } - return 0; - } - if((s->opaque & SW_ID_MASK) && (next->opaque==s->opaque)){ - /* Collapse long/short options into one entry. We expect the long - entry to be the first one. */ - /* FIXME? This only handles collapsing 2 options, but s2sh - help supports -?/-help/--help. */ - skip = next; - cliapp_print("\n %s%s | ", cliapp_flag_prefix(skip->dash), - skip->key); - }else if(!next->key){ - skip = 0; - } - /*MARKER(("switch entry: brief=%s, details=%s\n", s->brief, s->details));*/ - if(s->key && *s->key){ - cliapp_print("%s%s%s%s%s%s%s\n", - skip ? "" : "\n ", - cliapp_flag_prefix(s->dash), s->key, - s->value ? " = " : "", - s->value ? s->value : "", - s->brief ? "\n ": "", - s->brief ? s->brief : ""); - prevContinue = s->opaque; - }else{ - switch(s->opaque){ - case SW_CONTINUE: - assert(s->brief && *s->brief); - if(prevContinue==SW_INFOLINE){ - cliapp_print("%s\n", s->brief); - }else{ - cliapp_print(" %s\n", s->brief); - } - break; - case SW_INFOLINE: - assert(s->brief && *s->brief); - cliapp_print("\n%s\n", s->brief); - prevContinue = s->opaque; - break; - default: - prevContinue = s->opaque; - break; - } - } - return 0; -} - -static void s2sh_show_help(){ - int maxGroup = App.verbosity; - char const * splitter = 1 ? "" : - "------------------------------------------------------------\n"; - int n = 0; - s2sh_show_version(0); - cliapp_print("\nUsage: %s [options] [file | -f file]\n", - App.appName); - cliapp_print("%s",splitter); - cliapp_switches_visit( s2sh_help_switch_visitor, &maxGroup ); - cliapp_print("\n%sNotes:\n",splitter); - cliapp_print("\n%d) Flags which require a value may be provided as (--flag=value) or " - "(--flag value).\n", ++n); - cliapp_print("\n%d) All flags after -- are ignored by the shell but " - "made available to script code via the s2.ARGV object.\n", ++n); - cliapp_print("Minor achtung: the script-side arguments parser uses --flag=VALUE, " - "whereas this app's flags accept (--flag VALUE)!\n"); - - if(maxGroup<2){ - cliapp_print("\n%d) Some less obscure options were not shown. " - "Use -v once or twice with -? for more options.\n", ++n); - } - puts(""); -} - -static int s2shGotHelpFlag = 0; -static int s2sh_arg_callback_common(int ndx, CliAppSwitch const * s, - CliAppArg * a){ - int const sFlags = s ? s->opaque : 0; - int const sID = sFlags & SW_ID_MASK; - int rc = 0; - if(0 && a){ - MARKER(("switch callback sFlags=%06x, sID=%d, SW_ESCRIPT=%d: #%d %s%s%s%s\n", - sFlags, sID, SW_ESCRIPT, - ndx, cliapp_flag_prefix(a->dash), a->key, - a->value ? "=" : "", - a->value ? a->value : "")); - } - if(!a/* end of args - there's nothing for use to do in this case */){ - return 0; - } - switch(sID){ - case SW_ASSERT_0: App.traceAssertions = 0; break; - case SW_ASSERT_1: ++App.traceAssertions; break; - case SW_AUTOLOAD_0: App.enableArg0Autoload = 0; break; - case SW_AUTOLOAD_1: App.enableArg0Autoload = 1; break; - case SW_CLAMPDOWN: - WARN(("Clampdown mode has been removed.")); - return CWAL_RC_UNSUPPORTED; - case SW_CLEANROOM: App.cleanroom = 1; break; - case SW_DISABLE: App.zDisableFlags = a->value; break; - case SW_ESCRIPT: - assert(a->value && "Should have been captured by cliapp."); - rc = s2sh_push_script( a->value, 0 ); - if(App.interactive<0) App.interactive=0; - break; - case SW_HELP: ++s2shGotHelpFlag; break; - case SW_HIST_FILE_1: cliApp.lineread.historyFile = a->value; break; - case SW_HIST_FILE_0: cliApp.lineread.historyFile = 0; break; - case SW_INFILE: - assert(a->value && "Should have been captured by cliapp."); - rc = s2sh_push_script( a->value, 1 ); - if(!rc) App.inFile = a->value; - break; - case SW_INTERACTIVE: App.interactive = 1; break; - case SW_INTERNAL_FU: ++App.enableInternalFu; break; - case SW_MEM_FAST0: App.memcap.forceAllocSizeTracking = 0; break; - case SW_MEM_FAST1: App.memcap.forceAllocSizeTracking = 1; break; - case SW_MCAP_TOTAL_ALLOCS: - App.memcap.maxTotalAllocCount = - s2sh_parse_switch_unsigned(s, &rc); - break; - case SW_MCAP_TOTAL_BYTES: - App.memcap.maxTotalMem = s2sh_parse_switch_unsigned(s, &rc); - break; - case SW_MCAP_CONC_ALLOCS: - App.memcap.maxConcurrentAllocCount = - s2sh_parse_switch_unsigned(s, &rc); - break; - case SW_MCAP_CONC_BYTES: - App.memcap.maxConcurrentMem = s2sh_parse_switch_unsigned(s, &rc); - break; - case SW_MCAP_SINGLE_BYTES: - App.memcap.maxSingleAllocSize = - s2sh_parse_switch_unsigned(s, &rc); - break; - case SW_METRICS: App.showMetrics = 1; break; - case SW_MOD_INIT_0: App.initStaticModules = 0; break; - case SW_MOD_INIT_1: App.initStaticModules = 1; break; - case SW_OUTFILE: App.outFile = a->value; break; - case SW_RE_C0: App.maxChunkCount = 0; break; - case SW_RE_C1: App.maxChunkCount = 30; break; - case SW_RE_S0: App.enableStringInterning = 0; break; - case SW_RE_S1: App.enableStringInterning = 1; break; - case SW_RE_V0: App.enableValueRecycling = 0; break; - case SW_RE_V1: App.enableValueRecycling = 1; break; - case SW_SCOPE_H0: App.scopesUseHashes = 0; break; - case SW_SCOPE_H1: App.scopesUseHashes = 1; break; - case SW_SHELL_API_0: App.installShellApi = 0; break; - case SW_SHELL_API_1: App.installShellApi = 1; break; - case SW_TRACE_CWAL: - App.cwalTraceFlags = CWAL_TRACE_SCOPE_MASK - | CWAL_TRACE_VALUE_MASK - | CWAL_TRACE_MEM_MASK - | CWAL_TRACE_FYI_MASK - | CWAL_TRACE_ERROR_MASK; - break; - case SW_TRACE_STACKS_0: App.traceStacks = 0; break; - case SW_TRACE_STACKS_1: ++App.traceStacks; break; - case SW_TRACE_SWEEP_0: App.traceSweeps = 0; break; - case SW_TRACE_SWEEP_1: ++App.traceSweeps; break; - case SW_VERBOSE: ++App.verbosity; break; - case SW_SHOW_SIZEOFS: - default: break; - } - return rc; -} - -static int dump_args_visitor(CliAppArg const * p, int i, void * state){ - if(state){/*unused*/} - assert(p->key); - cliapp_print("argv[%d] %s%s%s%s\n",i, - cliapp_flag_prefix(p->dash), p->key, - p->value ? "=" : "", - p->value ? p->value : ""); - return 0; -} - -static void s2sh_dump_args(){ - puts("CLI arguments:"); - cliapp_args_visit(dump_args_visitor, 0, 1) - /* Over-engineering, just to test that cliapp code. */; -} - -int main(int argc, char const * const * argv){ - int rc = 0; - char const * arg = 0; - int gotNoOp = 0 - /* incremented when we get a "no-op" flag. These are - flags which perform some work without invoking the - interpreter, and this flag lets us distinguish between - not getting any options and getting some which did - something useful (--help, --version, etc)*/; - - s2_static_init(); - App.appName = argv[0]; - cliApp.switches = s2sh_cliapp_switches(); - cliApp.argCallack = s2sh_arg_callback_common; - assert(!App.shellExitFlag); - memset(&App.scripts,0,sizeof(App.scripts)); - App.memcap.forceAllocSizeTracking = 0; - - if((arg = getenv("S2SH_HISTORY_FILE"))){ - App.editHistoryFile = arg; - } - cliApp.lineread.historyFile = App.editHistoryFile; - - rc = cliapp_process_argv(argc, argv, 0); - if(rc) rc = CWAL_RC_MISUSE - /* It's *possibly* not a CWAL_RC_ code, to we assume misuse. */; - if(rc) goto end; - App.scriptArgc = cliApp.doubleDash.argc; - App.scriptArgv = cliApp.doubleDash.argv; - - if(0){ - CliAppArg const * p; - if(0){ - if(!rc){ - s2sh_dump_args(); - while((p=cliapp_arg_nonflag())){ - MARKER(("Non-flag arg: %s\n",p->key)); - } - cliapp_arg_nonflag_rewind(); - } - } - } - - if(s2shGotHelpFlag){ - s2sh_show_help(); - gotNoOp = 1; - }else if(cliapp_arg_flag("V","version", 0)){ - s2sh_show_version(App.verbosity); - gotNoOp = 1; - }else if(!App.inFile){ - /* Treat the first non-flag arg as an -f flag if no -f was - explicitely specified. */ - CliAppArg const * ca; - assert(argc>1 ? cliApp.cursorNonflag>0 : 1); - ca = cliapp_arg_nonflag(); - if(ca){ - rc = s2sh_push_script( ca->key, 1 ); - if(rc) goto end; - else App.inFile = ca->key; - } - } -#if 1 == S2SH_VERSION - if(cliapp_arg_flag("z",0,0)){ - gotNoOp = 1; -#define SO(T) MESSAGE(("sizeof("#T")=%d\n", (int)sizeof(T))) - SO(s2_stoken); - SO(s2_ptoken); - SO(s2_ptoker); - /* SO(s2_op); */ - SO(s2_scope); - SO(cwal_scope); - SO(s2_engine); - SO(cwal_engine); - SO(cwal_memchunk_overlay); - SO(cwal_list); -#undef SO - MESSAGE(("sizeof(s2_func_state)=%d\n", - (int)s2_sizeof_script_func_state())); - } -#endif /* -z flag */ - - if(!gotNoOp && App.inFile && cliapp_arg_nonflag()){ - WARN(("Extraneous non-flag arguments provided.\n")); - rc = CWAL_RC_MISUSE; - goto end; - } - - assert(!rc); - if(!gotNoOp){ - if(!App.inFile && App.interactive<0 - && !App.scripts.nScripts){ -#ifdef S2_OS_UNIX - if(isatty(STDIN_FILENO)){ - App.interactive = 1; - }else App.inFile = "-"; -#else - App.inFile = "-"; -#endif - } - rc = s2sh_main(argc, argv); - } - - end: - { - char const * errMsg = cliapp_err_get(); - if(errMsg){ - WARN(("%s\n", errMsg)); - } - } - if(App.verbosity>0 && rc){ - WARN(("rc=%d (%s)\n", rc, cwal_rc_cstr(rc))); - } - return rc ? EXIT_FAILURE : EXIT_SUCCESS; -} DELETED bindings/s2/shell_extend.c Index: bindings/s2/shell_extend.c ================================================================== --- bindings/s2/shell_extend.c +++ bindings/s2/shell_extend.c @@ -1,3716 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/** - This file is meant to act as a client-side way to extend the default - s2 shell app. It demonstrates examples of connecting C code to - the s2 scripting environment. - - If shell.c is built with S2_SHELL_EXTEND defined, then it will assume - this function will be linked in by the client: - - int s2_shell_extend(s2_engine * se, int argc, char const * const * argv); - - When built with the macro S2_SHELL_EXTEND, it calls that function right - after it installs its own core functionality. -*/ -#include -#if defined(S2_AMALGAMATION_BUILD) -# include "s2_amalgamation.h" -#else -# include "s2.h" -#endif -#include "fossil-scm/fossil.h" - -#ifdef S2_OS_UNIX -# include /* F_OK and friends */ -#elif defined(S2_OS_WINDOWS) -# include -#endif - -/* Only for debuggering... */ -#include -#define MARKER(pfexp) \ - do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ - printf pfexp; \ - } while(0) - - -/** - Type IDs for type-safely mapping (void*) to cwal_native - instances. Their types and values are irrelevant - we use only their - addresses. -*/ -static const int cwal_type_id_fsl_cx = 0; -static const int cwal_type_id_fsl_db = 0; -static const int cwal_type_id_fsl_stmt = 0; -#define FSL_TYPEID(X) (&cwal_type_id_##X) - -#define VERBOSE_FINALIZERS 0 - -static void cwal_finalizer_f_fsl_db( cwal_engine * e, void * m ){ - if(m){ -#if VERBOSE_FINALIZERS - MARKER(("Finalizing fsl_db @%p\n", m)); -#endif - fsl_db_close( (fsl_db*)m ); - } -} - -void cwal_finalizer_f_fsl_stmt( cwal_engine * e, void * m ){ - if(m){ -#if VERBOSE_FINALIZERS - MARKER(("Finalizing fsl_stmt @%p\n", m)); -#endif - fsl_stmt_finalize( (fsl_stmt*)m ); - } -} - -static void cwal_finalizer_f_fsl_cx( cwal_engine * e, void * m ){ - if(m){ -#if VERBOSE_FINALIZERS - MARKER(("Finalizing fsl_cx @%p\n", m)); -#endif - fsl_cx_finalize( (fsl_cx*)m ); - } -} - -#undef VERBOSE_FINALIZERS - -static int cb_toss( cwal_callback_args const * args, int code, - char const * fmt, ... ){ - int rc; - va_list vargs; - s2_engine * se = s2_engine_from_args(args); - cwal_native * nat = cwal_value_native_part(args->engine, args->self, - FSL_TYPEID(fsl_cx) ); - cwal_value * natV = nat ? cwal_native_value(nat) : NULL; - fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL; - assert(se); - va_start(vargs,fmt); - rc = cwal_exception_setfv(args->engine, code, fmt, vargs ); - if(f){ - /* Ensure that script-triggered errors do not unduly - lie around, potentially propagating long after - they are valid. - */ - fsl_cx_err_reset(f); - } - va_end(vargs); - return rc; -} - -static int cb_toss_fsl( cwal_callback_args const * args, - fsl_cx * f ){ - int rc; - assert(f && f->error.code); - rc = (FSL_RC_OOM==f->error.code) - ? CWAL_RC_OOM - : cb_toss(args, - f->error.code, - "%.*s", (int)f->error.msg.used, - (char const *)f->error.msg.mem ); - fsl_cx_err_reset(f); - return rc; -} - -static int cb_toss_db( cwal_callback_args const * args, - fsl_db * db ){ - int rc; - assert(db && db->error.code); - rc = (FSL_RC_OOM==db->error.code) - ? CWAL_RC_OOM - : cb_toss(args, db->error.code, - "%s", (char const *)db->error.msg.mem ); - fsl_db_err_reset(db); - return rc; -} - -static cwal_value * fsl_cx_prototype( s2_engine * se ); -static cwal_value * fsl_db_prototype( s2_engine * se ); -static cwal_value * fsl_stmt_prototype( s2_engine * se ); - -/* -** Copies fb's state into cb. -*/ -static int fsl_buffer_to_cwal_buffer( cwal_engine * e, - fsl_buffer const * fb, - cwal_buffer * cb ){ - cb->used = 0; - return cwal_buffer_append(e, cb, fb->mem, (cwal_size_t)fb->used); -} - -#if 0 -/* -** Shallowly copies cb's state into fb. fb MUST NOT be modified -** via the fsl allocator while this is in place, because cwal's -** allocator is not guaranteed to be compatible (and won't be -** if certain memory capping options are enabled). -*/ -static void cwal_buffer_to_fsl_buffer( cwal_buffer const * cb, - fsl_buffer * fb ){ - fb->mem = cb->mem; - fb->capacity = cb->capacity; - fb->used = cb->used; - fb->cursor = 0; -} -#endif - -/** - Script usage: - - const sha1 = bufferInstance.sha1() -*/ -static int cb_buffer_sha1_self( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self); - char * sha; - if(!buf){ - return cb_toss(args, CWAL_RC_TYPE, - "'this' is-not-a Buffer."); - } - sha = fsl_sha1sum_cstr( (char const*)buf->mem, (int)buf->used); - if(!sha) return CWAL_RC_OOM; - assert(sha[0] && sha[FSL_STRLEN_SHA1-1] && !sha[FSL_STRLEN_SHA1]); - *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_SHA1); - /* Reminder: can't use zstring here b/c of potentially different - allocators. */ - fsl_free(sha); - if(*rv) return 0; - else{ - return CWAL_RC_OOM; - } -} -static int cb_buffer_sha3_self( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self); - char * sha; - if(!buf){ - return cb_toss(args, CWAL_RC_TYPE, - "'this' is-not-a Buffer."); - } - sha = fsl_sha3sum_cstr( (char const*)buf->mem, (int)buf->used); - if(!sha) return CWAL_RC_OOM; - assert(sha[0] && sha[FSL_STRLEN_K256-1] && !sha[FSL_STRLEN_K256]); - *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_K256); - /* Reminder: can't use zstring here b/c of potentially different - allocators. */ - fsl_free(sha); - if(*rv) return 0; - else{ - return CWAL_RC_OOM; - } -} - -/** - Script usage: - - const md5 = bufferInstance.md5() -*/ -static int cb_buffer_md5_self( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self); - char * hash; - if(!buf){ - return cb_toss(args, CWAL_RC_TYPE, - "'this' is-not-a Buffer."); - } - hash = fsl_md5sum_cstr( (char const*)buf->mem, (int)buf->used); - if(!hash) return CWAL_RC_OOM; - assert(hash[0] && hash[FSL_STRLEN_MD5-1] && !hash[FSL_STRLEN_MD5]); - *rv = cwal_new_string_value(args->engine, hash, FSL_STRLEN_MD5); - fsl_free(hash); - if(*rv) return 0; - else{ - return CWAL_RC_OOM; - } -} - -/* -** fsl_output_f() impl which forwards to cwal_output(). -** state must be a (cwal_engine *). Reminder: must return -** a FSL_RC_xxx value, not CWAL_RC_xxx. -*/ -static int fsl_output_f_cwal_output( void * state, - void const * src, - fsl_size_t n ){ - cwal_engine * e = (cwal_engine *)state; - return cwal_output( e, src, (cwal_size_t)n ) - ? FSL_RC_IO - : 0; -} - -/* -** fsl_flush_f() impl which forwards to cwal_output_flush(). state -** must be a (cwal_engine *). -*/ -static int fsl_flush_f_cwal_out( void * state ){ - cwal_engine * e = (cwal_engine *)state; - return cwal_output_flush(e); -} - -static int s2_fsl_openmode_to_flags(char const * openMode, int startFlags){ - int openFlags = startFlags; - for( ; *openMode; ++openMode ){ - switch(*openMode){ - case 'r': openFlags |= FSL_OPEN_F_RO; - break; - case 'w': openFlags |= FSL_OPEN_F_RW; - break; - case 'c': openFlags |= FSL_OPEN_F_CREATE; - break; - case 'v': openFlags |= FSL_OPEN_F_SCHEMA_VALIDATE; - break; - case 'T': openFlags |= FSL_OPEN_F_TRACE_SQL; - break; - default: - break; - } - } - return openFlags; -} - -/* -** Searches v and its prototype chain for a fsl_cx binding. If found, -** it is returned, else NULL is returned. -*/ -static fsl_cx * cwal_value_fsl_cx_part( cwal_engine * e, - cwal_value * v ){ - if(!v) return NULL; - else { - cwal_native * nv; - fsl_cx * f; - while(v){ - nv = cwal_value_get_native(v); - f = nv - ? (fsl_cx *)cwal_native_get( nv, FSL_TYPEID(fsl_cx) ) - : NULL; - if(f) return f; - else v = cwal_value_prototype_get(e, v); - } while(v); - return NULL; - } -} - - -/* -** Creates a new cwal_value (cwal_native) wrapper for the given fsl_db -** instance. If addDtor is true then a finalizer is installed for the -** instance, else it is assumed to live native-side and gets no -** destructor installed (in which case we will eventually have a problem -** when such a db is destroyed outside of the script API, unless we -** rewrite these to use weak references instead, but that might require -** one more level of struct indirection). The role of the db -** is important so that we can get the proper filename. A role of -** FSL_DBROLE_NONE should be used for non-fossil-core db handles. -** -** On success, returns 0 and assigns *rv to the new Db value. -** -** If 0!=db->filename.used then the new value gets a 'name' property -** set to the contents of db->filename. -** -** Maintenance reminder: this routine must return cwal_rc_t codes, not -** fsl_rc_e codes. -** -** TODO? Wrap up cwal_weak_ref of db handle, instead of the db handle -** itself? We only need this if Fossil instances will have their DB -** instances manipulated from outside of script-space. -*/ -static int fsl_db_new_native( s2_engine * se, fsl_cx * f, - fsl_db * db, char addDtor, - cwal_value **rv, - fsl_dbrole_e role){ - cwal_native * n; - cwal_value * nv; - int rc = 0; - char const * fname = NULL; - fsl_size_t nameLen = 0; - cwal_value * tmpV = NULL; - assert(se && db && rv); - n = cwal_new_native(se->e, db, - addDtor ? cwal_finalizer_f_fsl_db : NULL, - FSL_TYPEID(fsl_db)); - if(!n) return CWAL_RC_OOM; - nv = cwal_native_value(n); - cwal_value_ref(nv); - /* Set up "filename" property. It's problematic because - of libfossil's internal DB juggling :/. - */ - if(f) fname = fsl_cx_db_file_for_role(f, role, &nameLen); - if(!fname){ - fname = fsl_db_filename(db, &nameLen); - } - if(fname){ - tmpV = cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen); - cwal_value_ref(tmpV); - rc = tmpV - ? cwal_prop_set(nv, "filename", 8, tmpV) - : CWAL_RC_OOM; - cwal_value_unref(tmpV); - tmpV = 0; - if(rc){ - goto end; - } - fname = 0; - } - - /* Set up "name" property. */ - if(f) fname = fsl_cx_db_name_for_role(f, role, &nameLen); - if(!fname){ - fname = fsl_db_name(db); - nameLen = fname ? cwal_strlen(fname) : 0; - } - if(fname){ - tmpV = - cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen); - cwal_value_ref(tmpV); - rc = tmpV - ? cwal_prop_set(nv, "name", 4, tmpV) - : CWAL_RC_OOM; - cwal_value_unref(tmpV); - tmpV = NULL; - if(rc){ - goto end; - } - } - end: - if(!rc){ - *rv = nv; - cwal_value_unhand(nv); - cwal_value_prototype_set( nv, fsl_db_prototype(se) ); - }else{ - /* - Achtung: if addDtor then on error the dtor will be called - here. - */ - cwal_value_unref(nv); - } - return rc; -} - - -/* TODO: fix corner case: inheritence via multiple levels of Native - types will break this. */ -#define THIS_DB s2_engine * se = s2_engine_from_args(args); \ - cwal_native * nat = cwal_value_native_part(args->engine, args->self, FSL_TYPEID(fsl_db)); \ - cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \ - fsl_db * db = natV ? (fsl_db*)cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL; \ - assert(se); \ - if(!se){/*potentially unused var*/} \ - if(!db){ return cb_toss(args, FSL_RC_TYPE, \ - "'this' is not (or is no longer) " \ - "a Db instance."); } (void)0 - -#define THIS_STMT s2_engine * se = s2_engine_from_args(args); \ - cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_stmt)); \ - cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \ - fsl_stmt * stmt = natV ? (fsl_stmt*)cwal_native_get( nat, FSL_TYPEID(fsl_stmt) ) : NULL; \ - if(!se){/*potentially unused var*/} \ - assert(se); \ - if(!stmt){ return cb_toss(args, FSL_RC_TYPE, \ - "'this' is not (or is no longer) " \ - "a Stmt instance."); } (void)0 - -#define THIS_F s2_engine * se = s2_engine_from_args(args); \ - cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_cx)); \ - cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \ - fsl_cx * f = natV ? (fsl_cx*)cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL; \ - if(!se){/*potentially unused var*/} \ - assert(se); \ - if(!f){ return cb_toss(args, FSL_RC_TYPE, \ - "'this' is not (or is no longer) " \ - "a Fossil Context instance."); } (void)0 - - -static int cb_fsl_db_finalize( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_DB; - cwal_native_clear( nat, 1 ); - return 0; -} - -static int cb_fsl_stmt_finalize( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_STMT; - cwal_native_clear( nat, 1 ); - return 0; -} - - -int cb_fsl_db_ctor( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_value * v = NULL; - int rc; - s2_engine * se = s2_engine_from_args(args); - char const * fn; - char const * openMode; - cwal_size_t fnLen = 0; - int openFlags = 0; - fsl_db * dbP = 0; - fsl_cx * f = cwal_value_fsl_cx_part(args->engine, args->self) - /* It's okay if this is NULL. It will only be set when we - aFossilContext.dbOpen() is called. - */; - assert(se); - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], &fnLen) - : NULL; - if(!fn){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting a string (db file name) argument."); - } - openMode = (args->argc>1) - ? cwal_value_get_cstr(args->argv[1], NULL) - : NULL; - if(!openMode){ - openFlags = FSL_OPEN_F_RWC; - }else{ - openFlags = s2_fsl_openmode_to_flags( openMode, openFlags ); - } - dbP = fsl_db_malloc(); - if(!dbP) return CWAL_RC_OOM; - dbP->f = f; - rc = fsl_db_open( dbP, fn, openFlags ); - if(rc){ - if(dbP->error.msg.used){ - rc = cb_toss(args, FSL_RC_ERROR, - "Db open failed: code #%d: %s", - dbP->error.code, - (char const *)dbP->error.msg.mem); - }else{ - rc = cb_toss(args, FSL_RC_ERROR, - "Db open failed " - "with code #%d (%s).", rc, - fsl_rc_cstr(rc)); - } - fsl_db_close(dbP); - } - else{ - rc = fsl_db_new_native(se, f, dbP, 1, &v, FSL_DBROLE_NONE); - if(rc){ - fsl_db_close(dbP); - dbP = NULL; - assert(!v); - } - } - if(rc){ - assert(!v); - }else{ - assert(v); - *rv = v; - } - return rc; -} - - -/** - Returns a new cwal_array value containing the result column names - of the given statement . Returns NULL on error, else an array value. -*/ -static cwal_value * s2_fsl_stmt_col_names( cwal_engine * e, - fsl_stmt * st ){ - cwal_value * aryV = NULL; - cwal_array * ary = NULL; - char const * colName = NULL; - int i = 0; - int rc = 0; - cwal_value * newVal = NULL; - assert(st); - if( ! st->colCount ) return NULL; - ary = cwal_new_array(e); - if( ! ary ) return NULL; - rc = cwal_array_reserve(ary, (cwal_size_t)st->colCount); - if(rc) return NULL; - aryV = cwal_array_value(ary); - assert(ary); - for( i = 0; (0==rc) && (i < st->colCount); ++i ){ - colName = fsl_stmt_col_name(st, i); - if( ! colName ) rc = CWAL_RC_OOM; - else{ - newVal = cwal_new_string_value(e, colName, - cwal_strlen(colName)); - if( NULL == newVal ){ - rc = CWAL_RC_OOM; - } - else{ - rc = cwal_array_set( ary, i, newVal ); - if( rc ) cwal_value_unref( newVal ); - } - } - } - if( 0 == rc ) return aryV; - else{ - cwal_value_unref(aryV); - return NULL; - } -} - -static int s2_fsl_setup_new_stmt(s2_engine * se, - cwal_value * nv, - fsl_stmt * st){ - int rc; - cwal_value * v; - cwal_engine * e = se->e; -#define STMT_INCLUDE_SQL 0 -#if STMT_INCLUDE_SQL - fsl_size_t sqlLen = 0; - char const * sql; -#endif -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(K) VCHECK; rc = cwal_prop_set(nv, K, cwal_strlen(K), v); \ - if(rc) goto end; -#if STMT_INCLUDE_SQL - /* Too costly, and so far never used. */ - sql = fsl_buffer_cstr2(&st->sql, &sqlLen); - v = cwal_new_string_value(e, sql, (cwal_size_t)sqlLen) - /* Reminder to self: we could get away with an X-string for 99.9% - of use cases, but if someone holds a ref to that string - after the statement is gone, blammo - cwal-level assertion - at some point. - */; - SET("sql"); -#endif -#undef STMT_INCLUDE_SQL - v = cwal_new_integer(e, (cwal_int_t)st->colCount); - SET("columnCount"); - v = cwal_new_integer(e, (cwal_int_t)st->paramCount); - SET("parameterCount"); - if(st->colCount){ - v = s2_fsl_stmt_col_names(e, st); - SET("columnNames"); - } - cwal_value_prototype_set(nv, fsl_stmt_prototype(se)); -#undef SET -#undef VCHECK - assert(!rc); - end: - return rc; -} - -static int cb_fsl_db_prepare( cwal_callback_args const * args, - cwal_value **rv ){ - fsl_stmt st = fsl_stmt_empty; - int rc; - char const * sql; - cwal_size_t sqlLen = 0; - cwal_value * nv = NULL; - cwal_engine * e = args->engine; - fsl_stmt * st2 = NULL; - THIS_DB; - sql = args->argc - ? cwal_value_get_cstr(args->argv[0], &sqlLen) - : NULL; - if(!sql || !sqlLen){ - return cwal_exception_setf(e, - FSL_RC_MISUSE, - "Expecting a non-empty string " - "argument (SQL)."); - } - assert(!st.stmt); - rc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql); - if(rc){ - rc = cb_toss_db(args, db); - assert(!st.stmt); - }else{ - st2 = fsl_stmt_malloc(); - nv = st2 - ? cwal_new_native_value(e, - st2, cwal_finalizer_f_fsl_stmt, - FSL_TYPEID(fsl_stmt)) - : NULL; - if(!nv){ - if(st2) fsl_free(st2); - fsl_stmt_finalize(&st); - st2 = NULL; - rc = CWAL_RC_OOM; - }else{ - void const * kludge = st2->allocStamp; - *st2 = st; - st2->allocStamp = kludge; - rc = s2_fsl_setup_new_stmt(se, nv, st2); - if(!rc) *rv = nv; - } - } - if(rc && nv){ - cwal_value_unref(nv); - } - return rc; -} - -static int cb_fsl_db_filename( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fname = NULL; - fsl_size_t nameLen = 0; - THIS_DB; - fname = fsl_db_filename(db, &nameLen); - *rv = fname - ? cwal_new_string_value(args->engine, fname, nameLen) - : cwal_value_null(); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int cb_fsl_db_name( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fname = NULL; - THIS_DB; - fname = fsl_db_name(db); - *rv = fname - ? cwal_new_string_value(args->engine, fname, fsl_strlen(fname)) - : cwal_value_null(); - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - Extracts result column ndx from st and returns a "the closest - approximation" of its type in cwal_value form by assigning it to - *rv. Returns 0 on success. On errror *rv is not modified. - Does not trigger a th1ish/cwal exception on error. -*/ -static int s2_fsl_stmt_to_value( cwal_engine * e, - fsl_stmt * st, - int ndx, - cwal_value ** rv ){ - int vtype = sqlite3_column_type(st->stmt, ndx); - switch( vtype ){ - case SQLITE_NULL: - *rv = cwal_value_null(); - return 0; - case SQLITE_INTEGER: - *rv = cwal_new_integer( e, - (cwal_int_t)fsl_stmt_g_int64(st,ndx) ); - break; - case SQLITE_FLOAT: - *rv = cwal_new_double( e, - (cwal_double_t)fsl_stmt_g_double(st, ndx) ); - break; - case SQLITE_BLOB: { - int rc; - fsl_size_t slen = 0; - void const * bl = 0; - rc = fsl_stmt_get_blob(st, ndx, &bl, &slen); - if(rc){ - /* FIXME: we need a fsl-to-cwal rc converter (many of them line up). */ - return CWAL_RC_ERROR; - }else if(!bl){ - *rv = cwal_value_null(); - }else{ - cwal_buffer * buf = cwal_new_buffer(e, (cwal_size_t)(slen+1)); - if(!buf) return CWAL_RC_OOM; - rc = cwal_buffer_append( e, buf, bl, (cwal_size_t)slen ); - if(rc){ - assert(!*rv); - cwal_value_unref(cwal_buffer_value(buf)); - }else{ - *rv = cwal_buffer_value(buf); - } - } - break; - } - case SQLITE_TEXT: { - fsl_size_t slen = 0; - char const * str = 0; - str = fsl_stmt_g_text( st, ndx, &slen ); - *rv = cwal_new_string_value(e, slen ? str : NULL, - (cwal_size_t) slen); - break; - } - default: - return cwal_exception_setf(e, CWAL_RC_TYPE, - "Unknown db column type (%d).", - vtype); - } - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - Extracts all columns from st into a new cwal_object value. - colNames is expected to hold the same number of entries - as st has columns, and in the same order. The entries in - colNames are used as the object's field keys. - - Returns NULL on error. -*/ -static cwal_value * s2_fsl_stmt_row_to_object2( cwal_engine * e, - fsl_stmt * st, - cwal_array const * colNames ){ - int colCount; - cwal_value * objV; - cwal_object * obj; - cwal_string * colName; - int i; - int rc; - if( ! st ) return NULL; - colCount = st->colCount; - if( !colCount || (colCount>(int)cwal_array_length_get(colNames)) ) { - return NULL; - } - obj = cwal_new_object(e); - if( ! obj ) return NULL; - objV = cwal_object_value( obj ); - cwal_value_ref(objV); - for( i = 0; i < colCount; ++i ){ - cwal_value * v = NULL; - cwal_value * colNameV = cwal_array_get( colNames, i ); - colName = cwal_value_get_string( colNameV ); - if( ! colName ) goto error; - rc = s2_fsl_stmt_to_value( e, st, i, &v ); - if( rc ) goto error; - cwal_value_ref(v); - rc = cwal_prop_set_v( objV, colNameV, v ); - cwal_value_unref(v); - if( rc ){ - goto error; - } - } - cwal_value_unhand(objV); - return objV; - error: - cwal_value_unref( objV ); - return NULL; -} - -/** - Extracts all columns from st into a new cwal_object value, - using st's column names as the keys. - - Returns NULL on error. -*/ -static cwal_value * s2_fsl_stmt_row_to_object( cwal_engine * e, - fsl_stmt * st ){ - cwal_value * objV; - cwal_object * obj; - char const * colName; - int i; - int rc; - if( ! st || !st->colCount ) return NULL; - obj = cwal_new_object(e); - if( ! obj ) return NULL; - objV = cwal_object_value(obj); - cwal_value_ref(objV); - for( i = 0; i < st->colCount; ++i ){ - cwal_value * v = NULL; - colName = fsl_stmt_col_name(st, i); - if( ! colName ) goto error; - rc = s2_fsl_stmt_to_value( e, st, i, &v ); - if( rc ) goto error; - cwal_value_ref(v); - rc = cwal_prop_set( objV, colName, cwal_strlen(colName), v ); - cwal_value_unref(v); - if( rc ){ - goto error; - } - } - cwal_value_unhand(objV); - return objV; - error: - cwal_value_unref( objV ); - return NULL; -} - -/** - Appends all result columns from st to ar. -*/ -static int s2_fsl_stmt_row_to_array2( cwal_engine * e, - fsl_stmt * st, - cwal_array * ar) -{ - int i; - int rc = 0; - for( i = 0; i < st->colCount; ++i ){ - cwal_value * v = NULL; - rc = s2_fsl_stmt_to_value( e, st, i, &v ); - if( rc ) goto end; - cwal_value_ref(v); - rc = cwal_array_append( ar, v ); - cwal_value_unref(v); - if( rc ){ - goto end; - } - } - end: - return rc; -} - -/** - Converts all column values from st into a cwal_array - value. Returns NULL on error, else an array value. -*/ -static cwal_value * s2_fsl_stmt_row_to_array( cwal_engine * e, - fsl_stmt * st ) -{ - cwal_array * ar; - int rc = 0; - if( ! st || !st->colCount ) return NULL; - ar = cwal_new_array(e); - if( ! ar ) return NULL; - rc = s2_fsl_stmt_row_to_array2( e, st, ar ); - if(rc){ - cwal_array_unref(ar); - return NULL; - }else{ - return cwal_array_value(ar); - } -} - -/** - mode: 0==Object, >0==Array, <0==no row data (just boolean - indicator) -*/ -static int s2_fsl_stmt_step_impl( cwal_callback_args const * args, - cwal_value **rv, - int mode ){ - int rc; - int scode; - THIS_STMT; - scode = fsl_stmt_step( stmt ); - switch(scode){ - case FSL_RC_STEP_DONE: - rc = 0; - *rv = cwal_value_undefined(); - break; - case FSL_RC_STEP_ROW:{ - if(0==mode){ - /* Object mode */ - cwal_array const * colNames = - cwal_value_get_array( cwal_prop_get(args->self, - "columnNames", 11) ); - *rv = colNames - ? s2_fsl_stmt_row_to_object2(args->engine, - stmt, colNames) - : s2_fsl_stmt_row_to_object(args->engine, stmt); - }else if(mode>0){ - /* Array mode */ - *rv = s2_fsl_stmt_row_to_array(args->engine, stmt); - }else{ - /* "Normal" mode */ - *rv = cwal_value_true(); - } - rc = *rv ? 0 : CWAL_RC_OOM; - break; - } - default: - rc = cb_toss_db(args, stmt->db); - break; - } - return rc; -} - -static int cb_fsl_stmt_step_array( cwal_callback_args const * args, cwal_value **rv ){ - return s2_fsl_stmt_step_impl( args, rv, 1 ); -} - -static int cb_fsl_stmt_step_object( cwal_callback_args const * args, cwal_value **rv ){ - return s2_fsl_stmt_step_impl( args, rv, 0 ); -} - -static int cb_fsl_stmt_step( cwal_callback_args const * args, cwal_value **rv ){ - return s2_fsl_stmt_step_impl( args, rv, -1 ); -} - -static int cb_fsl_stmt_reset( cwal_callback_args const * args, cwal_value **rv ){ - char resetCounter = 0; - int rc; - THIS_STMT; - if(1 < args->argc) resetCounter = cwal_value_get_bool(args->argv[1]); - rc = fsl_stmt_reset2(stmt, resetCounter); - if(!rc) *rv = args->self; - return rc - ? cb_toss_db( args, stmt->db ) - : 0; -} - -static int cb_fsl_stmt_row_to_array( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - THIS_STMT; - if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE, - "Cannot fetch row data from an " - "unstepped statement."); - *rv = s2_fsl_stmt_row_to_array(args->engine, stmt); - if(*rv) rc = 0; - else rc = cb_toss(args, CWAL_RC_ERROR, - "Unknown error converting statement " - "row to Array."); - return rc; -} - -static int cb_fsl_stmt_row_to_object( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - THIS_STMT; - if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE, - "Cannot fetch row data from " - "an unstepped statement."); - *rv = s2_fsl_stmt_row_to_object(args->engine, stmt); - if(*rv) rc = 0; - else rc = cb_toss(args, CWAL_RC_ERROR, - "Unknown error converting statement row " - "to Object."); - return rc; -} - - -/** - Tries to bind v to the given parameter column (1-based) of - nv->stmt. Returns 0 on success, triggers a script-side exception - on error. -*/ -static int s2_fsl_stmt_bind( cwal_engine * e, - fsl_stmt * st, - int ndx, - cwal_value * v ){ - int rc; - int const vtype = v ? cwal_value_type_id(v) : CWAL_TYPE_NULL; - if(ndx<1) { - return cwal_exception_setf(e, FSL_RC_RANGE, - "Bind index %d is invalid: indexes are 1-based.", - ndx); - } - else if(ndx > st->paramCount) { - return cwal_exception_setf(e, FSL_RC_RANGE, - "Bind index %d is out of range. Range=(1..%d).", - ndx, st->paramCount); - } - /*MARKER(("Binding %s to column #%u\n", cwal_value_type_name(v), ndx));*/ - switch( vtype ){ - case CWAL_TYPE_NULL: - case CWAL_TYPE_UNDEF: - rc = fsl_stmt_bind_null(st, ndx); - break; - case CWAL_TYPE_BOOL: - rc = fsl_stmt_bind_int32(st, ndx, cwal_value_get_bool(v)); - break; - case CWAL_TYPE_INTEGER: - /* We have no way of knowing which type (32/64-bit) to bind - here, so we'll guess. We could check the range, i guess, - but for sqlite it makes little or no difference, anyway. - */ - rc = fsl_stmt_bind_int64(st, ndx, - (int64_t)cwal_value_get_integer(v)); - break; - case CWAL_TYPE_DOUBLE: - rc = fsl_stmt_bind_double(st, ndx, (double)cwal_value_get_double(v)); - break; - case CWAL_TYPE_BUFFER: - case CWAL_TYPE_STRING: { - /* FIXME: bind using the fsl_stmt_bind_xxx() APIs!!! - string/buffer binding is currently missing. - */ - cwal_size_t slen = 0; - char const * cstr = cwal_value_get_cstr(v, &slen); - if(!cstr){ - /* Will only apply to empty buffers (Strings are never - NULL). But it's also possible that a buffer with - length 0 has a non-NULL memory buffer. So we cannot, - without further type inspection, clearly - differentiate between a NULL and empty BLOB - here. This distinction would seem to be (?) - unimportant for this particular use case, so - fixing/improving it can wait. - */ - rc = fsl_stmt_bind_null(st, ndx); - } -#if 1 - else if(CWAL_TYPE_BUFFER==vtype){ - rc = fsl_stmt_bind_blob(st, ndx, cstr, (int)slen, 1); - } -#endif - else{ - rc = fsl_stmt_bind_text(st, ndx, cstr, (int)slen, 1); - } - break; - } - default: - return cwal_exception_setf(e, FSL_RC_TYPE, - "Unhandled data type (%s) for binding " - "column %d.", - cwal_value_type_name(v), ndx); - } - if(rc){ - fsl_size_t msgLen = 0; - char const * msg = NULL; - int const dbRc = fsl_db_err_get(st->db, &msg, &msgLen); - rc = cwal_exception_setf(e, FSL_RC_DB, - "Binding column %d failed with " - "sqlite code #%d: %.*s", ndx, - dbRc, (int)msgLen, msg); - } - return rc; -} - -static int s2_fsl_stmt_bind_values_a( cwal_engine * e, - fsl_stmt * st, - cwal_array const * src){ - int const n = (int)cwal_array_length_get(src); - int i = 0; - int rc = 0; - for( ; !rc && (i < n); ++i ){ - rc = s2_fsl_stmt_bind( e, st, i+1, - cwal_array_get(src,(cwal_size_t)i) ); - } - return rc; -} - -/** - Internal helper for binding by name. -*/ -typedef struct { - fsl_stmt * st; - cwal_engine * e; -} BindByNameState; - -/** - Property visitor helper for binding parameters by name. -*/ -static int cwal_kvp_visitor_f_bind_by_name( cwal_kvp const * kvp, void * state ){ - cwal_size_t keyLen = 0; - cwal_value const * v = cwal_kvp_key(kvp); - char const * key = cwal_value_get_cstr(v, &keyLen); - BindByNameState const * bbn = (BindByNameState const *)state; - int ndx; - if(!key){ - if(!cwal_value_is_integer(v)){ - return 0 /* non-string property. */; - }else{ - ndx = (int)cwal_value_get_integer(v); - } - }else{ - ndx = fsl_stmt_param_index(bbn->st, key); - if(ndx<=0){ - return cwal_exception_setf(bbn->e, CWAL_RC_RANGE, "Parameter name '%.*s' " - "does not resolve to an index. (Maybe missing " - "the leading ':' or '$' part of the param name?)", - (int)keyLen, key); - } - } - return s2_fsl_stmt_bind(bbn->e, bbn->st, ndx, cwal_kvp_value(kvp)); -} - -/** - Internal helper for binding values to statements. - - bind may be either an Array of values to bind, a container of param - names (incl. ':' or '$' prefix), or a single value, to bind at the - given index. For arrays/containers, the given index is ignored. As - a special case, if bind === cwal_value_undefined() then it is - simply ignored. -*/ -static int s2_fsl_stmt_bind_proxy( cwal_engine * e, - fsl_stmt * st, - int ndx, - cwal_value * bind){ - int rc = 0; - if(cwal_value_undefined() == bind) rc = 0; - else if(cwal_value_is_array(bind)){ - rc = s2_fsl_stmt_bind_values_a(e, st, - cwal_value_get_array(bind)); - }else if(cwal_props_can(bind)){ - BindByNameState bbn; - bbn.e = e; - bbn.st = st; - rc = cwal_props_visit_kvp(bind, cwal_kvp_visitor_f_bind_by_name, - &bbn); - }else{ - rc = s2_fsl_stmt_bind( e, st, ndx, bind); - } - return rc; -} - -#if 0 -static int s2_fsl_stmt_bind_from_args( cwal_engine * e, - fsl_stmt *st, - cwal_callback_args const * args, - uint16_t startAtArg ){ - int rc = 0; - cwal_value * bind = (args->argc < startAtArg) - ? args->argv[startAtArg] - : NULL; - if(!bind) return 0; - if(cwal_value_is_array(bind)){ - rc = s2_fsl_stmt_bind_values_a(e, st, - cwal_value_get_array(bind)); - }else if(cwal_value_is_object(bind)){ - MARKER(("TODO: binding by name")); - rc = CWAL_RC_ERROR; - }else if(!cwal_value_is_undef(bind) && (args->argc > startAtArg)){ - int i = startAtArg; - for( ; !rc && (i < (int)args->argc); ++i ){ - rc = s2_fsl_stmt_bind( e, st, i, args->argv[i]); - } - } - return rc; -} -#endif - -static int cb_fsl_stmt_bind( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_int_t ndx = -999; - int rc; - THIS_STMT; - if(!args->argc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting (integer Index [,Value=null]) or " - "(Array|Object|undefined) arguments."); - } - if(cwal_value_undefined() == args->argv[0]){ - /* Special case to simplify some script code */ - *rv = args->self; - return 0; - }else if(cwal_value_is_integer(args->argv[0])){ - ndx = cwal_value_get_integer(args->argv[0]); - if(1>ndx){ - return cb_toss(args, FSL_RC_RANGE, - "SQL bind() indexes are 1-based."); - } - } - rc = s2_fsl_stmt_bind_proxy(args->engine, stmt, - (int)(-999==ndx ? 1 : ndx), - (-999==ndx) - ? args->argv[0] - : ((args->argc>1) - ? args->argv[1] - : NULL)); - if(!rc) *rv = args->self; - return rc; -} - -static int cb_fsl_stmt_get( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_int_t ndx; - THIS_STMT; - if(!args->argc || !cwal_value_is_integer(args->argv[0])){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting Index arguments."); - } - else if(!stmt->colCount){ - return cb_toss(args, FSL_RC_MISUSE, - "This is not a fetch-style statement."); - } - ndx = cwal_value_get_integer(args->argv[0]); - if((ndx<0) || (ndx>=stmt->colCount)){ - return cb_toss(args, CWAL_RC_RANGE, - "Column index %d is out of range. " - "Valid range is (%d..%d).", ndx, - 0, stmt->colCount-1); - } - rc = s2_fsl_stmt_to_value( args->engine, stmt, - (uint16_t)ndx, rv ); - if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){ - rc = cb_toss( args, rc, "Get-by-index failed with code %d (%s).", - rc, cwal_rc_cstr(rc)); - } - return rc; -} - -static int cb_fsl_db_exec_impl( cwal_callback_args const * args, - cwal_value **rv, - char isMulti ){ - int rc = 0; - int argIndex = 0; - THIS_DB; - do{ /* loop on the arguments, expecting SQL for each one... */ - cwal_size_t sqlLen = 0; - char const * sql = (args->argc>argIndex) - ? cwal_value_get_cstr(args->argv[argIndex], &sqlLen) - : NULL; - if(!sql || !sqlLen){ - rc = (argIndex>0) - ? 0 - : cb_toss(args, - FSL_RC_MISUSE, - "Expecting a non-empty string/buffer " - "argument (SQL)."); - break; - } - /* MARKER(("SQL:<<<%.*s>>>\n", (int)sqlLen, sql)); */ - rc = isMulti - ? fsl_db_exec_multi( db, "%.*s", (int)sqlLen, sql ) - : fsl_db_exec( db, "%.*s", (int)sqlLen, sql ) - ; - if(rc) rc = cb_toss_db( args, db ); - }while(!rc && (++argIndex < args->argc)); - if(!rc) *rv = args->self; - return rc; -} - -static int cb_fsl_db_exec_multi( cwal_callback_args const * args, - cwal_value **rv ){ - return cb_fsl_db_exec_impl( args, rv, 1 ); -} - -static int cb_fsl_db_exec( cwal_callback_args const * args, - cwal_value **rv ){ - return cb_fsl_db_exec_impl( args, rv, 0 ); -} - -static int cb_fsl_db_last_insert_id( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_DB; - *rv = cwal_new_integer(args->engine, - (cwal_int_t)fsl_db_last_insert_id(db)); - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - If !cb, this is a no-op, else if cb is-a Function, it is call()ed, - if it is a String/Buffer, it is eval'd, else an exception is - thrown. self is the 'this' for the call(). Result is placed in - *rv. Returns 0 on success. - */ -static int s2_fsl_stmt_each_call_proxy( s2_engine * se, fsl_stmt * st, - cwal_value * cb, cwal_value * self, - cwal_value **rv ){ - char const * cstr; - cwal_size_t strLen = 0; - char const useNewScope = 1 - /* - We need a new scope to avoid that evaluation of a string callback - vacuums up self. - */; - if(!cb) return 0; - else if((cstr = cwal_value_get_cstr(cb,&strLen))){ - /** TODO: tokenize this only the first time. */ - s2_ptoker pr = s2_ptoker_empty; - cwal_scope s2Scope = cwal_scope_empty; - int rc; - /** - Bug Reminder: in stack traces and assertion traces the - script name/location info is wrong in this case (empty and - relative to cstr, respectively) because s2 doesn't have - a way to map this string back to a source location at this - point. - */ - if(useNewScope){ - rc = cwal_scope_push2(se->e, &s2Scope); - if(rc) return rc; - } - s2_ptoker_init(&pr, cstr, (int)strLen); - pr.name = "Db.each() callback"; - rc = s2_eval_ptoker(se, &pr, 0, rv); - if(CWAL_RC_RETURN==rc){ - rc = 0; - *rv = cwal_propagating_take(se->e); - } - if(useNewScope){ - assert(se->e->current == &s2Scope); - cwal_scope_pop2(se->e, rc ? 0 : *rv); - } - return rc; - }else if(cwal_value_is_function(cb)){ - cwal_function * f = cwal_value_get_function(cb); - if(useNewScope){ - return cwal_function_call(f, self, rv, 0, NULL ); - }else{ - cwal_scope * sc = cwal_scope_current_get(se->e); - return cwal_function_call_in_scope(sc, f, self, rv, - 0, NULL ); - } - }else { - return cwal_exception_setf(se->e, FSL_RC_MISUSE, - "Don't now how to handle callback " - "of type '%s'.", - cwal_value_type_name(cb)); - } -} - -/** - Script usage: - - db.each({ - sql: "SQL CODE", // required - bind: X, // parameter value or array of values to bind. - // Passing the undefined value is the same as - // not passing any value. - mode: 0, // 0==rows as objects, else as arrays (default) - callback: string | function // called for each row - }) - - Only the 'sql' property is required, and 'bind' must be set - if the SQL contains any binding placeholders. - - In the scope of the callback, 'this' will resolve to the current - row data, either in object or array form (depending on the 'mode' - property). If the callback throws, that exception is propagated. - If it returns a literal false (as opposed to another falsy value) - then iteration stops without an error. - - In addition, the following scope-level variables are set: - - - rowNumber: 1-based number of the row (the iteration count). - - - columnNames: array of column names for the result set. - - Example callbacks: - - <<engine; - THIS_DB; - if(!args->argc || !cwal_value_is_object(args->argv[0])){ - return cwal_exception_setf(e, FSL_RC_MISUSE, - "Expecting Object parameter."); - } - props = args->argv[0]; - sql = cwal_prop_get(props, "sql", 3 ); - csql = sql ? cwal_value_get_cstr(sql, &sqlLen) : NULL; - if(!csql){ - return cb_toss(args, FSL_RC_MISUSE, - "Missing 'sql' string/buffer property."); - } - vMode = cwal_prop_get( props, "mode", 4 ); - mode = vMode ? cwal_value_get_integer(vMode) : 1; - bind = cwal_prop_get( props, "bind", 4 ); - callback = cwal_prop_get( props, "callback", 8 ); - if(callback){ - if(!cwal_value_is_function(callback) - && !cwal_value_get_cstr(callback, 0)){ - callback = NULL; - } - } - rc = fsl_db_prepare(db, &st, "%.*s", (int)sqlLen, csql); - if(rc){ - return cb_toss_db(args, db); - } - if(bind && !cwal_value_is_undef(bind)){ - rc = s2_fsl_stmt_bind_proxy( e, &st, 1, bind ); - if(rc) goto end; - } - if(st.colCount){ - colNamesV = s2_fsl_stmt_col_names(e,&st); - if(!colNamesV){ - rc = CWAL_RC_OOM; - goto end; - } - cwal_value_ref(colNamesV); - rc = s2_var_set( se, 0, "columnNames", 11, colNamesV ); - cwal_value_unref(colNamesV); - if(rc){ - colNamesV = NULL; - goto end; - } - colNames = cwal_value_get_array(colNamesV); - assert(cwal_array_length_get(colNames) == (cwal_size_t)st.colCount); - } - rc = s2_var_set( se, 0, "columnCount", 11, - cwal_new_integer(e, (cwal_int_t)st.colCount) ); - if(rc) goto end; - - /* - Step through each row and call the given callback function/string... - */ - for( rowNum = 1; - FSL_RC_STEP_ROW == (scode = fsl_stmt_step( &st )); - ++rowNum){ - if(callback && !cbSelf){ - /** Init callback info if needed */ - cbArray = (0==mode) - ? NULL - : cwal_new_array(e); - cbSelf = cbArray - ? cwal_array_value(cbArray) - : cwal_new_object_value(e); - if(!cbSelf){ - rc = CWAL_RC_OOM; - break; - }else{ - cwal_value_ref(cbSelf); - rc = s2_var_set(se, 0, "this", 4, cbSelf); - cwal_value_unref(cbSelf); - if(rc){ - goto end; - } - } - } - if(cbSelf){ - /** - Set up this.XXX to each column's value, where - XXX is either the column index (for array mode) - or column name (for object mode). - */ - cwal_value * frv = 0; - int i = 0; - for( ; !rc && (i < st.colCount); ++i ){ - cwal_value * cv; - cv = NULL; - rc = s2_fsl_stmt_to_value(e, &st, i, &cv); - if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){ - rc = cwal_exception_setf(e, rc, - "Conversion from db column to " - "value failed with code " - "%d (%s).", - rc, fsl_rc_cstr(rc)); - } - cwal_value_ref(cv); - if(cbArray) rc = cwal_array_set( cbArray, i, cv); - else{ - cwal_value * key; - assert(colNames); - key = cwal_array_get(colNames, i); - assert(key); - rc = cwal_prop_set_v( cbSelf, key, cv); - } - cwal_value_unref(cv); - } - if(rc) goto end; - rc = s2_var_set( se, 0, "rowNumber", 9, - cwal_new_integer(e, rowNum) ); - if(rc) goto end /* leave potentially leaked new integer for the - scope to clean up */; - rc = s2_fsl_stmt_each_call_proxy(se, &st, callback, cbSelf, &frv); - if(rc) goto end; - else if(frv == cwal_value_false()/*yes, a ptr comparison*/){ - /* If the function returns literal false, stop - looping without an error. */ - goto end; - } - cbSelf = 0 /* Causes the next iteration to create a new - * array/object per row, overwriting "this". */; - } - } - if(FSL_RC_STEP_ERROR==scode){ - rc = cwal_exception_setf(e, FSL_RC_DB, - "Stepping cursor failed."); - } - end: - if(st.stmt){ - fsl_stmt_finalize( &st ); - } - if(!rc) *rv = args->self; - return rc; -} - -/** - Value DB.selectValue(string SQL [, bind = undefined [,defaultResult=undefined]]) -*/ -static int cb_fsl_db_select_value( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0, dbrc = 0; - char const * sql; - cwal_size_t sqlLen = 0; - fsl_stmt st = fsl_stmt_empty; - cwal_value * bind = 0; - cwal_value * dflt = 0; - THIS_DB; - sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0; - if(!sql || !sqlLen){ - return cwal_exception_setf(args->engine, FSL_RC_MISUSE, - "Expecting SQL string/buffer parameter."); - } - bind = args->argc>1 ? args->argv[1] : 0; - - dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql ); - if(dbrc){ - rc = cb_toss_db(args, db); - assert(rc); - goto end; - } - - if(bind){ - if(2==args->argc && !st.paramCount){ - dflt = bind; - }else{ - rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind); - } - bind = 0; - } - - if( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){ - cwal_value * xrv = 0; - rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &xrv); - if(!rc){ - *rv = xrv ? xrv : cwal_value_undefined(); - } - }else if(FSL_RC_STEP_DONE == dbrc){ - *rv = dflt ? dflt : cwal_value_undefined(); - }else if(FSL_RC_STEP_ERROR == dbrc){ - assert(st.db->error.code); - rc = cb_toss_db(args, st.db); - } - end: - fsl_stmt_finalize(&st); - return rc; -} - - -/** - Array Db.selectValues(string|buffer SQL [, bind = undefined]) -*/ -static int cb_fsl_db_select_values( cwal_callback_args const * args, cwal_value **rv ){ - int rc = 0, dbrc = 0; - char const * sql; - cwal_size_t sqlLen = 0; - fsl_stmt st = fsl_stmt_empty; - cwal_value * bind = 0; - cwal_array * ar = 0; - cwal_value * arV = 0; - THIS_DB; - sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0; - if(!sql || !sqlLen){ - return cwal_exception_setf(args->engine, FSL_RC_MISUSE, - "Expecting SQL string/buffer parameter."); - } - bind = args->argc>1 ? args->argv[1] : 0; - - dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql ); - if(dbrc){ - rc = cb_toss_db(args, db); - assert(rc); - goto end; - } - - ar = cwal_new_array(args->engine); - if(!ar){ - rc = CWAL_RC_OOM; - goto end; - } - arV = cwal_array_value(ar); - cwal_value_ref(arV); - - if(bind){ - rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind); - bind = 0; - } - - while( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){ - cwal_value * col = 0; - rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &col); - if(!rc){ - cwal_value_ref(col); - rc = cwal_array_append(ar, col); - cwal_value_unref(col); - } - } - - end: - fsl_stmt_finalize(&st); - if(rc){ - cwal_value_unref(arV); - }else if(arV){ - *rv = arV; - cwal_value_unhand(arV); - } - return rc; -} - -static int cb_fsl_db_trans_begin( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - THIS_DB; - rc = fsl_db_transaction_begin(db); - if(rc){ - rc = cb_toss_db(args, db); - } - if(!rc) *rv = args->self; - return rc; -} - -static int cb_fsl_db_trans_end( cwal_callback_args const * args, cwal_value **rv, int mode ){ - int rc; - THIS_DB; - if(db->beginCount<=0){ - return cb_toss( args, CWAL_RC_RANGE, "No transaction is active."); - } - if(mode < 0){ - rc = fsl_db_rollback_force(db); - }else{ - rc = fsl_db_transaction_end(db, mode ? 1 : 0); - } - if(rc){ - if(db->error.code){ - rc = cb_toss_db(args, db); - }else{ - rc = cb_toss( args, CWAL_RC_ERROR, - "fsl error code during %s: %d (%s)", - (mode<0 - ? "forced rollback" - : (mode>0 - ? "rollback" - : "db commit" /* as opposed to repo checkin/commit */) - ), - rc, fsl_rc_cstr(rc) ); - } - }else{ - *rv = args->self; - } - return rc; -} - -static int cb_fsl_db_trans_commit( cwal_callback_args const * args, cwal_value **rv ){ - return cb_fsl_db_trans_end( args, rv, 0 ); -} - -static int cb_fsl_db_trans_rollback( cwal_callback_args const * args, cwal_value **rv ){ - return cb_fsl_db_trans_end( args, rv, - (args->argc - && cwal_value_get_bool(args->argv[0])) - ? -1 /* force immediate rollback */ - : 1 ); -} - -static int cb_fsl_db_trans_state( cwal_callback_args const * args, cwal_value **rv ){ - int bc; - THIS_DB; - bc = db->beginCount > 0 ? db->beginCount : 0; - *rv = cwal_new_integer(args->engine, - (cwal_int_t)(db->doRollback ? -bc : bc)); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int cb_fsl_db_trans( cwal_callback_args const * args, cwal_value **rv ){ - int rc; - cwal_function * func; - THIS_DB; - func = args->argc ? cwal_value_function_part(args->engine, args->argv[0]) : 0; - if(!func){ - return cb_toss(args, CWAL_RC_MISUSE, "Expecting a Function argument."); - } - rc = fsl_db_transaction_begin( db ); - if(rc){ - return db->error.code - ? cb_toss_db(args, db) - : cb_toss(args, CWAL_RC_ERROR, - "Error #%d (%s) while starting transaction.", - rc, fsl_rc_cstr(rc)); - } - rc = cwal_function_call( func, args->self, 0, 0, 0 ); - if(rc){ - fsl_db_transaction_end( db, 1 ); -#if 0 - /* Just proving to myself that this rollback is still called in - the face of an s2-level exit()/fatal()/assert(). It does. We - can in fact preempt a FATAL result here, but doing so is a bad - idea. - */ - if(CWAL_RC_FATAL==rc){ - cwal_exception_set(args->engine, cwal_propagating_take(args->engine)); - rc = CWAL_RC_EXCEPTION; - } -#endif - }else{ - rc = fsl_db_transaction_end( db, 0 ); - if(rc){ - rc = db->error.code - ? cb_toss_db(args, db) - : cb_toss(args, CWAL_RC_ERROR, - "Error #%d (%s) while committing transaction.", - rc, fsl_rc_cstr(rc)); - }else{ - *rv = args->self; - } - } - return rc; -} - - -/** - ** If cx has a property named "db", it is returned, else if - ** createIfNotExists is true then a new native fsl_db Object named - ** "db" is inserted into cx and returned. Returns NULL on allocation - ** error or if no such property exists and createIfNotExists is - ** false. - */ -static cwal_value * fsl_cx_db_prop( fsl_cx * f, - cwal_value * fv, - s2_engine * se, - char createIfNotExists){ - cwal_value * rv; - fsl_db * db = fsl_cx_db(f); - /* assert(db); */ - rv = db ? cwal_prop_get(fv, "db", 2) : NULL; - if(db && !rv && createIfNotExists){ - int rc = fsl_db_new_native(se, f, db, 0, &rv, FSL_DBROLE_MAIN); - if(rc) return NULL; - cwal_value_ref(rv); - if(rv){ - rc = cwal_prop_set(fv, "db", 2, rv); - cwal_value_unref(rv); - if(rc) rv = NULL; - } - } - return rv; -} - - -/** - ** Internal helper which adds vSelf->db->repo/checkout/config - ** handles. Returns 0 on success. MUST return CWAL_RC_xxx codes, NOT - ** FSL_RC_xxx codes. - */ -static int fsl_cx_add_db_handles(fsl_cx * f, cwal_value * vSelf, - s2_engine * se){ - cwal_value * dbV = fsl_cx_db_prop(f, vSelf, se, 1); - cwal_value * d = NULL; - fsl_db * db; - int rc; - cwal_size_t nameLen; - if(!dbV) return FSL_RC_OOM; -#define DBH(NAME, GETTER, ROLE) \ - nameLen = cwal_strlen(NAME); \ - d = cwal_prop_get(dbV, NAME, nameLen); \ - if(!d && (db = GETTER(f))){ \ - rc = fsl_db_new_native(se, f, db, 0, &d, ROLE); \ - if(rc) return rc; \ - cwal_value_ref(d); \ - assert(d); \ - rc = cwal_prop_set(dbV, NAME, nameLen, d); \ - cwal_value_unref(d); \ - if(rc) return rc; \ - } - /*DBH("main", fsl_cx_db, FSL_DBROLE_MAIN);*/ - DBH("repo", fsl_cx_db_repo, FSL_DBROLE_REPO); - DBH("checkout", fsl_cx_db_ckout, FSL_DBROLE_CKOUT); - DBH("config", fsl_cx_db_config, FSL_DBROLE_CONFIG); -#undef DBH - return 0; -} - -static int cb_fsl_config_open( cwal_callback_args const * args, - cwal_value **rv ){ - char const * dbName; - int rc; - THIS_F; - dbName = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(args->argc && (!dbName || !*dbName)){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting a non-empty string argument."); - } - rc = fsl_config_open( f, (dbName && *dbName) ? dbName : 0 ); - if(rc){ - rc = cb_toss_fsl(args, f); - }else{ - rc = fsl_cx_add_db_handles(f, args->self, se); - if(!rc) *rv = args->self; - } - return rc; -} - -#if 0 -/* this gets trickier than i'd like because of the script-side - db handles. -*/ -static int cb_fsl_config_close( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_F; - fsl_config_close(f); - *rv = args->self; - return 0; -} -#endif - -static int cb_fsl_repo_open( cwal_callback_args const * args, - cwal_value **rv ){ - char const * dbName; - int rc; - THIS_F; - dbName = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!dbName || !*dbName){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting a non-empty string argument."); - } - rc = fsl_repo_open( f, dbName ); - if(rc){ - rc = cb_toss_fsl(args, f); - }else{ - rc = fsl_cx_add_db_handles(f, args->self, se); - if(!rc) *rv = args->self; - } - return rc; -} - -static int cb_fsl_ckout_open_dir( cwal_callback_args const * args, - cwal_value **rv ){ - char const * dbName = 0; - cwal_size_t nameLen = 0; - int rc; - int checkParentDirs; - THIS_F; - dbName = args->argc - ? cwal_value_get_cstr(args->argv[0], &nameLen) - : 0; - checkParentDirs = args->argc>1 - ? (cwal_value_get_bool(args->argv[1]) ? 1 : 0) - : 1; - rc = fsl_ckout_open_dir( f, dbName, checkParentDirs ); - if(rc){ - rc = cb_toss_fsl(args, f); - }else{ - rc = fsl_cx_add_db_handles(f, args->self, se); - if(!rc) *rv = args->self; - } - return rc; -} - -static int cb_fsl_login_cookie_name( cwal_callback_args const * args, - cwal_value ** rv){ - int rc; - char * name; - THIS_F; - name = fsl_repo_login_cookie_name(f); - if(name){ - assert( name[22] ); - assert( !name[23] ); - *rv = cwal_string_value(cwal_new_stringf(args->engine, "%.23s", name)) - /* z-string is only legal if libfossil and s2 use the same - allocator, so we'll be pedantically correct here and - copy it. String interning might make this a no-op. - */; - rc = *rv ? 0 : CWAL_RC_OOM; - fsl_free(name); - }else{ - *rv = cwal_value_undefined(); - rc = 0; - /*cb_toss(args, CWAL_RC_ERROR, - "Unexpected FossilContext state: " - "no login cookie name.");*/ - } - return rc; -} - -/* -** Constructor function for fsl_cx instances. -** -** Script-side usage: -** -** var f = ThisFunc(object{...}) -** -** -** The parameter object is optional. The only supported option at the -** moment is the traceSql boolean, which enables or disabled SQL -** tracing output. -** -** On success it returns (via *rv) a new cwal_native which is bound to -** a new fsl_cx instance. The fsl_cx will be initialized such that -** fsl_output() and friends will be redirected through cwal_output(), -** to allow fsl_output()-generated output to take advantage of -** th1ish's output buffering and whatnot. -*/ -static int cb_fsl_cx_ctor( cwal_callback_args const * args, - cwal_value **rv ){ - fsl_cx * f = NULL; - cwal_value * v; - int rc; - fsl_cx_init_opt init = fsl_cx_init_opt_empty; - s2_engine * se = s2_engine_from_args(args); - assert(se); - - init.output.out = fsl_output_f_cwal_output; - init.output.flush = fsl_flush_f_cwal_out; - init.output.state.state = args->engine; - if(args->argc && cwal_props_can(args->argv[0])){ - init.config.traceSql = - cwal_value_get_bool(cwal_prop_get(args->argv[0], - "traceSql", 8)); - } - rc = fsl_cx_init( &f, &init ); - if(rc){ - fsl_cx_finalize( f ); - return cb_toss(args, FSL_RC_ERROR, - "Fossil context initialization failed " - "with code #%d (%s).", rc, - fsl_rc_cstr(rc)); - } - v = cwal_new_native_value(args->engine, f, - cwal_finalizer_f_fsl_cx, - FSL_TYPEID(fsl_cx)); - if(!v){ - fsl_cx_finalize( f ); - return CWAL_RC_OOM; - } - cwal_value_prototype_set( v, fsl_cx_prototype(se) ); - fsl_cx_db_prop( f, v, se, 1 ) /* initialize this->db */; - *rv = v; - return 0; -} - -/** - If parent contains a Db property with the given name, that property - is disconnected from its native and removed from parent, but its Db - finalizer is not called (ownership lies elsewhere for the fsl_db - parts of properties). The db's Fossil context pointer (if any) is - also set to 0. - -*/ -static void cb_fsl_clear_handles(cwal_value * parent, - char const * dbName){ - cwal_engine * e = cwal_value_engine(parent); - cwal_size_t const dbNameLen = cwal_strlen(dbName); - cwal_value * v = e ? cwal_prop_get(parent, dbName, dbNameLen) : NULL; - assert(e); - if(v){ - cwal_native * nat = cwal_value_native_part(e, v, FSL_TYPEID(fsl_db)); - fsl_db * db = nat ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL; - assert(nat ? !!db : 1); - if(db){ - db->f = 0 /* avoid holding a stale pointer. */; - cwal_native_clear(nat, 0); - cwal_prop_unset(parent, dbName, dbNameLen); - } - } -} - -static int cb_fsl_cx_close( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_value * dbNs; - THIS_F; - /* It's important that we clear the Value/Native bindings - to all of the context's databases because clients can do this: - - var db = f.db.repo; - f.close();' - db.something(...) - - Which, if we're not careful here, can lead to them having - not only a stale fsl_db handle, but one which points back - to a stale fsl_cx pointer. - - Thank you, valgrind. - */ - dbNs = fsl_cx_db_prop(f, natV, se, 0); - if(dbNs){ - cb_fsl_clear_handles(dbNs, "repo"); - cb_fsl_clear_handles(dbNs, "checkout"); - cb_fsl_clear_handles(dbNs, "config"); - cb_fsl_clear_handles(natV, "db"); - } - if(fsl_cx_db_config(f)){ - fsl_config_close(f); - } - if(fsl_cx_db_ckout(f)){ - fsl_ckout_close(f); - /* also closes its repo db */ - }else if(fsl_cx_db_repo(f)){ - fsl_repo_close(f); - } - *rv = args->self; - return 0; -} - - -static int cb_fsl_cx_finalize( cwal_callback_args const * args, - cwal_value **rv ){ - THIS_F; - if(fsl_cx_db_prop(f, args->self, se, 0)){ - cb_fsl_cx_close(args, rv); - } - cwal_native_clear( nat, 1 ); - return 0; -} - -static int cb_fsl_cx_user_name( cwal_callback_args const * args, - cwal_value **rv ){ - char const * uname; - THIS_F; - if(! (uname = fsl_cx_user_get(f)) ){ - uname = fsl_guess_user_name(); - } - *rv = (uname && *uname) - ? cwal_new_string_value(args->engine, uname, cwal_strlen(uname)) - : cwal_value_undefined(); - return *rv ? 0 : CWAL_RC_OOM; -} - - - -static int cb_fsl_cx_resolve_sym( cwal_callback_args const * args, - cwal_value **rv, - char toUuid ){ - char * uuid = NULL; - char const * sym; - int rc; - cwal_int_t argRid = 0; - fsl_id_t rid = 0; - THIS_F; - sym = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if((!sym || !*sym) && args->argc){ - argRid = cwal_value_get_integer(args->argv[0]); - } - if(argRid<=0 && (!sym || !*sym)){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting a positive integer or non-empty " - "string argument."); - } - rc = toUuid - ? fsl_sym_to_uuid( f, sym, FSL_SATYPE_ANY, &uuid, &rid ) - : fsl_sym_to_rid( f, sym, FSL_SATYPE_ANY, &rid ); - if(rc){ - /* Undecided: throw or return undefined if no entry found? */ -#if 1 - if(FSL_RC_NOT_FOUND==rc){ - *rv = cwal_value_undefined(); - rc = 0; - }else{ - rc = cb_toss_fsl(args, f); - } -#else - rc = cb_toss_fsl(args, f); -#endif - }else{ - if(toUuid){ - assert(uuid); - *rv = cwal_new_string_value(args->engine, uuid, cwal_strlen(uuid)); - fsl_free(uuid); - if(!*rv){ - rc = CWAL_RC_OOM; - } - }else{ - assert(!uuid); - assert(rid>0); - *rv = cwal_new_integer(args->engine, (cwal_int_t)rid); - if(!*rv) rc = CWAL_RC_OOM; - } - } - return rc; -} - - -static int cb_fsl_cx_sym2uuid( cwal_callback_args const * args, - cwal_value **rv ){ - return cb_fsl_cx_resolve_sym(args, rv, 1); -} - -static int cb_fsl_cx_sym2rid( cwal_callback_args const * args, - cwal_value **rv ){ - return cb_fsl_cx_resolve_sym(args, rv, 0); -} - -static int cb_fsl_cx_content_get( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - fsl_id_t rid = 0; - char const * sym = NULL; - fsl_buffer fbuf = fsl_buffer_empty; - THIS_F; - if(!args->argc) goto usage; - else{ - cwal_value * arg = args->argv[0]; - if(cwal_value_is_integer(arg)){ - rid = (fsl_id_t)cwal_value_get_integer(arg); - if(rid<=0) goto usage; - }else{ - sym = cwal_value_get_cstr(arg, NULL); - if(!sym || !*sym) goto usage; - } - } - rc = (rid>0) - ? fsl_content_get(f, rid, &fbuf) - : fsl_content_get_sym(f, sym, &fbuf); - if(rc){ - assert(f->error.code); - rc = cb_toss_fsl(args, f); - }else{ - cwal_buffer * cbuf = cwal_new_buffer(args->engine, 0); - if(!cbuf) rc = CWAL_RC_OOM; - else{ - cwal_value * cbufV = cwal_buffer_value(cbuf); - cwal_value_ref(cbufV); - rc = fsl_buffer_to_cwal_buffer(args->engine, &fbuf, cbuf); - if(rc){ - cwal_value_unref(cbufV); - }else{ - cwal_value_unhand(cbufV); - *rv = cbufV; - } - } - } - fsl_buffer_clear(&fbuf); - return rc; - usage: - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one symbolic name (string) or " - "RID (positive integer) argument."); -} - -typedef struct card_visitor_F_to_object { - cwal_engine * e; - cwal_array * dest; -} card_visitor_F_to_object; - -/** - fsl_card_F_visitor_f() impl which converts fc to a cwal Object - representation and appends it to - ((card_visitor_F_to_object*)state)->dest. -*/ -static int fsl_card_F_visitor_fc_to_object(fsl_card_F const * fc, - void * state){ - card_visitor_F_to_object * vs = (card_visitor_F_to_object*)state; - int rc; - cwal_value * ov = cwal_new_object_value(vs->e); - char const * typeLabel = NULL; - if(!ov) return CWAL_RC_OOM; - cwal_value_ref(ov); - rc = cwal_array_append(vs->dest, ov); - cwal_value_unref(ov); - if(rc){ - return rc; - } -#define vset(K,KL,V) cwal_prop_set(ov, K, KL, V) - vset("name", 4, cwal_new_string_value(vs->e, fc->name, - cwal_strlen(fc->name))); - if(fc->priorName){ - vset("priorName", 9, - cwal_new_string_value(vs->e, fc->priorName, - cwal_strlen(fc->priorName))); - } - if(fc->uuid){ - vset("uuid", 4, cwal_new_string_value(vs->e, fc->uuid, - cwal_strlen(fc->uuid))); - } - switch(fc->perm){ - case FSL_FILE_PERM_EXE: typeLabel = "x"; break; - case FSL_FILE_PERM_LINK: typeLabel = "l"; break; - default: typeLabel = "f"; - } - vset("perm", 4, cwal_new_string_value(vs->e, typeLabel, 1) - /* good case for cwal's string interning! */); -#undef vset - return rc; -} - -/** - Converts a fsl_deck to an Object representation, assigning *rv - to the new object (new, with no live references). - - Must return a CWAL_RC value, not FSL_RC! -*/ -static int s2_deck_to_object( cwal_engine * e, fsl_deck * mf, - char includeBaselineObj, - cwal_value ** rv ){ - int rc = 0; - cwal_value * ov = NULL; - cwal_value * card = NULL; - cwal_array * ar = NULL; - cwal_value * av = NULL; - cwal_size_t i; - assert(e); - assert(mf); - assert(mf->uuid); - assert(mf->rid); - rc = fsl_deck_F_rewind(mf); - if(rc) goto end; - ov = cwal_new_object_value(e); - if(!ov) return CWAL_RC_OOM; - cwal_value_ref(ov); -#define vset2(OBJ,K,V) cwal_prop_set(OBJ, K, cwal_strlen(K), V) -#define dset(K,V) vset2(ov,K,V) -#define cset(K,V) vset2(card,K,V) -#define vornull(VP,V) ((VP) ? (V) : cwal_value_null()) -#define strval2(CSTR,LEN) vornull((CSTR), cwal_new_string_value(e, (char const *)(CSTR), (cwal_size_t)(LEN))) -#define strval(CSTR) strval2((CSTR), cwal_strlen(CSTR)) - dset("type", cwal_new_integer(e, mf->type)); - dset("rid", cwal_new_integer(e, (cwal_int_t)mf->rid)); - dset("uuid", strval2(mf->uuid, cwal_strlen(mf->uuid))); - - if(mf->A.src){ - card = cwal_new_object_value(e); - dset("A", card); - cset("name", strval(mf->A.name)); - cset("tgt", strval(mf->A.tgt)); - cset("uuid", strval2(mf->A.src, cwal_strlen(mf->A.src))); - } - if(mf->B.uuid){ - card = cwal_new_object_value(e); - dset("B", card); - cset("uuid", strval2(mf->B.uuid,cwal_strlen(mf->B.uuid))); - if(includeBaselineObj){ - rc = fsl_deck_F_rewind(mf) /* load baseline if needed */; - if(!rc){ - cwal_value * v = NULL; - assert(mf->B.baseline); - rc = s2_deck_to_object( e, mf->B.baseline, 0, &v ); - if(v){ - cset("baseline", v); - } - else{ - assert(rc); - goto end; - } - } - } - } - if(mf->C){ - dset("C", strval(mf->C)); - } - if(mf->D > 0){ - dset("D", cwal_new_double(e, mf->D)); - } - if(mf->E.julian > 0){ - card = cwal_new_object_value(e); - dset("E", card); - cset("julian", cwal_new_double(e, mf->E.julian)); - cset("uuid", strval2(mf->E.uuid, cwal_strlen(mf->E.uuid))); - } - if(mf->F.used || mf->B.uuid){ - card_visitor_F_to_object vstate; - cwal_size_t const reserveSize = mf->F.used - + (mf->B.baseline ? mf->B.baseline->F.used : 0); - assert(fsl_card_is_legal(mf->type, 'F')); - ar = cwal_new_array(e); - card = av = cwal_array_value(ar); - dset("F", card); - cwal_array_reserve(ar, reserveSize); - vstate.e = e; - vstate.dest = ar; - rc = fsl_deck_F_foreach(mf, fsl_card_F_visitor_fc_to_object, - &vstate); - if(rc) goto end; - }/* end of F-card */ - if(mf->G){ - dset("G", strval2(mf->G, cwal_strlen(mf->G))); - } - if(mf->H){ - dset("H", strval(mf->H)); - } - if(mf->I){ - dset("I", strval2(mf->I, cwal_strlen(mf->I))); - } -#define append(V) cwal_array_append(ar, (V)) - if(mf->J.used){ - ar = cwal_new_array(e); - card = av = cwal_array_value(ar); - dset("J", card); - append(strval2("TODO",4)); - for(i = 0; i < mf->J.used; ++i ){ - /* TODO */ - } - } - if(mf->K){ - dset("K", strval2(mf->K, cwal_strlen(mf->K))); - } - if(mf->L){ - dset("L", strval(mf->L)); - } - - if(mf->M.used){ - ar = cwal_new_array(e); - card = av = cwal_array_value(ar); - dset("M", card); - for(i = 0; i < mf->M.used; ++i ){ - fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->M.list[i]; - assert(uuid); - append(strval2(uuid,cwal_strlen(uuid))); - } - } - - if(mf->P.used){ - ar = cwal_new_array(e); - card = av = cwal_array_value(ar); - dset("P", card); - for(i = 0; i < mf->P.used; ++i ){ - fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->P.list[i]; - assert(uuid); - append(strval2(uuid,cwal_strlen(uuid))); - } - } - - if(mf->Q.used){ - ar = cwal_new_array(e); - card = av = cwal_array_value(ar); - dset("Q", card); - append(strval2("TODO",4)); - for(i = 0; i < mf->P.used; ++i ){ - fsl_card_Q const * qc = (fsl_card_Q const *)mf->Q.list[i]; - assert(qc); - /* TODO */ - } - } - - if(mf->U){ - dset("U", strval(mf->U)); - } - - if(mf->W.used){ - dset("W", vornull(mf->W.used, strval2(mf->W.mem,mf->W.used))); - }else if(0 < fsl_card_is_legal(mf->type, 'W') /* required card */){ - /* corner-case: empty wiki page */ - dset("W", strval2("",0)); - } - -#undef append -#undef strval -#undef strval2 -#undef vornull -#undef cset -#undef dset -#undef vset2 - end: - if(!rc && rv){ - assert(ov); - cwal_value_unhand(ov); - *rv = ov; - } - else if(ov){ - cwal_value_unref(ov); - } - return rc; -} - -/** - Script usage: - - const deck = Fossil.loadManifest( - version [, includeBaselineObject] - - The deck object's struct matches fsl_deck pretty - closely. -*/ -static int cb_fsl_cx_deck_load( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - fsl_deck mf = fsl_deck_empty; - fsl_id_t rid = 0; - char const * sym = NULL; - fsl_satype_e mfType = FSL_SATYPE_ANY; - char includeBaselineObj; - THIS_F; - if(!args->argc) goto usage; - else if(cwal_value_is_number(args->argv[0])){ - rid = (fsl_id_t)cwal_value_get_integer(args->argv[0]); - if(rid<=0) goto usage; - }else{ - sym = cwal_value_get_cstr(args->argv[0], NULL); - if(!sym || !*sym) goto usage; - } - includeBaselineObj = (args->argc>1) - ? cwal_value_get_bool(args->argv[1]) - : 0; - /* TODO: script mapping for fsl_satype_e values for 3nd arg. - */ - rc = sym - ? fsl_deck_load_sym(f, &mf, sym, mfType) - : fsl_deck_load_rid(f, &mf, rid, mfType) - ; - if(rc){ - if(f->error.code) rc = cb_toss_fsl(args, f); - else{ - rc = sym - ? cb_toss(args, rc, "Loading manifest '%s' failed with code %d/%s", - sym, rc, fsl_rc_cstr(rc)) - : cb_toss(args, rc, "Loading manifest RID %d failed with code %d/%s", - (int)rid, rc, fsl_rc_cstr(rc)); - } - goto end; - } - - rc = s2_deck_to_object(args->engine, &mf, - includeBaselineObj, rv); - end: - fsl_deck_finalize(&mf); - return rc; - usage: - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one symbolic name (string) or " - "RID (positive integer) argument."); -} - -/** - Script usage: - - var x = thisFunc(fromVersion, toVersion [, Object options]) - - Options: - - integer contextLines - - integer sbsWidth - - bool html - - bool text - - bool inline - - bool invert - - Result is a Buffer containing the generated diff. -*/ -static int cb_fsl_cx_adiff( cwal_callback_args const * args, - cwal_value **rv ){ - char const * sym; - fsl_id_t rid1, rid2; - cwal_value * arg; - int diffFlags = 0; - int rc; - short contextLines = 3; - short sbsWidth = 0; - fsl_buffer c1 = fsl_buffer_empty; - fsl_buffer c2 = fsl_buffer_empty; - fsl_buffer delta = fsl_buffer_empty; - THIS_F; - if(args->argc<2){ - misuse: - return cb_toss(args, FSL_RC_MISUSE, "Expecting two artifact ID arguments."); - } - /* The v1 arg... */ - arg = args->argv[0]; - if(cwal_value_is_integer(arg)){ - rid1 = (fsl_id_t)cwal_value_get_integer(arg); - }else if(!(sym = cwal_value_get_cstr(arg,NULL))){ - goto misuse; - }else{ - rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid1); - if(rc) return cb_toss_fsl(args, f); - } - - /* The v2 arg... */ - arg = args->argv[1]; - if(cwal_value_is_integer(arg)){ - rid2 = (fsl_id_t)cwal_value_get_integer(arg); - }else if(!(sym = cwal_value_get_cstr(arg,NULL))){ - goto misuse; - }else{ - rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid2); - if(rc) return cb_toss_fsl(args, f); - } - - assert(rid1>0); - assert(rid2>0); - - rc = fsl_content_get(f, rid1, &c1); - if(!rc) rc = fsl_content_get(f, rid2, &c2); - if(rc){ - rc = cb_toss_fsl(args, f); - goto end; - } - diffFlags = FSL_DIFF_LINENO | FSL_DIFF_SIDEBYSIDE; - if((args->argc>2) && cwal_props_can((arg = args->argv[2]))){ - do{ - /* Collect diff flags from object */ - cwal_value * v; - v = cwal_prop_get(arg, "contextLines", 12); - if(v) contextLines = (short)cwal_value_get_integer(v); - v = cwal_prop_get(arg, "html", 4); - if(v && cwal_value_get_bool(v)){ - diffFlags |= FSL_DIFF_HTML; - } - v = cwal_prop_get(arg, "inline", 6); - if(v && cwal_value_get_bool(v)){ - diffFlags &= ~FSL_DIFF_SIDEBYSIDE; - sbsWidth = 0; - } - v = cwal_prop_get(arg, "invert", 6); - if(v && cwal_value_get_bool(v)){ - diffFlags |= FSL_DIFF_INVERT; - } - v = cwal_prop_get(arg, "sbsWidth", 8); - if(v){ - sbsWidth = (short)cwal_value_get_integer(v); - } - v = cwal_prop_get(arg, "text", 4); - if(v && cwal_value_get_bool(v)){ - diffFlags &= ~FSL_DIFF_HTML; - } - } while(0); - } - - rc = fsl_diff_text(&c1, &c2, fsl_output_f_buffer, &delta, - contextLines, sbsWidth, diffFlags); - if(rc){ - if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM; - else rc = cb_toss(args, rc, "Error %s while creating diff.", - fsl_rc_cstr(rc)); - }else{ - cwal_value * v = cwal_new_buffer_value(args->engine, 0); - cwal_buffer * bv = v ? cwal_value_buffer_part(args->engine, v) : NULL; - if(!v){ - rc = CWAL_RC_OOM; - goto end; - }else{ - cwal_value_ref(v); - assert(!bv->mem); - rc = fsl_buffer_to_cwal_buffer(args->engine, &delta, bv); - if(rc){ - cwal_value_unref(v); - }else{ - assert(bv->used == delta.used); - cwal_value_unhand(v); - *rv = v; - } - } - } - end: - fsl_buffer_clear(&c1); - fsl_buffer_clear(&c2); - fsl_buffer_clear(&delta); - return rc; -} - - -cwal_value * fsl_stmt_prototype( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - cwal_value * v; - char const * pKey = "Fossil.Stmt"; - proto = s2_prototype_stashed(se, pKey); - if(proto) return proto; - proto = cwal_new_object_value(se->e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash( se, pKey, proto ); - if(rc) goto end; - -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end;} (void)0 - - v = cwal_new_string_value(se->e, "Stmt", 4); - SET("__typename"); - - { - s2_func_def const funcs[] = { - S2_FUNC2("stepObject", cb_fsl_stmt_step_object), - S2_FUNC2("stepArray", cb_fsl_stmt_step_array), - S2_FUNC2("step", cb_fsl_stmt_step), - S2_FUNC2("rowToObject", cb_fsl_stmt_row_to_object), - S2_FUNC2("rowToArray", cb_fsl_stmt_row_to_array), - S2_FUNC2("reset", cb_fsl_stmt_reset), - S2_FUNC2("get", cb_fsl_stmt_get), - S2_FUNC2("finalize", cb_fsl_stmt_finalize), - S2_FUNC2("bind", cb_fsl_stmt_bind), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - } - { - /* Stmt.each() impl. */ - char const * src = - "proc(_){" - "affirm typeinfo(iscallable _) && typeinfo(iscallable _.call); " - "while(this.step()) false === _.call(this) && break;" - "return this" - "}"; - int const srcLen = (int)cwal_strlen(src); - rc = s2_set_from_script(se, src, srcLen, proto, "each", 4); - } - -#undef SET -#undef VCHECK - end: - return rc ? NULL : proto; -} - -cwal_value * fsl_db_prototype( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - cwal_value * v; - char const * pKey = "Fossil.Db"; - cwal_engine * e; - proto = s2_prototype_stashed(se, pKey); - if(proto) return proto; - e = s2_engine_engine(se); - /* cwal_value * fv; */ - assert(se && e); - proto = cwal_new_object_value(e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash( se, pKey, proto ); - if(rc) goto end; -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end; } (void)0 - - v = cwal_new_string_value(e, "Db", 2); - SET("__typename"); - - { - s2_func_def const funcs[] = { - S2_FUNC2("transactionState", cb_fsl_db_trans_state), - S2_FUNC2("transaction", cb_fsl_db_trans), - S2_FUNC2("selectValues", cb_fsl_db_select_values), - S2_FUNC2("selectValue", cb_fsl_db_select_value), - S2_FUNC2("rollback", cb_fsl_db_trans_rollback), - S2_FUNC2("prepare", cb_fsl_db_prepare), - S2_FUNC2("open", cb_fsl_db_ctor), - S2_FUNC2("lastInsertId", cb_fsl_db_last_insert_id), - S2_FUNC2("getName", cb_fsl_db_name), - S2_FUNC2("getFilename", cb_fsl_db_filename), - S2_FUNC2("execMulti", cb_fsl_db_exec_multi), - S2_FUNC2("exec", cb_fsl_db_exec), - S2_FUNC2("each", cb_fsl_db_each), - S2_FUNC2("commit", cb_fsl_db_trans_commit), - S2_FUNC2("close", cb_fsl_db_finalize), - S2_FUNC2("begin", cb_fsl_db_trans_begin), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(rc) goto end; - } - v = cwal_prop_get(proto, "open", 4); - assert(v && "We just installed this!"); - rc = s2_ctor_method_set(se, proto, cwal_value_get_function(v)); - if(rc) goto end; - -#if 0 - /* TODO: port in stepTuple() from the main sqlite3 s2 mod - and then add these... */ - { - /* selectRow(sql,bind,asTuple) impl. */ - char const * src = - "proc(s,b,t){"/*sql, bind, asTuple*/ - "return this.prepare(s).bind(b)" - "[t?'stepTuple':'stepObject']()" - /* return will indirectly finalize() the anonymous stmt. */ - "}"; - rc = s2_set_from_script(se, src, (int)cwal_strlen(src), - proto, "selectRow", 9); - if(rc) goto end; - /* selectRows(sql,bind,asTuple) impl: */ - src = "proc(s,b,t){"/*sql, bind, asTuple*/ - "const S = this.prepare(s).bind(b), m = t?'stepTuple':'stepObject', rc=[];" - "var v; while(v=S[m]())rc[]=v; S.finalize();" - "return rc;" - "}"; - rc = s2_set_from_script(se, src, (int)cwal_strlen(src), - proto, "selectRows", 10); - } -#endif - - v = fsl_stmt_prototype(se); - VCHECK; - if( (rc = cwal_prop_set_with_flags( proto, "Stmt", 4, v, - CWAL_VAR_F_CONST)) ){ - goto end; - } - -#undef SET -#undef VCHECK - end: - return rc ? NULL : proto; -} - -cwal_value * fsl_cx_prototype( s2_engine * se ){ - int rc = 0; - cwal_value * proto; - cwal_value * v; - char const * pKey = "Fossil.Context"; - cwal_engine * e; - proto = s2_prototype_stashed(se, pKey); - if(proto) return proto; - e = s2_engine_engine(se); - assert(se && e); - proto = cwal_new_object_value(e); - if(!proto){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash( se, pKey, proto ); - if(rc) goto end; -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end; } (void)0 - - v = cwal_new_string_value(e, "Context", 7); - SET("__typename"); - { - s2_func_def const funcs[] = { - S2_FUNC2("symToUuid", cb_fsl_cx_sym2uuid), - S2_FUNC2("symToRid", cb_fsl_cx_sym2rid), - S2_FUNC2("openRepo", cb_fsl_repo_open), - S2_FUNC2("openDb", cb_fsl_db_ctor), - S2_FUNC2("openConfig", cb_fsl_config_open), - S2_FUNC2("openCheckout", cb_fsl_ckout_open_dir), - S2_FUNC2("loginCookieName", cb_fsl_login_cookie_name), - S2_FUNC2("loadManifest",cb_fsl_cx_deck_load), - S2_FUNC2("loadBlob", cb_fsl_cx_content_get), - S2_FUNC2("getUserName", cb_fsl_cx_user_name), - S2_FUNC2("finalize", cb_fsl_cx_finalize), - /* S2_FUNC2("closeConfig", cb_fsl_config_close), */ - S2_FUNC2("close", cb_fsl_cx_close), - S2_FUNC2("artifactDiff", cb_fsl_cx_adiff), - s2_func_def_empty_m - }; - rc = s2_install_functions(se, proto, funcs, 0); - if(rc) goto end; - } - rc = s2_ctor_callback_set(se, proto, cb_fsl_cx_ctor); - -#undef SET -#undef VCHECK - end: - return rc ? NULL : proto; -} - - -/** - Script usage: - - - const m = globMatches("*.*", "some.string") - - Or: - - const m = "*.*".globMatches(someString) - - (The second form is not installed by default!) - - */ -static int cb_strglob( cwal_callback_args const * args, - cwal_value **rv ){ - int rc = 0; - char const * glob = - (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL; - char const * str = - (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL; - char const * sSelf = cwal_value_get_cstr(args->self, NULL); - if(sSelf){ - str = glob; - glob = sSelf; - } - if(!glob){ - rc = cb_toss(args, FSL_RC_MISUSE, - "Expecting (glob,string) arguments " - "OR stringInstance.thisFunc(otherString) " - "usage."); - } - else if(!str){ - *rv = cwal_value_false(); - }else{ - *rv = fsl_str_glob(glob, str) - ? cwal_value_true() : cwal_value_false(); - } - return rc; -} - -static int cb_is_uuid( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_size_t slen = 0; - char const * str = - (args->argc>0) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL; - *rv = (str && fsl_is_uuid(str)) - ? cwal_value_true() - : cwal_value_false(); - return 0; -} - -static fsl_timer_state RunTimer = fsl_timer_state_empty_m; - -/** - Retuns the number of microseconds of _CPU_ time (not wall clock - time) used since s2_shell_extend() was run. -*/ -static int cb_run_timer_fetch( cwal_callback_args const * args, - cwal_value **rv ){ - uint64_t t = fsl_timer_fetch(&RunTimer); - *rv = cwal_new_integer(args->engine, (cwal_int_t)t); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int cb_fsl_delta_create( cwal_callback_args const * args, - cwal_value **rv ){ - char const * s1 = NULL; - char const * s2 = NULL; - cwal_size_t len1, len2; - fsl_size_t outLen = 0; - cwal_buffer * cb; - cwal_value * cbV = 0; - int rc; - if(args->argc>1){ - s1 = cwal_value_get_cstr(args->argv[0], &len1); - s2 = cwal_value_get_cstr(args->argv[1], &len2); - } - if(!s1 || !s2){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting two non-empty string/buffer " - "arguments."); - } - cb = cwal_new_buffer(args->engine, len2+61); - if(!cb) return CWAL_RC_OOM; - cbV = cwal_buffer_value(cb); - cwal_value_ref(cbV); - assert(cb->capacity > (len2+60)); - rc = fsl_delta_create( (unsigned char const *)s1, (fsl_size_t)len1, - (unsigned char const *)s2, (fsl_size_t)len2, - cb->mem, &outLen); -#if 0 - if(!rc){ - /* Resize the buffer to fit. */ - rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)outLen); - } -#endif - if(rc) cwal_value_unref(cbV); - else{ - cwal_value_unhand(cbV); - *rv = cbV; - } - return rc; -} - -static int cb_fsl_delta_applied_len( cwal_callback_args const * args, - cwal_value **rv ){ - char const * src; - cwal_size_t srcLen; - fsl_size_t appliedLen = 0; - int rc; - src = (args->argc>0) - ? cwal_value_get_cstr(args->argv[0], &srcLen) - : NULL; - if(!src){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one delta string " - "argument."); - } - rc = fsl_delta_applied_size((unsigned char const *)src, - (fsl_size_t)srcLen, &appliedLen); - if(rc){ - return cb_toss(args, rc, - "Input does not appear to be a " - "delta. Error #%d (%s).", - rc, fsl_rc_cstr(rc)); - }else{ - *rv = cwal_new_integer(args->engine, - (cwal_int_t)appliedLen); - return *rv ? 0 : CWAL_RC_OOM; - } -} - -static int cb_fsl_delta_apply( cwal_callback_args const * args, - cwal_value **rv ){ - char const * s1 = NULL; - char const * s2 = NULL; - char const * src; - char const * delta; - cwal_size_t len1, len2, srcLen, dLen; - fsl_size_t appliedLen = 0; - cwal_buffer * cb; - cwal_value * cbV; - int rc; - if(args->argc>1){ - s1 = cwal_value_get_cstr(args->argv[0], &len1); - s2 = cwal_value_get_cstr(args->argv[1], &len2); - } - if(!s1 || !s2){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting two non-empty string/buffer " - "arguments."); - } - rc = fsl_delta_applied_size((unsigned char const *)s2, - (fsl_size_t)len2, &appliedLen); - if(!rc){ - src = s1; - srcLen = len1; - delta = s2; - dLen = len2; - }else{ /* Check if the user perhaps swapped the args. */ - rc = fsl_delta_applied_size((unsigned char const *)s1, - (fsl_size_t)len1, &appliedLen); - if(rc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting a delta string/buffer as one " - "of the first two arguments."); - } - src = s2; - srcLen = len2; - delta = s1; - dLen = len1; - } - cb = cwal_new_buffer(args->engine, appliedLen+1); - if(!cb) return CWAL_RC_OOM; - cbV = cwal_buffer_value(cb); - cwal_value_ref(cbV); - assert(cb->capacity > appliedLen); - rc = fsl_delta_apply( (unsigned char const *)src, (fsl_size_t)srcLen, - (unsigned char const *)delta, (fsl_size_t)dLen, - cb->mem); - if(!rc){ - assert(0==cb->mem[appliedLen]); -#if 0 - if(cb->capacity > (cb->used * 4 / 3)){ - rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)appliedLen); - } -#endif - }else{ - rc = cb_toss(args, rc, - "Application of delta failed with " - "code #%d (%s).", rc, - fsl_rc_cstr(rc)); - } - if(rc) cwal_value_unref(cbV); - else { - cwal_value_unhand(cbV); - *rv = cbV; - } - return rc; -} - -static int fsl_add_delta_funcs( cwal_engine * e, cwal_value * ns ){ - cwal_value * func; - cwal_value * v; - int rc; - func = cwal_new_object_value(e); - if(!func) return CWAL_RC_OOM; - cwal_value_ref(func); - rc = cwal_prop_set(ns, "delta", 5, func); - cwal_value_unref(func); - if(rc){ - return rc; - } -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc){goto end;}(void)0 -#define FUNC(NAME,FP) \ - v = cwal_new_function_value( e, FP, 0, 0, 0 ); \ - SET(NAME) - FUNC("appliedLength", cb_fsl_delta_applied_len); - FUNC("apply", cb_fsl_delta_apply); - FUNC("create", cb_fsl_delta_create); -#undef SET -#undef FUNC -#undef VCHECK - end: - return rc; -} - -/** - Script usage: - - string canonicalName(string filename [, bool keepSlash = false]) - string canonicalName(string rootDirName, string filename[, bool keepSlash = false]) -*/ -static int cb_fs_file_canonical( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - char const * fn = 0; - char const * root = 0; - fsl_buffer buf = fsl_buffer_empty; - char slash = 0; - if(1==args->argc){ - fn = cwal_value_get_cstr(args->argv[0], 0); - }else if(args->argc>1){ - if(cwal_value_is_bool(args->argv[1])){ - fn = cwal_value_get_cstr(args->argv[0], 0); - slash = cwal_value_get_bool(args->argv[1]) ? 1 : 0; - }else{ - root = cwal_value_get_cstr(args->argv[0], 0); - fn = cwal_value_get_cstr(args->argv[1], 0); - if(args->argc>2) slash = cwal_value_get_bool(args->argv[2]); - } - } - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting a non-empty string " - "argument."); - rc = fsl_file_canonical_name2( root, fn, &buf, slash ); - if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM; - else if(!rc){ - *rv = cwal_new_string_value(args->engine, - buf.used ? (char const *)buf.mem : 0, - (cwal_size_t)buf.used); - if(!*rv){ - rc = CWAL_RC_OOM; - } - } - fsl_buffer_clear(&buf); - return rc; -} - -/** - Script binding to fsl_file_dirpart(): - - var x = thisFunc(path, leaveSlash=true) - */ -static int cb_fs_file_dirpart( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - char const * fn; - fsl_buffer buf = fsl_buffer_empty; - cwal_size_t fnLen = 0; - char leaveSlash; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], &fnLen) - : NULL; - if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - leaveSlash = args->argc>1 - ? cwal_value_get_bool(args->argv[1]) - : 1; - - rc = fsl_file_dirpart(fn, (fsl_size_t)fnLen, &buf, leaveSlash); - if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM; - else if(!rc){ - *rv = cwal_new_string_value(args->engine, - buf.used ? (char const *)buf.mem : 0, - buf.used); - if(!*rv){ - rc = CWAL_RC_OOM; - } - } - fsl_buffer_clear(&buf); - return rc; -} - -/** - Script usage: - - const size = Fossil.file.size(filename) -*/ -static int cb_fs_file_size( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - fsl_size_t sz; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - sz = fsl_file_size(fn); - if((fsl_size_t)-1 == sz){ - return cb_toss(args, FSL_RC_IO, - "Could not stat file: %s", fn); - } - *rv = (sz > (int64_t)CWAL_INT_T_MAX) - ? cwal_new_double(args->engine, (cwal_double_t)sz) - : cwal_new_integer(args->engine, (cwal_int_t)sz) - ; - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - Script usage: - - const tm = Fossil.file.unlink(filename) -*/ -static int cb_fs_file_unlink( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - int rc; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - rc = fsl_file_unlink( fn ); - if(!rc) *rv = args->self; - return rc - ? cb_toss(args, rc, "Got %s error while " - "trying to remove file: %s", - fsl_rc_cstr(rc), fn) - : 0; -} - -static int cb_fs_file_chdir( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - int rc; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - rc = fsl_chdir( fn ); - if(!rc) *rv = args->self; - return rc - ? cb_toss(args, rc, "Got %s error while " - "trying to remove file: %s", - fsl_rc_cstr(rc)) - : 0; -} - -static int cb_fs_file_cwd( cwal_callback_args const * args, - cwal_value **rv ){ - int rc; - enum { BufSize = 512 * 5 }; - fsl_size_t nLen = 0; - char const slash = args->argc ? cwal_value_get_bool(args->argv[0]) : 0; - char buf[BufSize] = {0}; - rc = fsl_getcwd(buf, BufSize, &nLen); - if(rc) return cb_toss(args, rc, "Got error %d (%s) while " - "trying to getcwd()", - rc, fsl_rc_cstr(rc)); - if(slash && nLen<(fsl_size_t)BufSize){ - buf[nLen++] = '/'; - } - *rv = cwal_new_string_value(args->engine, buf, (cwal_size_t)nLen); - return *rv ? 0 : CWAL_RC_OOM; -} - - -/** - Script usage: - - const tm = Fossil.file.mtime(filename) -*/ -static int cb_fs_file_mtime( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - fsl_time_t sz; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - sz = fsl_file_mtime(fn); - if(-1 == sz){ - return cb_toss(args, FSL_RC_IO, - "Could not stat() file: %s", fn); - } - *rv = cwal_new_integer(args->engine, (cwal_int_t)sz); - return *rv ? 0 : CWAL_RC_OOM; -} - -/** - Script usage: - - const bool = Fossil.file.isFile(filename) -*/ -static int cb_fs_file_isfile( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - *rv = fsl_is_file(fn) - ? cwal_value_true() - : cwal_value_false(); - return 0; -} - -/** - Script usage: - - const bool = Fossil.file.isDir(filename) -*/ -static int cb_fs_file_isdir( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - *rv = (fsl_dir_check(fn) > 0) - ? cwal_value_true() - : cwal_value_false(); - return 0; -} - - -/** - Script usage: - - bool Fossil.file.access(filename [, bool mustBeADir=false) -*/ -static int cb_fs_file_access( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - char checkWrite; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - checkWrite = (args->argc>1) - ? cwal_value_get_bool(args->argv[1]) - : 0; - - *rv = fsl_file_access( fn, checkWrite ? W_OK : F_OK ) - /*0==OK*/ - ? cwal_value_false() - : cwal_value_true(); - return 0; -} - - -/** - Streams a file's contents directly to the script engine's output - channel, without (unless the file is small) reading the whole file - into a buffer first. - - Script usage: - - Fossil.file.passthrough(filename) -*/ -static int cb_fs_file_passthrough( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - cwal_size_t len; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], &len) - : NULL; - if(!fn){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - }else if(0!=fsl_file_access(fn, 0)){ - return cb_toss(args, FSL_RC_NOT_FOUND, - "Cannot find file: %s", fn); - }else{ - FILE * fi = fsl_fopen(fn, "r"); - int rc; - if(!fi){ - rc = cb_toss(args, FSL_RC_IO, - "Could not open file for reading: %s", - fn); - }else{ - rc = fsl_stream( fsl_input_f_FILE, fi, - fsl_output_f_cwal_output, - args->engine ); - fsl_fclose(fi); - } - if(!rc) *rv = args->self; - return rc ? CWAL_RC_IO : 0; - } -} - -/** - Script binding to fsl_stat(). Returns undefined if - stat fails, else an object: - - { - name: args->argv[0], - mtime: unix epoch, - ctime: unix epoch, - size: in bytes, - type: 'file', 'dir', 'link', or 'unknown' - } - - TODO? Canonicalize the result name? -*/ -static int cb_fs_file_stat( cwal_callback_args const * args, - cwal_value **rv ){ - char const * fn; - fn = args->argc - ? cwal_value_get_cstr(args->argv[0], NULL) - : NULL; - if(!fn) return cb_toss(args, FSL_RC_MISUSE, - "Expecting non-empty string " - "argument."); - else{ - fsl_fstat fst = fsl_fstat_empty; - int rc; - cwal_engine * e = args->engine; - rc = fsl_stat( fn, &fst, 1 ); - if(rc){ - *rv = cwal_value_undefined(); - rc = 0; - }else{ - char const * typeName; - cwal_value * obj = cwal_new_object_value(e); - switch(fst.type){ - case FSL_FSTAT_TYPE_DIR: typeName = "dir"; break; - case FSL_FSTAT_TYPE_FILE: typeName = "file"; break; - case FSL_FSTAT_TYPE_LINK: typeName = "link"; break; - case FSL_FSTAT_TYPE_UNKNOWN: - default: - typeName = "unknown"; - break; - } - cwal_prop_set(obj, "type", 4, cwal_new_string_value(e, typeName, - cwal_strlen(typeName))); - cwal_prop_set(obj, "name", 4, args->argv[0]); - cwal_prop_set(obj, "size", 4, cwal_new_integer(e, (cwal_int_t)fst.size)); - cwal_prop_set(obj, "mtime", 5, cwal_new_integer(e, (cwal_int_t)fst.mtime)); - cwal_prop_set(obj, "ctime", 5, cwal_new_integer(e, (cwal_int_t)fst.ctime)); - *rv = obj; - /* See how little code it is if we ignore malloc errors? */ - } - return rc; - } -} - -static int fsl_add_file_funcs( cwal_engine * e, cwal_value * ns ){ - cwal_value * func; - cwal_value * v; - int rc; - func = cwal_new_object_value(e); - if(!func) return CWAL_RC_OOM; - cwal_value_ref(func); - rc = cwal_prop_set(ns, "file", 4, func); - cwal_value_unref(func); - if(rc){ - return rc; - } - -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end;}(void)0 -#define FUNC(NAME,FP) \ - v = cwal_new_function_value( e, FP, 0, 0, 0 ); \ - SET(NAME) - - FUNC("canonicalName", cb_fs_file_canonical); - FUNC("chdir", cb_fs_file_chdir); - FUNC("currentDir", cb_fs_file_cwd); - FUNC("dirPart", cb_fs_file_dirpart); - FUNC("isAccessible", cb_fs_file_access); - FUNC("isDir", cb_fs_file_isdir); - FUNC("isFile", cb_fs_file_isfile); - FUNC("mtime", cb_fs_file_mtime); - FUNC("passthrough", cb_fs_file_passthrough); - FUNC("size", cb_fs_file_size); - FUNC("stat", cb_fs_file_stat); - FUNC("unlink", cb_fs_file_unlink); - -#undef SET -#undef FUNC -#undef VCHECK - end: - - return rc; -} - -static int cb_time_now( cwal_callback_args const * args, - cwal_value **rv ){ - char asJulian = 0; - time_t now; - asJulian = args->argc && cwal_value_get_bool(args->argv[0]); - time(&now); - *rv = asJulian - ? cwal_new_double(args->engine, - fsl_unix_to_julian((fsl_time_t)now)) - : cwal_new_integer(args->engine, - (cwal_int_t)now) - ; - return *rv ? 0 : CWAL_RC_OOM; -} - - -static int cb_unix_to_julian( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_int_t tm; - if(!args->argc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one integer argument."); - } - tm = cwal_value_get_integer(args->argv[0]); - *rv = cwal_new_double(args->engine, - fsl_unix_to_julian( (fsl_time_t)tm )); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int cb_julian_to_unix( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_double_t tm; - if(!args->argc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one double argument."); - } - tm = cwal_value_get_double(args->argv[0]); - *rv = cwal_new_integer(args->engine, - fsl_julian_to_unix( (double)tm )); - return *rv ? 0 : CWAL_RC_OOM; -} - -static int cb_julian_to_iso8601( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_double_t tm; - char includeMs; - char buf[24]; - if(!args->argc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one double argument."); - } - tm = cwal_value_get_double(args->argv[0]); - includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0; - if(fsl_julian_to_iso8601(tm, buf, includeMs)){ - *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - return cb_toss(args, FSL_RC_MISUSE, - "Invalid Julian date value."); - } -} - -static int cb_julian_to_human( cwal_callback_args const * args, - cwal_value **rv ){ - cwal_double_t tm; - char includeMs; - char buf[24]; - if(!args->argc){ - return cb_toss(args, FSL_RC_MISUSE, - "Expecting one double argument."); - } - includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0; - tm = cwal_value_get_double(args->argv[0]); - if(fsl_julian_to_iso8601(tm, buf, includeMs)){ - assert('T' == buf[10]); - buf[10] = ' '; - *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19); - return *rv ? 0 : CWAL_RC_OOM; - }else{ - return cb_toss(args, FSL_RC_MISUSE, - "Invalid Julian data value."); - } -} - -static int fsl_add_time_funcs( cwal_engine * e, cwal_value * ns ){ - cwal_value * func; - cwal_value * v; - int rc; - func = cwal_new_object_value(e); - if(!func) return CWAL_RC_OOM; - cwal_value_ref(func); - rc = cwal_prop_set(ns, "time", 4, func); - cwal_value_unref(func); - if(rc){ - return rc; - } - -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end;} (void)0 -#define FUNC(NAME,FP) \ - v = cwal_new_function_value( e, FP, 0, 0, 0 ); \ - SET(NAME) - - FUNC("julianToHuman", cb_julian_to_human); - FUNC("julianToISO8601", cb_julian_to_iso8601); - FUNC("julianToUnix", cb_julian_to_unix); - FUNC("now", cb_time_now); - FUNC("unixToJulian", cb_unix_to_julian); - FUNC("cpuTime", cb_run_timer_fetch); - -#undef SET -#undef FUNC -#undef VCHECK - end: - return rc; -} - -static int cb_fsl_rc_cstr( cwal_callback_args const * args, - cwal_value **rv ){ - if(!args->argc){ - return cb_toss(args,CWAL_RC_MISUSE, - "Expecting a single integer argument (FSL_RC_xxx value)."); - }else{ - cwal_int_t const i = cwal_value_get_integer(args->argv[0]); - char const * msg = fsl_rc_cstr((int)i); - *rv = msg - ? cwal_new_xstring_value(args->engine, msg, cwal_strlen(msg)) - : cwal_value_undefined(); - return *rv ? 0 : CWAL_RC_OOM; - } -} - -static cwal_value * s2_fsl_ns( s2_engine * se ){ - int rc = 0; - cwal_value * ns; - cwal_value * v; - char const * pKey = "Fossil"; - cwal_engine * e; - ns = s2_prototype_stashed(se, pKey); - if(ns) return ns; - e = s2_engine_engine(se); - assert(se && e); - ns = cwal_new_object_value(e); - if(!ns){ - rc = CWAL_RC_OOM; - goto end; - } - rc = s2_prototype_stash( se, pKey, ns ); - if(rc) goto end; -#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 -#define SET(NAME) \ - VCHECK; \ - cwal_value_ref(v); \ - rc = cwal_prop_set( ns, NAME, cwal_strlen(NAME), v ); \ - cwal_value_unref(v); \ - if(rc) {goto end; } (void)0 -#define FUNC(NAME,FP) \ - v = cwal_new_function_value(e, FP, 0, 0, 0 ); \ - SET(NAME) - - v = cwal_new_string_value(e, "Fossil", 6); - SET("__typename"); - - FUNC("globMatches", cb_strglob); - FUNC("isUuid", cb_is_uuid); - FUNC("rcString", cb_fsl_rc_cstr); - - v = fsl_db_prototype(se); - VCHECK; - if( (rc = cwal_prop_set_with_flags( ns, "Db", 2, v, - CWAL_VAR_F_CONST)) ){ - goto end; - } - - v = fsl_cx_prototype(se); - VCHECK; - if( (rc = cwal_prop_set_with_flags( ns, "Context", 7, v, - CWAL_VAR_F_CONST)) ){ - goto end; - } - - if( (rc = fsl_add_delta_funcs(e, ns)) ) goto end; - if( (rc = fsl_add_file_funcs(e, ns)) ) goto end; - if( (rc = fsl_add_time_funcs(e, ns)) ) goto end; - -#undef SET -#undef FUNC -#undef VCHECK - end: - return rc ? NULL : ns; -} - -/** - We copy fsl_lib_configurable.allocator as a base allocator. -*/ -static fsl_allocator fslAllocOrig; - -/** - Proxies fslAllocOrig() and abort()s on OOM conditions. - */ -static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){ - void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n); - if(n && !rv){ - fprintf(stderr,"\nOUT OF MEMORY\n"); - fflush(stderr); - s2_fatal(CWAL_RC_OOM,"Out of memory."); - } - return rv; -} - -/** - Replacement for fsl_lib_configurable.allocator which abort()s on OOM. - Why? Because fossil(1) has shown how much that can simplify error - checking in an allocates-often API. - */ -static const fsl_allocator fcli_allocator = { -fsl_realloc_f_failing, -NULL/*state*/ -}; - - -/** - Gets called by s2sh's init phase to install our bindings. -*/ -int s2_shell_extend(s2_engine * se, int argc, char const * const * argv){ - static char once = 0; - int rc = 0; - cwal_value * v /* temp value */; - cwal_engine * e = s2_engine_engine(se) - /* cwal_engine is used by the core language-agnostic script engine - (cwal). s2_engine is a higher-level abstraction which uses - cwal_engine to (A) provide a Value type system and (B) manage - the lifetimes of memory allocated on behalf of the scripting - language (s2). s2 has to take some part in managing the - lifetimes, but it basically just sets everything up how cwal - expects it to be, and lets cwal do the hard parts wrt memory - management. - */ - ; - if(once){ - assert(!"libfossil/s2 bindings initialized more than once!"); - s2_fatal(CWAL_RC_MISUSE, - "libfossil/s2 bindings initialized more than once!"); - } - fslAllocOrig = fsl_lib_configurable.allocator; - fsl_lib_configurable.allocator = fcli_allocator - /* This MUST be done BEFORE the fsl API allocates - ANY memory! - */; - - if(0){ - /* force hash-based scope property storage for future scopes */ - int32_t featureFlags = cwal_engine_feature_flags(e, -1); - featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH; - cwal_engine_feature_flags(e, featureFlags); - } - -#define VCHECK if(!v && (rc = CWAL_RC_OOM)) goto end - - /* MARKER(("Initializing Fossil namespace...\n")); */ - - fsl_timer_start(&RunTimer); - - v = s2_fsl_ns(se); - VCHECK; -#if 1 - if( (rc = s2_define_ukwd(se, "Fossil", 6, v)) ) goto end; -#else - cwal_scope * dest = cwal_scope_current_get(e) - /* Where we want to install our functionality to. Note that we're - not limited to installing in one specific place, but in practice - that is typical, and the module loader's interface uses that - approach. */; - if( (rc = cwal_scope_chain_set_with_flags( dest, 0, "Fossil", 6, - v, CWAL_VAR_F_CONST)) ){ - goto end; - } -#endif - - { - /* Extend the built-in Buffer prototype */ - cwal_value * bufProto = s2_prototype_buffer(se); - assert(bufProto); -#define BFUNC(NAME,FP) cwal_prop_set( bufProto, NAME, cwal_strlen(NAME), \ - cwal_new_function_value(se->e, FP, 0, 0, 0)) - BFUNC("md5", cb_buffer_md5_self); - BFUNC("sha1", cb_buffer_sha1_self); - BFUNC("sha3", cb_buffer_sha3_self); - } - -#undef VCHECK - end: - return rc; -} - -#undef MARKER -#undef THIS_DB -#undef THIS_STMT -#undef THIS_F DELETED bindings/s2/timeline.s2 Index: bindings/s2/timeline.s2 ================================================================== --- bindings/s2/timeline.s2 +++ bindings/s2/timeline.s2 @@ -1,70 +0,0 @@ -#!/usr/bin/env f-s2sh -/** - A very basic timeline application implemented in s2. - - Optional flags, passed after '--' on the CLI: - - -f|--files lists files changed by each commit. - - -R|--repo-db=DBNAME specifies a repository db (default=current checkout). -*/ -assert Fossil.require; -Fossil.require( -['fsl/db/repoOrCheckout', // opens -R|--repo-db REPOFILE db or the current checkout - 'fsl/timeline/basic', // array of recent event table entries - 'cliargs' // flags passed after '--' are "script flags", and this module assists in using them -], -proc(db,tl,cli){ - const showFiles = cli.takeFlag('f', cli.takeFlag('files')); - if(cli.hasFlags()){ - throw "Unexpected flags(s): "+{}.toJSONString.call(cli.flags); - }else if(cli.hasNonFlags()){ - throw "Unexpected non-flag(s): "+cli.nonFlags.toJSONString(); - } - const j2h = Fossil.time.julianToHuman; - const showFilesChangedBy = proc(uuid){ - affirm 'string' === typename uuid; - var gotOne = 0; - db.each({ - sql:<<>2; -assert 4 === 1<<1<<1; - -assert 4 === 1<<1<<1 ||| 0; -assert 4 === 0 || 1<<1<<1 ||| 1; - -assert 1 === 0b1; -assert 4 === 0b100; -assert 0xff === 0b11111111; -assert 0b0110 === (0b_11_10 & 0b_01_11); -assert 0b100 === 0b1 << 2; DELETED bindings/s2/unit/000-010-compare.s2 Index: bindings/s2/unit/000-010-compare.s2 ================================================================== --- bindings/s2/unit/000-010-compare.s2 +++ bindings/s2/unit/000-010-compare.s2 @@ -1,73 +0,0 @@ -assert true === true; -assert true == 1; -assert true !== 1; -assert true != 0; -assert !false; -assert !(false == 1);; -assert false != '0'; -assert false == +'0'; -assert false == ''; -assert !!!''; -assert << 0; -assert !(0 > 1); -assert !(1 < 0); -assert 0 < 1 && 1 > 0; - -assert !(3>7); -assert 1 ? 3<2+2 : 3>7; - -// stack size error: (1 ? 'hi' : (again && yet !wrong) ) -// (is invalid syntax, but the error message could be better) -// hmmm: -assert 'hi' === (false ? nope : (1 ? 'hi' : (again && yet + !wrong) )); - -assert 1 === 3.2 % 2.1 /* modulo is always integer */; - -assert 1 < 1.1 /* this wasn't always the case in s2 */; -assert 1.1 > 1 /* and yet this was */; - -/* Arguable (but consistent) result type behaviours: */ -assert 18 === 8*2.3 /* integer result of double multiplication */; -assert 18.0 < 8.0*2.3 /* double result of double multiplication */; -assert 8.0*2.3 > 18 /* make sure this works both ways. */; -assert 18 < 8.0*2.3 /*b/c of the integer-to-double mode comparison at the cwal level*/; -assert !(8*2.3 > 18) /* integer math on the LHS of > op */; -assert 2 === 1/0.4 /* integer result of double division */; -assert 0 === 1/2; -assert 0 === 1/2.0; - -assert 2 === 1 ? 2 ? 3 : 4 : 5; -assert 5 === 0 ? 1&&2 ? 3 : 4 : 5; -assert 5 === 0 ? 1||2 ? 3 : 4 : 5; -assert 4 === 1 ? 0 ? 3 : 4 : 5; -assert 3 === 0&&1 ? 2 : 3; -assert 2 === 1||0 ? 2 : 3; -assert 2 === 0||1 ? 2 : 3; -assert 3 === 0||0 ? 2 : 3; -assert 3 === false||null ? 2 : 3; - -var y = ""; -assert 1 === (y ||| 1); -assert "" === (0 ||| y); -var x; -assert 1 === (x ?: 1); -x = false; -assert false === (x ?: 1); -assert 0 === (0 ?: 1); -x = undefined; -assert 1 === (x ?: 1); - -assert false === (false ?: throw 1) /* short-circuits the RHS */; -assert 1 === catch {undefined ?: throw 1}.message /* doesn't short-circuit the RHS */; - - DELETED bindings/s2/unit/001-010-typename.s2 Index: bindings/s2/unit/001-010-typename.s2 ================================================================== --- bindings/s2/unit/001-010-typename.s2 +++ bindings/s2/unit/001-010-typename.s2 @@ -1,20 +0,0 @@ -assert 'string' === typeinfo(name ''); -assert 'integer' === typeinfo(name 3 * 8); -assert 'double' === typeinfo(name 3.1); -assert 'function' === typeinfo(name print); -assert 'string' === typeinfo(name typeinfo(name 3)); -assert 'undefined' === typeinfo(name undefined); -assert 'undefined' === typeinfo(name $omeUndefined$ymbol); -assert 'object' === typeinfo(name s2.('prototype')); -assert 'object' === typeinfo(name s2.'prototype'); -assert 'object' === typeinfo(name s2.prototype); -assert 'object' === typeinfo(name s2['pro'+'totype']); -assert 'object' === typeinfo(name s2[('p'+('r'+('o')))+('totype')]); - -assert 'integer' === typeinfo(name 8*2.3); -assert 18 === 8*2.3 /* LHS type determines result type, - but double on the RHS is honored - for multiplication/division purposes - and the result gets truncated to an int. - */; -assert 'double' === typeinfo(name 8.0*2.3); DELETED bindings/s2/unit/001-011-nameof.s2 Index: bindings/s2/unit/001-011-nameof.s2 ================================================================== --- bindings/s2/unit/001-011-nameof.s2 +++ bindings/s2/unit/001-011-nameof.s2 @@ -1,3 +0,0 @@ -var x; -assert nameof x === 'x'; -assert 'x' === nameof x; DELETED bindings/s2/unit/002-000-eval.s2 Index: bindings/s2/unit/002-000-eval.s2 ================================================================== --- bindings/s2/unit/002-000-eval.s2 +++ bindings/s2/unit/002-000-eval.s2 @@ -1,16 +0,0 @@ -assert undefined === eval -> {}; -assert undefined === eval -> ''; - -assert 3 === eval {1 * 3}; -assert 3 === eval -> {1 * 3}; -assert '3 + 1' === eval "3 + 1"; - -assert 4 === eval -> "3 + 1"; -assert eval {2+2} === eval -> "3 + 1"; - -assert '3 * 2+1; 0' === eval => { 3 * 2+1; 0 }; -assert '3 * 2+1' === eval => 3 * 2+1 ; -assert 7 === eval-> eval =>3*2+1; -assert 2 === eval -> eval { 1; 2 }; -assert undefined === eval -> eval {}; -assert undefined === eval -> eval { 1; 2;; }; DELETED bindings/s2/unit/002-050-catch.s2 Index: bindings/s2/unit/002-050-catch.s2 ================================================================== --- bindings/s2/unit/002-050-catch.s2 +++ bindings/s2/unit/002-050-catch.s2 @@ -1,107 +0,0 @@ -const oldStackTrace = pragma(exception-stacktrace true); -assert undefined === catch {1+3}; -var ex; -//ex = catch -> ","; -ex = catch -> {","}; -//ex = catch{,} -//return ex; - -assert typeinfo(isexception ex); -assert typeinfo(isstring ex.script); -assert typeinfo(isinteger ex.line); -assert typeinfo(isinteger ex.column); -assert typeinfo(isinteger ex.code); -//print(typename ex,':',ex); - -assert 'CWAL_RC_TYPE' === catch{var x; x.foo}.codeString(); -assert 'CWAL_RC_NOT_FOUND' === catch{unknownVar}.codeString(); - -/* - dupe vars and var names which collide with a keyword currently - throw, rather than being triggered like syntax errors. The reason - is so that they are catchable. That is arguable behaviour but the - current unit test system can't (without additional sub-scripts) - 'catch' a syntax error to test these... -*/ -assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var dupe, dupe }.codeString(); -assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var for /*<== any keyword*/ }.codeString(); - -assert 'CWAL_RC_TYPE' === catch{s2.import('.')/*cannot eval a directory*/} - .codeString(); - -scope { - var ex = exception("hi"); - assert 'CWAL_RC_EXCEPTION' === ex.codeString(); - assert 'hi' === ex.message; - - ex = exception(1,2); - assert 1 === ex.code; - assert 2 === ex.message; - - ex = proc(){ - return exception(1,2); - }(); - assert 1 === ex.code; - assert 2 === ex.message; - assert typeinfo(isarray ex.stackTrace); - - const obj = { - prototype: exception(1,2) - }; - assert obj inherits ex.prototype; - assert 1 === obj.code; - assert 2 === obj.message; - assert typeinfo(isobject obj); - - const e2 = catch{throw obj}; - assert e2 === obj && "obj is-a exception, so it is thrown as-is."; -} - -assert undefined === (1 ? catch 0 : 0) -/* must not syntax error (fixed 20160205). Formerly the implicit scope - pushed by 'catch' (resp. the 'eval' family of keywords) was not - respecting the current ternary level (it was clearing it, on - purpose, because that's what we want in most cases), causing a - syntax error in such constructs. It now retains the ternary level - if the 'eval' operand is not a block expression. */; - -assert 0 === (1 ? catch {0:} : 0).message.indexOf("Unexpected ':'") -/* But _this_ use of ':', without a '?' in the same (explicit) scope, - is not permitted. */; - -ex = catch [1,2,3].get(-1); -assert typeinfo(isexception ex); -assert 'CWAL_RC_RANGE' === ex.codeString(); - -scope { - const ex = catch proc() { proc() { proc() {throw 1}() }() }(); - assert typeinfo(isexception ex); - const st = ex.stackTrace; - assert st.length() >= 3 /*(may vary in amalgamated tests)*/; - assert 47 === ex.column; - assert 55 === st.0.column; - assert 59 === st.1.column; - assert 63 === st.2.column; -} - -var line; -ex = catch{ - (line=__LINE+1), var x = {a:1 /*intentionally missing comma*/ - b:1}/* ACHTUNG: 'b' must be at column 5 or adjust the assertion below */ -}; -//print(__FLC,line, ex); -assert ex; -assert ex.column === 5 - /* at one point it was reporting the line/column of the opening '{' - for the object literal containing the syntax error. */; -assert ex.line === line; - -/* - On 20171130 it was accidentally discovered that tokens with token - type IDs in the range (1..127) were, in effect, being treated as - string literals by the eval engine. They now trigger a syntax error. -*/ -assert 'CWAL_SCR_SYNTAX' === catch{/*<== a literal \r*/}.codeString(); -assert 'CWAL_SCR_SYNTAX' === catch{``}.codeString(); - -pragma(exception-stacktrace oldStackTrace); DELETED bindings/s2/unit/003-000-string-ops.s2 Index: bindings/s2/unit/003-000-string-ops.s2 ================================================================== --- bindings/s2/unit/003-000-string-ops.s2 +++ bindings/s2/unit/003-000-string-ops.s2 @@ -1,9 +0,0 @@ -assert 'ab' === 'a'+"b"; -assert 'a123' === 'a' + 123; -assert 123 === 123 + 'a'; -assert '123' === '1' + 2 + 3; -assert 123 === +'1' + 122; -assert 'def' === 'd'+<<>=1); - assert 2 === (b<<=1); - a = 2; a*=3+2; - assert 10===a; - assert (a/=3*4%5) === 5; -} DELETED bindings/s2/unit/010-010-properties.s2 Index: bindings/s2/unit/010-010-properties.s2 ================================================================== --- bindings/s2/unit/010-010-properties.s2 +++ bindings/s2/unit/010-010-properties.s2 @@ -1,74 +0,0 @@ - -scope { - var o = {}; - assert 'object' === typename o; - o.x = o; - o.x['x'].'z' = 1; - assert 1 === o.x.'x'['x'].z; - assert 1 === o.x['x'].('z'); - assert 1 === o.x['x'].('x').('z'); - assert 1 === o.x['x'].('x')['x'].('z'); - o.x = 1; - o.x = o.x + 2; - assert 3 === o.x; - assert 6 === o.x * 2; - assert 4 === 1 + o.('x'); - assert 2 === o['x'] * 3 % 7; - var z = 'x'; - assert 3 === o[z]; - assert 9 === o.(z) * o[z]; -} - -scope { - var o = {a:1}; - assert 2 === (o.a+=1); - assert -2 === (o.a*=3*3-10); - o.a = 2; - assert 10===(o.a*=3+2); - assert 10 === o.a; - assert 5 === (o['a'] /= 2); - assert 20 === (o['a'] <<= 2); - assert 20 === o.a; -} - -scope { - var o = {a:1}, o2 = {prototype:o}; - assert o2 inherits o; - assert 1 === o2.a; - o2.a = -1; - assert 1 === o.a; - assert -1 === o2.a; - unset o2.a; - assert 1 === o2.a; - unset o2.a; - assert 1 === o2.a /* o.a not cleared by unset */; - o2.unset('a'); - assert 1 === o2.a /* o.a not cleared by o2.unset() */; - o.clearProperties(); - assert undefined === o2.a; - - o.x = 1, o2.x = 1; - assert 1 === o.x && 1 === o2.x; - unset o.x, o2.x; - assert undefined === o.x /* ensure unset handles commas properly */; - assert undefined === o2.x /* ensure unset handles commas properly */; -} - -scope { - // Test 20190706 fix for bool lookup/property key bug... - var o = {}; - assert undefined === o[true] /* must not resolve to a truthy-keyed - property (e.g. a member method) */; - o[true] = 3; // bool-typed property key - assert undefined === o['x'] /*must not match a boolean true property key */; - assert 3 === o[true] /* must match existing bool-type key */; - assert undefined === o.true /* note that o.true is a STRING key, not bool */; - assert undefined === o[false] /* just checking */; - o[false] = 1; - assert 1 === o[false] /* must match literal bool prop key */; - assert undefined === o.false /* note that o.false is a STRING key, not bool */; - assert undefined === o[0] /* must not match property key [false] */; - o[0] = 2; - unset o[false]; - assert undefined === o[false] /* must not match falsy property key [0] */; -} DELETED bindings/s2/unit/010-050-incrdecr.s2 Index: bindings/s2/unit/010-050-incrdecr.s2 ================================================================== --- bindings/s2/unit/010-050-incrdecr.s2 +++ bindings/s2/unit/010-050-incrdecr.s2 @@ -1,65 +0,0 @@ - -if(1) { - var a = 1; - assert 1 === a++; - assert 2 === a; - assert 3 === ++a; - assert 'CWAL_SCR_SYNTAX' === catch{++a = a}.codeString() /*invalid target for assignment*/; - assert 3 === a /* b/c the exception is thrown before ++a happened */; - assert 'CWAL_RC_NOT_FOUND' === catch{++unknownSymbol}.codeString() /*unknown symbol*/; - assert 3 === (a = a++); - assert 3 === a; - assert 3 === a--; - assert 2 === a; - assert 1 === --a; - assert 6 === --a + 2 * 3; - assert 0 === a; -} - -if(1) { - var o = {a:1}; - var a = 1; - assert 1 === o.a++; - assert 2 === o.a; - assert 3 === ++o.a; - assert 'CWAL_SCR_SYNTAX' === catch{++o.a = o.a}.codeString() /*invalid target for assignment*/; - assert 3 === o.a /* b/c the exception is thrown before ++o.a happened */; - assert 3 === (o.a = o.a++); - assert 3 === o.a; - assert 3 === o.a--; - assert 2 === o.a; - assert 1 === --o['a']; - assert 6 === --o.a + 2 * 3; - assert 0 === o.a; -} - -if(1){ - // Make sure incr/decr do not demote doubles to integers. (They used to.) - var a = 0.0; - assert 1.0 === ++a; - assert 0.0 === --a; - - assert 0.0 === a++; - assert 1.0 === a++; - assert 2.0 === a; - - var o = {a:0.0}; - assert 1.0 === ++o.a; - assert 0.0 === --o.a; - assert 0.0 === o.a++; - assert 1.0 === o.a++; - assert 2.0 === o.a; - - // Make sure booleans are coerced to integers... - a = false; - assert 1 === ++a; - a = true; - assert true === a++ /* Interesting... but is it wrong? */; - assert 2 === a; - - assert 1 === +true; - assert 0 === +false; - assert -1 === -true; - assert 0 === -false; - -} DELETED bindings/s2/unit/015-000-interceptors.s2 Index: bindings/s2/unit/015-000-interceptors.s2 ================================================================== --- bindings/s2/unit/015-000-interceptors.s2 +++ bindings/s2/unit/015-000-interceptors.s2 @@ -1,181 +0,0 @@ -if(!s2.propertyInterceptor){ - /** - property interceptor support will almost certainly never be - enabled - the chances of unwanted side effects, especially - within cwal's limited key/value property model are simply too - high. Also, the performance hit it applies to the library is - not something i'm willing to accept for a sugar-only feature - like this one. It might be more feasible if cwal had a - higher-level properties API, instead of simply key/value pairs, - but that's too heavy-weight for cwal's design goals. - */ - // print("s2.propertyInterceptor() not enabled."); - /* No 'return' - it messes up one of the unit tests. */ -}else{ - affirm typeinfo(isfunction const mkint = s2.propertyInterceptor); - - scope { - const o = { - a: 1 - }; - assert mkint === mkint(o, 'b', proc(){ - if(argv.length()){ - // setter... - interceptee.a = argv.0; - //throw "testing"; - return /* return val is ignored for setters! */; - } - // getter... - return interceptee.a; - }); - assert 1 === o.b; - o.b = 12; - //throw {o}; - assert 12 === o.a; - ++o.a; - ++o.b; /* well that wasn't supposed to work. */ - o.b++; /* well that wasn't supposed to work. */ - assert 15 === o.b; - assert o.a === o.b; - - const x = {prototype:o, foo: 7}; - assert o.a === x.b; - x.b *= 2; - assert 30 === o.a; - assert 30 === x.b; - o.a *= 2; - assert 60 === o.a; - } - - scope { - const pro = {foo: 7}; - const x = {prototype: pro}; - - mkint(pro, 'y', proc(){ - affirm pro === interceptee; - affirm x === this; - //return this.y; - //throw {x: typename this.y}; - // this.y is NOT supposed to be resolving - // as a Function here, but should recurse. - return this.y(@argv); - }); - x.y2 = mkint(proc(){ - affirm this === interceptee; - affirm x === this; - return this.y; - }); - mkint(x, 'r', proc(){ - affirm this === interceptee; - affirm x === this; - return this.r /* why is this access not triggering the - interceptor again? It's most certainly - not by design. */; - }); - mkint(x, 'zz', proc(){ - //break; // error info here is not useful. - affirm this === interceptee; - affirm x === this; - if(argv.length()){ - this.foo = argv.0; - return; - } - return this.foo; - }); - x.zzz = mkint(proc(){ - //break; // error info here is not useful. - affirm this === interceptee; - affirm x === this; - if(argv.length()){ - this.zz = argv.0; - return; - } - return this.zz; - }); - assert 7 === x.zzz; - var ex; - //why not triggering the interceptor here!?!?!? - //ex = catch{x.y}.codeString(); - //assert typeinfo(isexception ex); - //assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString(); - ex = catch{x.y2}; - assert typeinfo(isexception ex); - assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString(); - assert typeinfo(isfunction x.r) - /* This assertion is unexpected/wrong: i'm expecting an exception. */; - unset ex; - var i = 0; - foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v)); - assert !i /* foreach skips over interceptors */; - - x.zzz = 3; - assert 3 === x.zz; - assert 3 === x.foo; - - foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v)); - assert 1===i /* .foo prop */; - - } - - scope { - const ar = [1,2,3]; - ar.L = mkint(proc(){ - const x = 0 ? this : interceptee; - affirm typeinfo(isarray interceptee); - if(argv.length()){ - // setter... - //print(__FLC,"setting",x,".length(",argv.0,")"); - x.length(argv.0); - //throw "testing"; - return/* return val is ignored for setters! */; - - } - // getter... - //print(__FLC,"getting",x,".length()"); - return x.length(); - }); - assert 3 === ar.L; - ar.L = 5; - assert 5 === ar.length(); - assert undefined === foreach(ar=>k,v){ - k === 'L' && break k; - } /* foreach() (currently) explicitly skips over interceptors */; - var obj = {prototype:ar, x:1}; - foreach(obj=>k,v){ - assert k !== 'L'; - }; - - var newLen = 2; - obj.L = newLen; - assert obj.L === ar.length(); - assert 2 === ar.L; - assert ar.L === obj.length(); - assert ar.L === obj.L; - obj.L *= newLen; - newLen *= newLen; - assert newLen === ar.L; - assert newLen === ar.length(); - assert 2 === ar.1; - assert undefined === ar[ar.L-1]; - - } - - scope { - const str = "abcdef"; - str.prototype.L = mkint(proc(){ - affirm "".prototype === interceptee; - affirm typeinfo(isstring this); - if(argv.length()){ - // setter... - throw "Strings are immutable - cannot set their length."; - - } - // getter... - return this.length(); - }); - assert 6 === str.L; - assert catch str.L = 1; - assert str[5] === str[str.L-1]; - } - -} DELETED bindings/s2/unit/020-000-strings.s2 Index: bindings/s2/unit/020-000-strings.s2 ================================================================== --- bindings/s2/unit/020-000-strings.s2 +++ bindings/s2/unit/020-000-strings.s2 @@ -1,296 +0,0 @@ -/* String method tests... */ - -scope{ - assert 'ab' === 'a'.concat('b'); - assert 'ABC' === 'a'.concat('b','c').toUpper(); - assert 42 === '0*'.byteAt(1); - assert undefined === '0'.byteAt(3); - assert '*' === '1*1'.charAt(1); - assert '☺' === '1☺1'.charAt(1); - assert '*' === '1*1'[1]; - assert '☺' === '1☺'[1]; - assert '☺' === '☺1'[0]; - assert undefined === '☺1'[2]; - - assert '.'.isAscii(); - assert !'1☺1'.isAscii(); - assert '...'[0].isAscii(); - assert '1☺1'[0].isAscii(); - assert !'1☺1'[1].isAscii(); - - var x = "hi! there! this string cannot not get interned (not simple/short enough)"; - var y = "".concat(x); - assert x === y - /* String.concat() optimization: "".concat(oneString) returns - oneString. Unfortunately, from the script level, we can't - _really_ be sure that we have the same string instance. Except - possibly via this little cheat... */; - assert typeinfo(refcount x) === typeinfo(refcount y); - var z = [x,x,x]; // just grab a few refs to x - assert typeinfo(refcount x) === typeinfo(refcount z.0); - assert typeinfo(refcount x) === typeinfo(refcount y); - y = "".concat("",y) /* bypasses optimization */; - assert typeinfo(refcount x) === typeinfo(refcount z.0); - assert typeinfo(refcount x) > typeinfo(refcount y); - // WEIRD... the refcount on y is 1 higher on the first hit: - // print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z)); - // it's almost like an argv isn't letting its ref go (but gets - // swept up by the next call), but i just checked and argv is - // ref/unref'd properly. Hmmm. Aha: it's the pending result value - // in the stack (s2_eval_ptoker(), actually) at that point! Correct behaviour - - // it gets cleaned up by s2_eval_ptoker() after the next expression (the first - // call, were the refcount is +1 higher than we might (otherwise) expect). - //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z)); - //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z)); -} - -scope { - assert 'Z☺Z' === 'z☺z'.toUpper(); - assert 'z☺z' === 'Z☺Z'.toLower(); - - var str = '↻Æ©'; - assert 5 === "©123©".length(); - assert 5 === "©123©".#; - assert 7 === "©123©".lengthBytes(); - assert 3 === str.length(); - assert 3 === str.#; - assert 7 === str.lengthBytes(); - assert '↻' === str.charAt(0); - assert 'Æ' === str.charAt(1); - assert '©' === str.charAt(2); - assert '©' === str[2]; - assert undefined === str.charAt(3); - assert undefined === str[3]; - assert 'Æ©' === str.substr(1); - assert '↻Æ' === str.substr(0,2); - - - str = 'こんにちは' /*no idea what this says (or not) - copied it from the net. - Google says it means "Hi there"*/; - assert 15 === str.lengthBytes(); - assert 5 === str.length(); - assert 'こ' === str.charAt(0); - assert 'ん' === str.charAt(1); - assert 'に' === str.charAt(2); - assert 'ち' === str.charAt(3); - assert 'は' === str.charAt(4); - assert 'んにち' === str.substr(1,3); - assert 'にちは' === str.substr(2,-1); - - assert '' === str.substr(0,0); - - assert 'CWAL_RC_RANGE' === catch {"abc"[-1]}.codeString() - /* negative (from-the-end) indexes are a potential TODO, currently disallowed. */; - - assert 'c' === 'abc'.charAt(2).charAt(0).charAt(0).charAt(0); - assert 'c' === 'abc'[2][0][0] - /* SOMETIMES a cwal-level assertion: lifetime issue. Triggering it - depends on recycling settings, engine state, and... lemme - guess... right: string interning again >:(. Somewhere we're - missing a reference we need. This was "resolved" in - s2_process_top() by adding its newly-pushed result value to the - eval-holder list (if such a list is active). - */; - assert 'c' === 'abc'[2][0][0][0][0][0][0][0][0][0]; - -}; - - -scope { - /* The subtleties of split()... */ - const split = proc(s,sep){ - sep || (sep = ':'); - var ar = s.split(sep); - var j = ar.join(sep); - assert j === s; - return ar; - }; - - scope { - var str = "//aaa//b//c//xy//"; - var sep = '//'; - var ar = split(str,sep); - assert ar.join(sep) === str; - assert "" == ar.0; - assert "" === ar.5; - assert 6 === ar.length(); - assert 'aaa' === ar.1; - assert 'xy' === ar.4; - } - - scope { - var str = "aaa/b/c/xy/"; - var sep = '/'; - var ar = split(str, sep); - assert ar.join(sep) === str; - assert 5 === ar.length(); - assert 'aaa' === ar.0; - assert "" === ar.4; - } - - scope { - var str = "aaa©b©c©"; - var sep = '©'; - var ar = split(str, sep); - assert ar.join(sep) === str; - assert 4 === ar.length(); - assert 'aaa' === ar.0; - assert "" === ar.3; - } - - scope{ - var str = ":::"; - var sep = ':'; - var ar = split(str, ':'); - assert ar.join(sep) === str; - assert 4 === ar.length(); - assert !ar.0; - assert !ar.3; - } - - scope { - var str = ":"; - var sep = str; - var ar = split(str, sep); - assert ar.join(sep) === str; - assert 2 === ar.length(); - assert !ar.0; - assert !ar.1; - } - - scope { - var ar = split(":a:b:",':'); - assert 4 === ar.length(); - assert !ar.0; - assert 'a' === ar.1; - assert 'b' === ar.2; - assert !ar.3; - } - - scope { - var ar = split("abc", '|'); - assert 1 === ar.length(); - assert 'abc' === ar.0; - } - - scope { - var ar = split("", '.'); - assert 1 === ar.length(); - assert "" === ar.0; - } - - scope { - var ar = split("a:::b", ':'); - assert 4 === ar.length(); - assert "a" === ar.0; - assert !ar.1; - assert !ar.2; - assert "b" === ar.3; - } - - scope { - // The 'limit' semantics changed on 20160213 to match what JS does. - // Note that we still don't accept split() with no args, like JS does. - var ar = "a:b:c".split(':',2); - assert 2 === ar.length(); - assert 'b' === ar.1; - - ar = 'a:b:c'.split('/'); - assert 1 === ar.length(); - assert 'a:b:c' === ar.0; - } - - scope { - /* "Empty split". String interning saves us scads of memory here - when the input string is large... */ - var ar = "abc".split(''); - assert typeinfo(isarray ar); - assert 3 === ar.length(); - assert 'c' === ar.2; - - ar = "abc".split('',2); - assert 2 === ar.length(); - assert 'b' === ar.1; - } - -} /* end split() */ - -scope { - //assert "3.00.1" === "3.00.1" /* just making sure */; - //print("3.00.1", "3.0"+0.1); - assert "3.00.1" === "3.0"+0.1; // string on the left - assert 3.7 === 0.7 + "3"; // string on the right - assert 3.74 === 0.7 + "3" + "0.04"; - assert 3.1 === 3.1 + "abc"; // "abc"==0 - assert 5 === +"5"; - assert -5 === -"5"; - assert -5 === +"-5"; - assert 1.2 === -"-1.2"; // but beware of precision changes on such conversions! -} - -scope { - const sp = "".prototype; - sp.firstChar = proc(asInteger=false){ - return this.charAt(0, asInteger); - }; - assert "a" === "abc".firstChar(); - //unset "".prototype.firstChar; // hmmm - unset cannot deal with this. - unset sp.firstChar; -} - -scope { - var a = "a"; - a += "b"; - assert 'ab' === a; -} - -scope { - /* String.replace() tests... */ - proc x(haystack,needle,replace/*,limitAndOrExpect*/){ - const expect = argv.4 ||| argv.3, - limit = argv.4 ? argv.3 : 0; - const v = haystack.replace(needle, replace, limit); - (expect === v) - && (assert 1 /*String.replace() check passed*/) - && return x; - throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]"; - } - ("abc", 'b', 'B', 'aBc') - ('abcabc', 'b', 'BB', 1, 'aBBcabc') - ('ab©ab©ab©', 'b', 'BB', 2, 'aBB©aBB©ab©') - ('(C)ab(C)', '(C)', '©', '©ab©') - ('abc', 'x', 'y', 'abc') - ('abc', 'abc', '', '') - ('a\r\nc\r\n', '\r\n', '\n', 'a\nc\n') - ;; -} - -scope { - assert 2 === 'abcd'.indexOf('c'); - assert 0 > 'abcd'.indexOf('e'); - assert 2 === 'ab©'.indexOf('©'); - assert 3 === 'ab©c'.indexOf('c'); - assert 3 === 'ab©cはd'.indexOf('cはd'); - - // 20190713 bugfix: the indexOf() of same-length strings - // was just plain broken due to an "optimization" which - // backfired. - assert 'a'.indexOf('b') < 0; - assert 'cab'.indexOf('cab') === 0; -} - -scope { - - /* 20191220: an invalid \Uxxxxxxxx value now triggers a syntax - error rather than being immediately fatal with no error - location info. Because it's a syntax error, and not an exception, - it can only be catch'd if it comes from inside/through a block. - i.e. (catch '\U00E0A080') will not convert it to an exception, but - the following will... */ - const ex = catch { - '\U00E0A080' - }; - assert ex; - assert ex.message.indexOf('Uxx')>0; - assert 'CWAL_SCR_SYNTAX' === ex.codeString(); -} DELETED bindings/s2/unit/020-050-buffer.s2 Index: bindings/s2/unit/020-050-buffer.s2 ================================================================== --- bindings/s2/unit/020-050-buffer.s2 +++ bindings/s2/unit/020-050-buffer.s2 @@ -1,336 +0,0 @@ - -const Buffer = s2.Buffer.new; - -scope { - - var b = Buffer(); - assert b inherits s2.Buffer; - assert !b.capacity(); - assert !b.length(); - b.length(10); - assert 10 === b.length(); - assert b.# === b.length(); - assert b.capacity() >= b.length(); -} - - -scope { - const sz = 20, b = Buffer(sz); - assert !b.#; - assert b.isEmpty(); - assert b.capacity() >= sz; - b.append('a'); - assert !b.isEmpty(); - assert 1 === b.#; - b.appendf('%1$d', 1); - assert 2 === b.#; - - b.fill(42); - assert '**' === b.toString() /* ensure that fill() does not change the length() */; - - b.reset(); - assert 0 === b.#; - b.fill('!'); - assert undefined === b.byteAt(0) /* ensure that fill() does not change the length() */; - - b.length(sz).fill( '$' ); - assert 36 === b.byteAt( 0 ); - assert 36 === b.byteAt( sz-1 ); - assert undefined === b.byteAt( sz ); - - b.fill( '!', 1, 18 ); - assert 36 === b.byteAt( 0 ); - assert 36 === b.byteAt( 19 ); - assert 33 === b.byteAt( 1 ); - assert 33 === b.byteAt( 18 ); - assert '$!' === b.toString( 0, 2 ); -} - -scope { - const fmt = proc callee(fmt){ - return callee.buffer.reset( 0 ).appendf.apply( callee.buffer, argv ).toString(); - }; - fmt.buffer = Buffer(100); - const check = proc(cmp){ - argv.shift(); - const s = fmt.apply(fmt,argv); - (s===cmp) || throw ("Mismatch: <<<"+cmp+">>> !== <<<" +s+">>>"); - assert 1 /* count the above comparison as an assertion. - We use throw only to get more info out of it - if it fails*/; - }; - const av = [ 42, "string formatter", "abcde", - 19.17, true, false, -13.19]; - - assert 'CWAL_RC_RANGE' === catch{fmt("%8$+f", 0)/*not enough args*/}.codeString(); - - check( '%', '%%' ); - check( '%x%y', '%%x%%y' ); - - check( 'hi, world', "%1$s", 'hi, world' ); - check('hi, world ( hi, world)', - "%1$s (%1$20s)", 'hi, world'); - check('integer 42 042 02A 002a', - "%1$y %1$d %1$03d %1$03X %1$04x", av.0); - check('( 42) (42 )', - "(%1$10d) (%1$-10d)", av.0); - check('( +42) (+42 )', - "(%1$+10d) (%1$+-10d)", av.0); - - check( '00042', "%1$05d", av.0 ); - check( '42.00000', "%1$0.5f", av.0 ); - check('string formatter', "%2$s", 0, av.1); - check('string', "%2$.6s", 0, av.1); - check('string formatter and string formatter', - "%2$s and %2$s", 0, av.1); - check( '(abcde ) ( abcde)', - "(%3$-10s) (%3$10s)", 0, 0, av.2 ); - check( '( abcde) (abcde )', - "(%3$10s) (%3$-10s)", 0, 0, av.2 ); - check( '( abc) (abc )', - "(%3$10.3s) (%3$-10.3s)", 0, 0, av.2 ); - check( '(abc ) ( abc)', - "(%3$-10.3s) (%3$10.3s)", 0, 0, av.2 ); - check( '(abcd) (abcd )', - "(%3$.4s) (%3$-8.4s)", 0, 0, av.2 ); - check( '19.17 19.2 19.17 19.170', - "%4$f %4$.1f %4$.2f %4$.3f", 0, 0, 0, av.3 ); - check( '+19.17 (19.170 ) ( 19.170) (+00019.170)', - "%4$+0f (%4$-10.3f) (%4$10.3f) (%4$+010.3f)", 0, 0, 0, av.3 ); - check( '1 true false null undefined', - "%5$d %5$b %6$b %5$N %5$U", 0, 0, 0, 0, av.4, av.5 ); - check( '0.0', "%6$+f", 0, 0, 0, 0, 0, av.5 ); - check( '-13.19', "%7$+f", 0, 0, 0, 0, 0, 0, av.6 ); - - check( '68692C20776F726C64', - "%1$B", "hi, world" ); - check( '68692C20', - "%1$.4B", "hi, world" ); - check( ' 68692C20776F726C64', - "%1$30B", "hi, world" ); - - check( '-1.0', '%1$.1f', -1.02 ); - check( '+1.0', '%1$+.1f', 1.02 ); - - check( '-1', '%1$d', (-1) ); - check( '-1', '%1$+d', (-1) ); - check( '+1', '%1$+d', 1 ); - - check( 'h', '%1$c', 'hi' ); - check( ' hhhhh', '%1$10.5c', 'hi' ); - check( 'hhhhh ', '%1$-10.5c', 'hi' ); - check( ' ©', '%1$3c', '©' ); - check( '©©©', '%1$0.3c', '©' ); - check( '©©©', '%1$.3c', '©' ); - check( '©©© ', '%1$-5.3c', '©' ); - check( ' ©©©', '%1$5.3c', '©' ); - check( 'A', '%1$c', 65 ); - check( 'AAAA', '%1$.4c', 65 ); - check( '10', '%1$o', 8 ); - check( '0010', '%1$04o', 8 ); - check( ' 10', '%1$4o', 8 ); - assert '1 2 3' === "%1$d %2$d %3$d".applyFormat( 1,2,3 ); - check( '(NULL)', '%1$q', null ); - check( "h''i", '%1$q', "h'i" ); - check( "'h''i'", '%1$Q', "h'i" ); - check( 'NULL', '%1$Q', null ); - - // check 0-precision characters... - check( '', '%1$.0c', '*' ); - check( '', '%1$0.0c', '*' ); - check( ' ', '%1$2.0c', '*' ) /* arguable, but currently true */; - - // check urlencoding/decoding - check( 'a%20b%26c', '%1$r', 'a b&c' ); - check( 'a b&c', '%1$R', 'a%20b%26c' ); - check( 'a%2zb', '%1$R', 'a%2zb' ); - - assert catch {'%1$.1r'.applyFormat()}.message.indexOf('precision')>0; - assert catch {'%1$1r'.applyFormat()}.message.indexOf('width')>0; - assert catch {'%1$.1R'.applyFormat()}.message.indexOf('precision')>0; - assert catch {'%1$1R'.applyFormat()}.message.indexOf('width')>0; -} - -scope{ - var b = Buffer(100); - var x = 0, y = 0; - b << <<b; - throw "NOT REACHED"; - }; - assert ex; - //print(__FLC,'ex =',ex) /* a cycle (the exception obj!) in the stack trace!?!?!? Corruption??? */; - // something here is triggering an assertion in cleanup! - // Worked around: comments are in evalContents() impl for further consideration later. - assert name === ex.script; - assert 3 === ex.message; - assert 3 === ex.line; - assert 4 === ex.column; - - assert 3 === "a+b".evalContents({a:1,b:2}); - assert 'xyz' === "__FILE".evalContents('xyz'); - assert 'xyz9' === "__FILE+a".evalContents({a:9},'xyz'); - assert 'xyz8' === "__FILE+a".evalContents('xyz',{a:8}); - assert 'test.x' === catch {"A_+B_".evalContents('test.x',{a:1,b:2})}.script; -} - -scope { - var b = Buffer() << "h©, ©orld"; - assert '©, ©' === b.substr(1,4); - assert 'h©, ©orld' === b.substr(0,-1); - assert '©orld' === b.substr(4); - assert ', ©o' === b.substr(2,4); - - b.reset(); - assert '' === b.substr(1); -} - -scope { - var b = Buffer() << 1.00; - assert "1.0" === b.toString() /* bugfix check: leave final trailing 0 after the dot. */; - - - /* Testing Buffer.slice()... */ - b.reset() << "012345"; - proc x(expect,offset=0,count=-1){ - const v = b.slice(offset, count).toString(); - (expect === v) && (assert "slice() check passed") && return x; - throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]"; - } - ("012345") - ("012345",0) - ("012345",0,-1) - ("12345",1,-1) - ("12345",1,50) - ("5", 5, 1) - ("5", 5, 10) - ("", 5, 0) - ("1234", 1, 4) - ; - - /* Buffer.replace() tests... */ - b.reset() << "012345"; - const check = proc callee(needle,replace/*,limitAndOrExpect*/){ - const expect = argv.3 ||| argv.2, - limit = argv.3 ? argv.2 : 0; - const v = b.replace(needle, replace, limit).toString(); - (expect === v) && (assert 1 /*Buffer.replace() check passed*/) && return callee; - throw "Mismatch: expecting ["+expect+"] but got: ["+v+"]"; - } - ('1', '9', '092345') - (57, 42, '0*2345') - ('345', '**', 42, '0*2**') - ('*', 'x', 1, '0x2**') - ('0x2', '', '**') - (42, 0, '\0\0') - ; - assert !b.isEmpty() /* technically speaking */; - assert 2 === b.#; - assert 0 === b.byteAt(1); - var v = b.toString(); - assert 2 === v.#; - assert '\0\0' === v; - - b.reset() << "012345"; - check('1', '©', '0©2345') - ('345', '©©', '0©2©©') - ; - - b.reset() << "a*b*c"; - assert b.replace(42,33).toString() === "a!b!c"; - b.reset() << "a*b*c"; - assert b.replace(42,33,1).toString() === "a!b*c"; - - assert catch {b.replace("","x")}.codeString() === 'CWAL_RC_RANGE' /* needle must be >0 bytes */; -} - -scope { - /* - Ensure that evalContents() behaves safely when the being-eval'd - buffer's contents are modified during evaluation. i.e. the - modifications are only temporary and get discarded when - evalContents() is done. - */ - const contents = "x = 2; b<<'abc'; assert 'abc'===b.takeString()"; - var x, b = Buffer() << contents; - b.evalContents(__FLC); - assert 2 === x; - assert contents === b.takeString(); -} - - -if(const BB = s2.Buffer.compression ? s2.Buffer : 0){ - assert 0 === catch {BB.isCompressed()}.message.indexOf("'this'"); - assert 0 === catch {BB.isCompressed(1)}.message.indexOf("Argument"); - assert 0 === catch {BB.uncompressedSize()}.message.indexOf("'this'"); - assert 0 === catch {BB.uncompressedSize(1)}.message.indexOf("Argument"); - const b = BB.readFile(__FILE); - const len = b.#; - assert len > 5000; - assert !b.isCompressed(); - assert !b.isCompressed(b); - assert 0 === catch {b.isCompressed(1)}.message.indexOf("Argument"); - assert !BB.isCompressed(b); - assert undefined === b.uncompressedSize(); - assert undefined === BB.uncompressedSize(b); - assert b === b.uncompress() /* must be a no-op */; - assert b.# === len; - - assert b === b.compress(); - assert b.isCompressed(); - assert b.isCompressed(b); - assert BB.isCompressed(b); - const zlen = b.#; - assert zlen < len; - assert zlen > len/10; - assert b === BB.compress(b) /* must be a no-op */; - assert b.# === zlen; - assert len === b.uncompressedSize(); - assert len === BB.uncompressedSize(b); - - assert b === BB.uncompress(b); - assert !b.isCompressed(); - assert !b.isCompressed(b); - assert b.# === len; - assert undefined === b.uncompressedSize(); - assert undefined === BB.uncompressedSize(b); - -}else{ - /* For the benefit of the -A flag (assert tracing). */ - var b = Buffer(); - assert !b.isCompressed(); - assert 'CWAL_RC_UNSUPPORTED' === catch {b.compress()}.codeString(); - assert 'CWAL_RC_UNSUPPORTED' === catch {b.uncompress()}.codeString(); - assert undefined === b.uncompressedSize(); - /* note that b.uncompressedSize() _would_ work if it was compressed, - but that's hard to demonstrate here w/o compression support. */ -} DELETED bindings/s2/unit/030-000-arrays.s2 Index: bindings/s2/unit/030-000-arrays.s2 ================================================================== --- bindings/s2/unit/030-000-arrays.s2 +++ bindings/s2/unit/030-000-arrays.s2 @@ -1,52 +0,0 @@ -var a = [1,2,3], b = []; -assert 'array' === typename a; -assert 'array' === typename b; -assert 2 === a[1]; - -// unset array.index broken by other fixes: -assert undefined === unset a[1]; -//a[1] = undefined; -assert undefined === a[1]; - -assert undefined === b[2]; -a[3] = [4,5,[6,7,8]]; -assert 'array' === typename a[3]; -assert 'array' === typename a[3][2]; -assert 8 === a[3][2][2]; -assert 8 === a.3[2].2; // tokenizer sees a.3.2.2 as: a.(3.2).2 -//assert 8 === a.3.2.2; // tokenizer sees 3.2 as a double -assert 8 === a.3[2][2]; -a[] = 5*2-3; -assert 7 === a.4; -((a))[ - // note that space/comments don't count here - ] = // nor on the RHS of a binary op -(2*5-2); -assert 8 === a.5; -assert 8 === a[4+1]; -assert 9 === ++a[5]; -assert 9 === a[5]++; -assert 10 === a[5]; - -a[3][2][] = 9; -assert 9 === a[3][2][3]; - -assert 'CWAL_SCR_SYNTAX'===catch{print[]/* must throw*/}.codeString(); - -// Must cause syntax errors: -assert catch{[1,2,3,,]}.message.indexOf('comma')>0; -assert catch{[1,2,,3]}.message.indexOf('comma')>0; -assert catch{[1,]}.message.indexOf('comma')>0; -assert catch{[,1]}.message.indexOf('comma')>0; -assert catch{[,]}.message.indexOf('comma')>0; -// Bugfix 20171111: disallow trailing semicolon in array value expressions: -assert catch{[1,2;]}.message.indexOf('semicolon')>0; -assert catch{[1;,2]}.message.indexOf('semicolon')>0; - -0 && scope { - var a = []; - assert 1 === (a[]=1); - assert 2 === a.length(); -}; - -a /* propagate it out for lifetime checks */; DELETED bindings/s2/unit/040-000-objects.s2 Index: bindings/s2/unit/040-000-objects.s2 ================================================================== --- bindings/s2/unit/040-000-objects.s2 +++ bindings/s2/unit/040-000-objects.s2 @@ -1,275 +0,0 @@ -/* Basic object literal tests. The prototype-supplied methods - are tested in a separate script. -*/ -const o = { - a: 1, b: 2, - c -: -3, - 4:'four', - 5: 5, - array: [2,4,6], - "removed": true -}; - -o.self = o; -o[((('d')))] = 42; -assert o === o.self; -assert 'array' === typename o.array; -assert 1 === o.a; -assert 2 === o['b']; -assert 3 === o.self['self'].self.c; -assert 'four' === o.4; -assert 5 === o[3+2]; -//assert 5 === o.(3+2); -assert o.removed; -assert undefined === unset o.self.removed, o.removed /*does not fail on missing properties*/; -assert 'undefined' === typename o.removed; - -//unset o.self /* cannot JSON-output cyclical objects */; -scope { - const proto = { a:0 }, o = { prototype:proto, a:1 }; - assert o inherits proto; - assert 1 === o.a; - unset o.a; - assert 0 === o.a; - unset o.a; - assert 0 === o.a; - - o.a = 1; - ++o.a; - assert 2 === o.a; - o.a += -1; - assert 1 === o.a; - - o.prototype = null /* 'null' or 'undefined' assignment removes a prototype... */; - assert undefined === o.prototype /* ... which looks like this afterwards */; - assert !(o inherits proto); - assert o !inherits proto; - assert 'CWAL_RC_TYPE' === catch{o.prototype = 1}.codeString(); -} - -scope { - const g = {x:0}; - assert 1 === ++g.x; - assert ++g.x < 3 /* must not error b/c of missing "this" via dot op */; - assert g.x++ === 2 /* must not error b/c of missing "this" via dot op */; - - // Former BUG: - g.x++ ? g.x-- : --g.x /* Must not error with: Unexpected LHS (X++ operation) for X?Y:Z operator. */; - g /* trailing expr. needed to trigger the bug. */; - ;; -} - -assert catch{{a:1, b:2, }}.codeString() === 'CWAL_SCR_SYNTAX'; -assert catch{{a:1,, b:2, }}.codeString() === 'CWAL_SCR_SYNTAX'; -assert catch{{,a:1, b:2, }}.codeString() === 'CWAL_SCR_SYNTAX'; -assert catch{{,}}.codeString() === 'CWAL_SCR_SYNTAX'; -// Bugfix 20171111: disallow trailing semicolon in value-part expressions: -assert catch{{a:1;}}.message.indexOf('semicolon')>0; -assert catch{{a:1;,b:0}}.message.indexOf('semicolon')>0; - -scope { - /** - Added 20171201: JS-like shortcut syntax: - - {a, b, c} ==> {a:a, b:b, c:c} - */ - const a = 1, b = "hi", c = [1,2,3]; - const o = {a, x:4, b, c, y: 5}; - assert 1 === o.a; - assert "hi" === o.b; - assert c === o.c; - assert 4 === o.x; - assert 5 === o.y; - assert catch{{x$x$x}}.codeString() === 'CWAL_RC_NOT_FOUND' /* unknown identifier */; - assert catch{{"x"}}.codeString() === 'CWAL_SCR_SYNTAX' /* non-identifier */; -} - -scope { - /* - Added 20191117: use an expression as an object literal key: - - const x = 'c'; - const o = {a:1, b: 2, [x]: 3} - */ - const x = 'c'; - const o = {prototype: null, a:1, b: 2, [x]: 3}; - assert 3 === o[x]; - assert 3 === o.c; - assert 1 === { - prototype:null, - [scope{ - for(;;) break "hi, "+"world"; - }]: 1 - }["hi, world"]; - - assert 1 === { - ["abc äbc"[4][0][0][0][0][0]]: 1 - }.ä; - - assert 0 === catch {{[x, break]: 1}} - .message.indexOf("Unhandled 'break' in object literal."); - assert 0 === catch {{[x, return]: 1}} - .message.indexOf("Unhandled 'return' in object literal."); - assert 0 === catch {{[x, continue]: 1}} - .message.indexOf("Unhandled 'continue' in object literal."); - ;; -} - -assert 999 === scope { - /* - 2016-02-03: testing fix: - - https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194 - - If it's broken, this will crash if built in debug mode, - triggering a cwal-level assertion, possibly a different one - depending on the type of the scope result value's type. - */ - {a: 999}.a - /* 2020-02-20: The code that was testing has since disappeared: it - seems that the related feature was no longer needed once cwal - added scope push/pop APIs and cwal_scope_pop2(), both of which - allowed that behaviour to be relegated to cwal. */ -}; - -scope { - /** 2020-02-06: dot-length operator (lhs.#) now works on non-list - containers, resolving to the number of properties. */ - assert 2 === import.# /* path+doPathSearch properties */; - assert 3 === {a:1, b:1, c:1}.#; -} - -scope { - /** 2020-02-18: {x:=y} sets x as a const property. */ - const p = {x:1}; - const o = {a:1, b:=2, c:3, prototype:=undefined}; - assert 2 === o.b; - assert 'CWAL_RC_CONST_VIOLATION' === catch {o.b = 1}.codeString(); - assert p === (o.prototype = p) - /* consting the prototype has no effect b/c we don't have a way - to flag/enforce that constraint at the C level. */; - assert 0 === o.clearProperties().# - /* also removes const properties! Bug or feature? */; - assert 1 === (o.b=1 /* constness was lost via clearProperties() */); -} - -scope { - /** 2020-02-18: x.y:=z sets x as a const property. */ - const o= {a:1}; - assert 2 === (o.a=2); - assert 3 === (o.a:=3); - assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a = 1}.codeString(); - assert 'CWAL_RC_CONST_VIOLATION' === catch {o['a'] = 1}.codeString(); - assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a := 1}.codeString(); - - assert 'CWAL_SCR_SYNTAX' === catch {o := 1}.codeString() - /* := op only works for property assignment. */; - assert 1 === o.#; - assert o === o.clearProperties(); - assert 0 === o.#; -} - -scope { - /** 2020-02-20: inline expansion of object properties into an - object literal, similar to JS's {...otherObj}, which it calls - "spread" syntax. We use the @ prefix because already use that - for @array expansion, which is semantically very similar to - what we use it for here. - */ - var o = {a: 1, b: 2, c:3}; - var o2 = {@o}; - assert o.# === o2.#; - foreach(o=>k,v) assert o2[k]==v; - - o2 = {@o, c:4}; - assert o.# === o2.#; - assert 4 === o2.c; - - o2 = {a:4, @o, d:5}; - assert o.#+1 === o2.#; - assert o.a === o2.a; - assert 5 === o2.d; - - o2 = {d:5, @o}; - assert o.#+1 === o2.#; - assert 5 === o2.d; - - o2 = {@{a:1, b:2}}; - assert 2 === o2.#; - assert 1 === o2.a; - assert 2 === o2.b; - - o2 = {@proc(){return {a:1,b:2}}(), c:3}; - assert 3 === o2.#; - assert 1 === o2.a; - assert 2 === o2.b; - assert 3 === o2.c; - - var ex = catch{ {a:1, @, b:1} }; - assert 'CWAL_SCR_SYNTAX' === ex.codeString(); - assert ex.message.indexOf('empty expression') > 0; - - ex = catch{ {@3} }; - assert 'CWAL_RC_TYPE' === ex.codeString(); - assert ex.message.indexOf('container') > 0; - - assert 'CWAL_RC_CONST_VIOLATION' === catch o2 = {a:=1, @o}.codeString(); - o2 = {@o, a:=3}; - assert 3 === o2.a; - assert 'CWAL_RC_CONST_VIOLATION' === catch {o2.a =1}.codeString(); - - o = { - // Special case: constructor in an object literal is - // assigned as hidden/const, and hidden properties - // are not iterated over but do count towards the - // container.# operator... - __new:proc(){} - }; - assert 1 === o.#; - o2 = {@o}; - assert 0 === o2.#; - - assert 'CWAL_RC_TYPE' === catch{ - /* - The "problem" with enums is that they can use either object - or hash storage internally, so we would need to - differentiate between the two in the @ expansion. We don't - currently do that, but maybe someday will. - - ^^^^ that's outdated. enums now always use hashes. - */ - {@enum {a,b,c}} - }.codeString(); -} - -scope { - /* 2021-06-24: ensure that vars declared in property access [] or () - are not leaked into the current scope. e.g. - - var o = {}; o[var x = 'hi'] = 1; assert 'hi'===x; - */ - var o = {}; - o[var xx = 'a']; - assert !typeinfo(isdeclared xx); - o.(var xx = 'b'); - assert !typeinfo(isdeclared xx); - /* But we want standalone (...) to run in the current scope. */ - 1 && (var xx = 'y'); - assert 'y' === xx; -} - -scope { - /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain - property key types... */ - const o = {}; - assert 'CWAL_RC_TYPE' === catch{ - o[new s2.Buffer()] = 1; - }.codeString(); - assert 'CWAL_RC_TYPE' === catch{ - o[[#]] = 1; - }.codeString(); -} - -o /* propagate top-most o out for lifetime checks */; DELETED bindings/s2/unit/050-000-func-call.s2 Index: bindings/s2/unit/050-000-func-call.s2 ================================================================== --- bindings/s2/unit/050-000-func-call.s2 +++ bindings/s2/unit/050-000-func-call.s2 @@ -1,161 +0,0 @@ - -scope { - // make sure we can chain calls... - const P = proc f(){return f}; - P.x = P; - assert P === P('from P()'); - assert P === P.x('from P.x()'); - assert P === P.x['x']('from P.x["x"]()'); - - // Demonstrate skip-mode's effect on function calls: - false && P.x.y.z(foo); - true || P.x.y.z(foo); - - assert P === (P.x)('from (P.x)()'); - - var o = { - p: P - }; - assert P === o.p('from o.p()'); - - if(s2.isCallable){ - assert s2.isCallable(s2.isCallable, false); - assert s2.isCallable(s2.isCallable, true); - assert s2.isCallable(s2.isCallable); - o = { - prototype: proc(){ - ++this.value; - this.args = argv; - return argv; - }, - value: 0 - }; - assert 0 === o.value; - assert undefined === o.args; - assert o(1,2,3) === o.args; - assert 1 === o.value; - assert 3 === o.args.2; - assert s2.isCallable(o) /* searches up through prototypes */; - assert !s2.isCallable(o,false) /* does not search prototypes */; - assert s2.isCallable(o,true) /* searches up through prototypes */; - } -} - -scope { // exploring "callable" objects... - var obj = { - foo: proc(){ - assert this === obj; - } - }.eachProperty(proc(k,v){ - //print(__FLC,'eachprop',k); - v.importSymbols(nameof this); - }); - - // let's change up "this" a bit... - var f = obj.foo; - obj.foo(); - f(); - obj.bar = proc(name){ - //print(name,this); - return this === eval -> name; - }; - assert obj.bar(nameof obj); - var x =obj.bar; - assert x(nameof x); -} - -scope { - /* Prior to 20181015, completely empty functions were optimized - away at call()-time. This led to side-effects in their - parameter/argument lists not happening, e.g.: proc(){}(assert 0) did not - fail. Make sure that's no longer the case... - - We now optimize away the call() part but still process the - argument list, if any, so that side-effects can trigger. - */ - var f = proc(){}, x = 3, y; - f(y=x); - assert x === y /* side-effects in argument list trigger */; - assert (catch proc(){}(affirm 0)).message.indexOf('Affirmation') >= 0 /* affirmation must trigger */; - - /* Let's test some more complicated cases involving default - parameter values which use call()-time imported symbols... */ - y = 0; - f = proc callee(a=(y=Z), b=(z=callee.foo)){} using{Z:2}; - f.foo = 7; - assert 0 === y; - var z; - f(); - assert 2 === y /* side effect of default param value using imported symbol */; - assert 7 === z /* side effect of default param value using imported symbol */; - y = 0; - f.foo = 8; - f(1); - assert 0 === y /* skipped side effect of default param value */; - assert 8 === z /* side effect of default param value using imported symbol */; - z = 0; - f(1,2); - assert 0 === z /* skipped side effect of default param value */; - - z = 0; - f = proc(a=(z=this.foo)){}; - f.foo = 7; - f(); - assert 7 === z /* 'this' was set up before default parameter values were processed */; -} - -scope { - // Check for handling of errant continue/break... - // Prior to 20181104 continue/break in call param lists - // were handled incorrectly. - - const ar = [], f=proc(){}; - foreach(@[1,2,3]=>v) f(ar[] = v%2 ? v : continue); - assert 2 === ar.#; - assert 1===ar.0; - assert 3===ar.1; - - ar.length(0); - foreach(@[1,2,3]=>v) f(ar[] = v%2 ||| break); - assert 1 === ar.#; - assert 1===ar.0; - - var ex = catch foreach(@ar=>v) proc(){continue}(); - assert typeinfo(isexception ex); - assert 0===ex.message.indexOf("Unhandled 'continue'"); - - ex = catch foreach(@ar=>v) proc(){break}(); - assert typeinfo(isexception ex); - assert 0===ex.message.indexOf("Unhandled 'break'"); -} - -scope { - /** - 20190820: confirm fix of @-expansion bug when a newline preceeds - the @expr in a function call. */ - const f = proc(){}, args = [1,2,3]; - f(1,2,3, - @args); - // ^^^ previously, that would fail with "'@' is not allowed here -} - -scope { - /** - 2020-02-18: var keyword now supports the := op to assign as - const, but it is explicitly disabled for function parameters - (which are handled by the same C-level code - (s2_keyword_f_var_impl())) because of inconsistencies in - parameters with and without default values. Namely, we can - easily make the argument to proc(a:=2){...} const whether or - not an argument is passed to the function call but we have no - syntax to saying that it sould be const unless it has a default - value. e.g. we could not make that same parameter const in - proc(a){...}. - */ - - var f = proc(a:=1){} /* params are not parsed yet, so won't fail - here. */; - var ex = catch f(); - assert 'CWAL_SCR_SYNTAX' === ex.codeString(); - assert ex.message.indexOf(':=') > 0; -} DELETED bindings/s2/unit/060-000-numbers.s2 Index: bindings/s2/unit/060-000-numbers.s2 ================================================================== --- bindings/s2/unit/060-000-numbers.s2 +++ bindings/s2/unit/060-000-numbers.s2 @@ -1,162 +0,0 @@ -scope { - /* Some sanity checks... */ - assert 0xffff === 0xf_f__f___f; - assert 0b101 === 5; - assert 0b101 === 0b_1__0___1; - assert 0x13 === 0b_0001_0011; - assert 123 === 1_2__3; - assert 501 === 0o7_6__5; -} - -scope { - //print(0.INT_MIN, 0.INT_MAX); - assert 0.INT_MIN < 0; - assert 0.INT_MAX > 0; - - assert 0.INT_MIN-1 === 0.INT_MAX /* This (signed underflow/overflow) "should" be is platform-dependent: */; - assert 0.INT_MAX+1 === 0.INT_MIN /* This (signed underflow/overflow) "should" be is platform-dependent: */; - - var i = 0; - assert 'integer' === typename i; - assert 0 === i.compare(0); - assert i.compare(1) < 0; - assert i.compare(-1) > 0; - assert 0.0 === i.toDouble(); - assert 0 !== i.toDouble(); - assert "0" === i.toJSONString(); - assert "0" === i.toString(); - assert "1" === 1.toString(); - i = 42; - assert '*' === i.toChar(); -} - -scope { - var d = 0.0; - assert 'double' === typename d; - assert 0 === d.compare(0); - assert d.compare(1) < 0; - assert d.compare(-1) > 0; - assert 0 === d.toInt(); - assert 0.0 !== d.toInt(); - assert "0.0" === d.toJSONString(); - assert "0.0" === d.toString(); - assert "1.0" === 1.0.toString(); - assert 1 === 0.5.ceil(); - assert 0 === (-0.5).ceil(); // parens needed: dot has higher precedence than unary -. - //assert 0 === 0.ceil(); // INTEGER 0 doesn't have ceil() - assert 0 === 0.0.ceil(); - assert -1 === (-1.0).ceil(); - assert -1 === (-1.01).ceil(); - assert 0 === (-0.999).ceil(); - assert -2 === (-2.2).ceil(); - assert 3 === 2.2.ceil(); - assert 2 === 2.0.ceil(); - - assert 1 === 1.5.floor(); - assert 1 === 1.0.floor(); - assert 0 === 0.0.floor(); - assert 0 === 0.5.floor(); - assert -1 === (-0.5).floor(); - assert -2 === (-1.5).floor(); -} - -scope { - 1.0.prototype.twice = proc(){ return this * 2 }; - assert 6.2 === 3.1.twice(); - assert 8.0 === (2.0*2).twice(); - //unset 0.0.prototype.twice; // unset doesn't like this, so... - var d = 0.0; - unset d.prototype.twice, d; -} - -scope { // number.parseInt/Double/Number() - const pi = 0.parseInt, pf = 0.parseDouble, pn = 0.parseNumber; - assert 1 === pi(1); - assert 1 === pi('1'); - assert undefined === pn('1_'); - assert undefined === pi('1_'); - assert 1 === pi('1.0'); - assert -1 === pi('-1'); - assert -1 === pi('-1.2'); - assert undefined === pi('1-1'); - assert 1.0 === pf(1); - assert 1.0 === pf('1'); - assert 1.0 === pf(true); - assert 0.0 === pf(false); - assert 0.0 === pf('-0'); - assert 1 === pn(1); - assert 1 === pn('1'); - assert 1.0 === pn(1.0); - assert 1.0 === pn('1.0'); - assert 0 === pn('-0'); - assert 0.0 === pn(' + 0.0'); - assert 56 === pn('0o70'); - assert 56 === pn('0o7_0'); - assert 56 === pn('0o_7__0'); - assert undefined === pn('0o1_')/*trailing non-digit*/; - assert undefined === pn('0o18')/*trailing non-[octal-]digit*/; - assert -1 === pn('-0o1'); - assert -1 === pn('-0x0001'); - assert undefined === pn('-0x0001.')/*trailing non-digit*/; - assert undefined === pn(pn); - assert pf('1') === pn('1.0'); - - assert 'double' === typename pn("1.3") /* parseNumber() keeps the numeric type */; - assert 'integer' === typename pn("1") /* parseNumber() keeps the numeric type */; - assert 'integer' === typename pi("1.3") /* parseInt() reduces to an integer */; -} - -scope { // number.nthPrime() - assert 2 === 0.nthPrime(1); - assert 7919 === 0.nthPrime(1000); - assert 'CWAL_RC_RANGE' === catch {0.nthPrime(0)}.codeString(); - assert 'CWAL_RC_RANGE' === catch {0.nthPrime(1001)}.codeString(); - assert 'CWAL_RC_MISUSE' === catch {0.nthPrime()}.codeString(); -} - -scope { // toString() - assert '1' === 1.toString(); - assert '1.0' === 1.0.toString(); - assert '1' === 1.0.toString('d'); - assert '000c' === 12.toString('04x'); - assert '000C' === 12.toString('04X'); - assert '0e' === 0b11_10.toString('02x'); - assert 'FBF' === 0x_f_B_f.toString('X'); - assert '765' === 0o7_6_5.toString('o'); - assert '765' === 0b111_110_101.toString('o'); -} - -scope { - assert 0x_f_0 === 2_4__0; - /* Interesting: we can't catch these errors because they trigger - in the tokenizer while slurping the {...} blocks. That means - that catch cannot really know that it "could" safely convert - these fatal syntax errors to non-fatal exceptions. */ - //assert 0 === catch {1_}.message.indexOf('Malformed'); - //assert catch {1_2.3}.message.indexOf('not legal') > 0; - /* So... to test these we'll wrap them in strings and eval them in - the 2nd-pass phase, as 2nd-pass eval knows that it can convert - fatal syntax errors to non-fatal exceptions. Note that using - eval=>{...} to capture them as strings cannot work here for the - same timing reason. */ - assert 0 === catch -> {'1_'}.message.indexOf('Malformed numeric literal'); - assert 0 === catch -> {'1.2_'}.message.indexOf('Malformed numeric literal'); - assert 0 === catch -> {'0b1_'}.message.indexOf('Malformed binary'); - assert 0 === catch -> {'0o1_'}.message.indexOf('Malformed octal'); - assert 0 === catch -> {'0x1_'}.message.indexOf('Malformed hex'); - assert catch -> {'1_2.3'}.message.indexOf('not legal in floating-point') > 0; -} - -if(0.INT_MAX > 0xFFFF_FFFF /* 64 bit */) { - const largeI = 0xffff_ffff_ffff, - largeD = largeI.toDouble(); - assert 0.parseNumber(largeD.toString()) === largeD; - assert 0.parseNumber(largeI.toString()) === largeI; - - const largeD2 = 10.356 + 0xffffffffffff, - d2Str = largeD2.toString(); - /* was "2.814749767107e14" prior to 20181127, but now - it's "281474976710665.4" (or thereabouts). */ - assert d2Str.indexOf('e')<0 /* no scientific notation */; - assert 0.parseNumber(d2Str) === largeD2; -} DELETED bindings/s2/unit/070-000-enum.s2 Index: bindings/s2/unit/070-000-enum.s2 ================================================================== --- bindings/s2/unit/070-000-enum.s2 +++ bindings/s2/unit/070-000-enum.s2 @@ -1,318 +0,0 @@ - - -const countEach = proc callee(k,v){ - if(1===++callee.count){ - assert 'unique' === typeinfo(name v); - assert 'string' === typeinfo(name k); - assert this inherits enumProto; - } -}; - -const enumProto = (enum {a}).prototype; - -scope { - assert 'enum' === typeinfo(name enum{a,b,c}); - assert 'CWAL_SCR_SYNTAX' === catch{enum {a,b,c, /* extra trailing comma */}}.codeString(); - assert 'CWAL_SCR_SYNTAX' === catch{enum {a,,c /* extra comma */}}.codeString(); - assert 'CWAL_SCR_SYNTAX' === catch{enum {a:,,c /* missing value after ':' */}}.codeString() -} - -scope { - const E = enum MyEnum { - // This small enum will use an Object for storage. - A, B, C - }; - - assert 'MyEnum' === typeinfo(name E); - assert E inherits enumProto; - assert 3 === E.#; - assert E.A; - assert undefined === E.A.prototype - /* prototype pseudo-property is allowed but (currently) evals - to undefined */; - assert E.hasEnumEntry(E.A); - assert E.hasEnumEntry('A'); - assert !E.hasEnumEntry('a'); - assert 'C' === E[E.('C')]; - assert catch {E.x}.message.indexOf('Unknown')>=0; - assert catch {E.A = 1}.message.indexOf('disallowed')>=0; - var ar = E.getEnumKeys(); - assert 'array' === typeinfo(name ar); - assert 3 === ar.length(); - assert ar.indexOf('C') >= 0; - assert ar.indexOf('D') < 0; - - E.eachEnumEntry(countEach); - assert 3 === countEach.count; -} - -scope { - const Big = enum { - ☺,b,c,d,e, - f,g,h,i,j, - k,l,m,n,o, - p,q,r,s,t, - u,v, - こんにちは - }; - const bigLen = Big.#; - assert 23 === bigLen; - assert 'enum' === typeinfo(name Big); - assert Big inherits enumProto; - assert 'c' === Big[Big.c]; - assert '☺' === Big[Big.☺]; - assert 'こんにちは' === Big[Big.こんにちは]; - assert Big.こんにちは === Big.'こんにちは'; - assert Big[<<'こんにちは'; - assert catch {Big.z}.message.indexOf('Unknown')>=0; - assert catch {Big.b = 1}.message.indexOf('disallowed')>=0; - - var ar = Big.getEnumKeys(); - assert 'array' === typeinfo(name ar); - assert bigLen === ar.length(); - assert ar.indexOf('☺') >= 0; - assert ar.indexOf('B') < 0; - - countEach.count = 0; - Big.eachEnumEntry(countEach); - assert bigLen === countEach.count; -} - -scope { - var x = enum {a,b,c,d,e,f,g,h,i,j,k}; - assert 'unique' === typeinfo(name x.a); - /** - 20191210: we may disallow enum prototype reassignment in the - future, so don't rely on this feature. It's not a question of - whether it makes sense to allow it, but whether or not it's a - tolerable inconsistency vis-a-vis the disallowing of setting - non-prototype properties. For relevant commentary, search for - comments in s2.c near references to - CWAL_CONTAINER_DISALLOW_PROP_SET. "The problem" is that if we - allow it here, we always have to allow it on other "sealed" - objects, such as the one which the s2out keyword resolves to. - */ - x.prototype = { - prototype: x.prototype, - z: 3 - }; - assert 'enum' === typeinfo(name x); - assert x.a; - assert 3===x.z; - assert 'CWAL_RC_DISALLOW_PROP_SET' - === catch{x.z = 1}.codeString(); - // But... - x.prototype.z = 4; - assert 4===x.z /* modified in (mutable) prototype */; - var ar = x.getEnumKeys(); - assert 'array' === typename ar; - assert x.# === ar.length(); - assert 'CWAL_RC_NOT_FOUND' - === catch{x['x']}.codeString(); - -} - -scope { - - var x = enum {'a',b,c,d,e,f,g,h,i,j,k}; - assert 'unique' === typename x.a; - assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch x.z = 1).codeString(); - assert catch {x.z}.message.indexOf('Unknown')>=0; - x.prototype = { - prototype: x.prototype, - z: 3 - }; - assert undefined === catch {x.z}; - assert 'enum' === typename x; - assert x.a; - assert 3===x.z; - ++x.prototype.z; - assert 4 === x.prototype.z; - - assert 'a' === x.a.value; - assert 'a' === x::a; - - var e = enum { E, A: 'Aa', "B": 'abc', C: 13, OBJ: {x: 1, y:0}, D}; - assert 'enum' === typeinfo(name e); - assert 'Aa' === e.A.value; - assert 'abc' === e.B.value; - assert 13 === e.C.value; - assert 0 === e.OBJ.value.y; - assert 1 === e.OBJ['value'].x; - assert 1 === e.OBJ.('va'+'lue').x; - assert 'CWAL_RC_TYPE' === catch (e.OBJ.invalid).codeString(); - assert e[e.A] === 'A'; - assert 'D' === e.D.value; - assert 2 === e.A.value.length(); -} - -scope { - const e = enum { - a: {x:1, y:undefined}, - b: proc(){return 1}, - c, - d - }; - assert 4 === e.#; - assert 1 === e.a.value.x; - assert 1 === e::a.x; - assert 1 === e::'a'.x; - assert 1 === e.b.value(); - assert 1 === e::b(); - assert undefined === e.a.value.y++; - assert e.a.value.x === e.a.value.y; - assert 2 === ++e::a.y; - assert 2 === e::a.y; - assert 2 === e::(scope{while(true) break 'a'}).y - /* reminder: parens needed b/c dot/dotdot treat all identifiers - on the RHS equally, and does not expand keywords as keywords. */; - assert e.c; - assert 'c' === e::c; - assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch e.a = 1).codeString(); - assert 'CWAL_RC_NOT_FOUND' === catch{e.x}.codeString(); - assert 'CWAL_RC_NOT_FOUND' === catch {e::x}.codeString(); -} - -scope { - scope { - var e = enum {a:{x:1},b:{x:2}}; - e.a.value.b = e.b; - e.b.value.a = e.a; - } - assert 1 /* must not crash during prior scope's cleanup (former - cwal bug in cleanup handling of Unique-type values).*/; - /* Must also properly upscope everything... */ - assert 200 === scope { - var e = enum {a:{x:100},b:{x:200}}; - e::a.b = e.b; - e::b.a = e.a; - assert e.a.value.b === e::a.b; - assert e::a.b === e.b; - e.a.value.b/*===e.b*/ - }.value.a/*===e.a*/.value.b.value.x; - assert scope { - enum {a:{x:100}}::a - }.x === 100; - assert enum {a:{x:-100}}::a.x === -100; -} - -scope { - const o2e = proc(obj, name) { - affirm typeinfo(iscontainer obj); - var b = new s2.Buffer(100).append("enum ", name ||| '', '{'), - first = true; - foreach(obj=>k){ - first ? first = false : b.append(", "); - b.append(k , ": obj.",k); - } - return b.append("}").evalContents(__FLC); - }; - var obj = {a:1, b:{x:2}}; - var e = o2e(obj, 'blah'); - assert 'blah' === typeinfo(name e); - assert 2 === e.#; - //print(__FLC, e); - foreach(obj=>k, v) { - assert e::(k) === v; - assert e->(k).value === v; - assert e.(k).value === v; - assert v === ('e.'+k+'.value').evalContents(__FLC); - assert v === ('e::'+k).evalContents(__FLC); - } - - assert enum{a:enum{x:30}}::a::x + enum{b:30}::b === 60 /* lifetime(s) check */; -} - -scope { - const e = enum { - f1: proc(){assert 'enum' === typeinfo(name E)}, - f2: proc(){assert 'unique' === typeinfo(name E.f1)} - }; - const imports = {E:e}; - foreach(e=>k,v) v.value.importSymbols(imports); - e::f1(); - e::f2(); - e::f1.z = e.f1 - /* will propagate e.f1 out of the scope. Formerly this crashed - during scope cleanup due to a mis-assertion in the Unique-type's - finalizer. Let's see what happens to it... */; -} - -scope { - /* 20191210: it's now possible (though probably not wise) to set - the prototype property in an enum's body. Before this, that - handling was not well-defined. */ - const x = {__typename: "X"}; - assert catch {enum {prototype:x}}.message.indexOf("at least one") > 0; - var e = enum {prototype:x, a}; - assert 'X' === typeinfo(name e); - assert e inherits x; - assert typeinfo(isenum e); - e = enum eee {prototype:x, b, c}; - assert 'eee' === typeinfo(name e); - assert e inherits x; - assert typeinfo(isenum e); - - e = enum hhh {prototype:x, b, c, d, e, f, g, h, i, j, k}; - assert 'hhh' === typeinfo(name e); - assert e inherits x; - assert e.prototype === x; - assert typeinfo(isenum e); - - e = enum { prototype: null /* remove prototype */, a }; - assert catch {e.prototype}.message.indexOf('Unknown')===0; - /** - Interestingly, this formulation doesn't work: - - assert catch e.prototype.message.indexOf('Unknown')===0; - - Unexpected consecutive non-operators: - #3006 (KeywordCatch) ==> #1011 (Identifier) - - At the ".message" part. Because... ??? - - Hmmm. - */ - - ;;; /* <=== for vacuum testing */ -} - -scope { - /* As of 2020-02-21, enums are always hashes, but the the - restriction against using the hash search operator persists - because it would otherwise behave exactly as the -> operator. - typeinfo(ishash) still evals to false for enums. - */ - const e = enum{a}; - assert typeinfo(isunique e.a); - assert 'a' === e::a; - assert e.a === e->'a'; - assert catch {e#'a'}.codeString()==='CWAL_RC_TYPE' /* hash op is not allowed on enums */; - assert e::a === e.'a'.value; - assert typeinfo(is-enum e); - assert !typeinfo(is-hash e); -} - -scope { - /* Examples from the user docs... */ - const e = enum OptionalTypeName { - a, // its own name (a string) as its value - b: 2, // any value type is fine - f: proc(){return this} - }; - assert 3 === e.#; - assert 'a' === e.a.value; - assert 'a' === e::a; // equivalent - assert 2 === e.b.value; - assert 2 === e::b; // equivalent unless the entry.value is a Function call: - assert e.f === e.f.value(); // e.f is bound as 'this' - assert e::f === e::f(); // no 'this', so e.f.value is its own 'this' - assert 'b' === e[e.b]; // get the string-form name of an entry - assert e.a === e['a']; // note that e['a'] is functionally identical to e.a. - assert 'OptionalTypeName' === typeinfo(name e); - assert undefined === e->'c'; //-> op does not throw for unknown properties - assert catch {e#'c'}.codeString()==='CWAL_RC_TYPE'; // hash op not allowed - assert catch {e::c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property - assert catch {e.c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property - assert catch {e.a = 1}.codeString()==='CWAL_RC_DISALLOW_PROP_SET'; -} DELETED bindings/s2/unit/099-000-typeinfo.s2 Index: bindings/s2/unit/099-000-typeinfo.s2 ================================================================== --- bindings/s2/unit/099-000-typeinfo.s2 +++ bindings/s2/unit/099-000-typeinfo.s2 @@ -1,198 +0,0 @@ -/* - typeinfo keyword tests... -*/ - -assert typeinfo(isbool true); -assert typeinfo(is-bool false); -assert !typeinfo(isbool 1); - -assert typeinfo(isinteger 3); -assert !typeinfo(is-integer 3.3); -assert typeinfo(isdouble 3.3); -assert !typeinfo(is-double 3); -assert typeinfo(hasprototype 3); - -scope { - const b = s2.Buffer.new(); - assert typeinfo(isbuffer b); - assert typeinfo(hasbuffer b); - assert !typeinfo(is-buffer {prototype:b}); - assert typeinfo(has-buffer {prototype:b}); - assert !typeinfo(isbuffer s2.Buffer); - assert !typeinfo(hasbuffer s2.Buffer); - assert typeinfo(cannew s2.Buffer); - assert typeinfo(can-new s2.Buffer); - assert !typeinfo(isnewing s2.Buffer); - assert !typeinfo(is-newing s2.Buffer); - /* is-newing positive test is in the 'new' unit tests */ - assert !typeinfo(cannew 0); - assert !typeinfo(can-new 0); -} - -assert !typeinfo(isdeclared hasNative); -assert !typeinfo(islocal hasFunction); - - -const obj = {}, - ar = [], - h = {#}, - hasHash = {prototype:h}, - hasArray = {prototype:ar}, - f = proc(){}, - hasFunction = {prototype:f}, - e = enum {a:undefined,b}, - ex = exception(0,0), - hasEx = {prototype: ex}, - pf = new s2.PathFinder(), - hasNative = {prototype:pf} -; - -assert catch { - obj = {a:1} - /* 20160226: BUG: when hashes are used as scope storage, const - violations are not caught! Not sure why - a cursory examination - seems to imply that all const checks are in place. - - Can't reproduce it in the interactive shell!?!? - - Was caused by the automatic resizing of hashes losing their - flags. - */; -}; - -assert typeinfo(is-declared hasFunction); -assert typeinfo(is-local hasFunction); -scope{ - var x; - assert !typeinfo(islocal hasFunction); - assert typeinfo(islocal x); -} - -assert typeinfo(hasprototype obj); -assert !typeinfo(has-prototype {prototype:null}); - -assert typeinfo(hasobject obj); -assert typeinfo(isobject obj); -assert typeinfo(has-object ar); -assert !typeinfo(is-object ar); -assert typeinfo(iscontainer ar); - -assert typeinfo(hasobject 0); -assert !typeinfo(isobject 0); -assert !typeinfo(hasobject null); - -assert typeinfo(isderefable obj); -assert typeinfo(is-derefable ar); -assert typeinfo(isderefable f); -assert typeinfo(iscontainer f); -assert typeinfo(isderefable 0); -assert !typeinfo(is-container 0); - -assert !typeinfo(isderefable null); -assert !typeinfo(iscontainer null); -assert !typeinfo(isderefable undefined); -assert !typeinfo(isderefable true); -assert typeinfo(isderefable e.a); -assert !typeinfo(isderefable e.a.value); - -assert typeinfo(isarray ar); -assert typeinfo(hasarray ar); -assert typeinfo(islist ar); -assert !typeinfo(is-array obj); -assert !typeinfo(has-array obj); -assert !typeinfo(isarray hasArray); -assert typeinfo(hasarray hasArray); -assert !typeinfo(islist hasArray); - -assert typeinfo(iscallable f); -assert typeinfo(is-callable hasFunction); -assert typeinfo(isfunction f); -assert !typeinfo(is-function hasFunction); - -assert typeinfo(isenum e); -assert typeinfo(is-enum e); -assert !typeinfo(isenum obj); -assert typeinfo(isunique e.a); -assert typeinfo(is-unique e.a); - -assert !typeinfo(hashash obj); -assert !typeinfo(ishash obj); -assert !typeinfo(is-hash e) - /* 2020-02-21: though enums are technically hashes, we - report them here as non-hashes because not all hash - operations work on enums and reporting them as such - might lead script code to inadvertently send an enum - down a hash-only code path. */; -assert !typeinfo(has-hash e); - -assert typeinfo(has-hash h); -assert typeinfo(is-hash h); -assert typeinfo(hashash hasHash); -assert !typeinfo(ishash hasHash); - -assert typeinfo(isexception ex); -assert typeinfo(hasexception hasEx); -assert !typeinfo(is-exception hasEx); -assert typeinfo(has-exception ex); - - -assert typeinfo(isinteger 1); -assert !typeinfo(is-integer '1'); -assert typeinfo(isdouble 1.0); -assert !typeinfo(is-double 1); - -assert typeinfo(isnumber 1); -assert !typeinfo(is-number true); -assert typeinfo(isnumeric true); -assert typeinfo(is-numeric '3'); -assert !typeinfo(isnumeric '3x'); -assert typeinfo(isnumeric '-3.3'); -assert typeinfo(isnumeric '+0o7'); -assert !typeinfo(isnumeric '0o8'); - -assert typeinfo(isnative pf); -assert typeinfo(hasnative pf); -assert !typeinfo(is-native obj); -assert !typeinfo(isnative hasNative); -assert typeinfo(has-native hasNative); -assert !typeinfo(hasnative obj); - -assert typeinfo(isstring ''); -assert !typeinfo(is-string 1); -assert typeinfo(isstring <<k){ - assert typeinfo(may-iterate-props obj); - assert typeinfo(may-iterate obj); - assert !typeinfo(may-iterate-list obj); - assert typeinfo(is-iterating-props obj); - assert !typeinfo(is-iterating-list obj); - assert typeinfo(is-iterating obj); -} -assert !typeinfo(isiterating obj); -assert !typeinfo(isiteratingprops obj); -assert !typeinfo(isiteratinglist obj); - -scope { - var t0 = [#/* empty tuple is a built-in constant */], - t2 = [#1,2]; - assert typeinfo(istuple t0); - assert typeinfo(is-tuple t2); - assert typeinfo(islist t0); - assert typeinfo(islist t2); -} - DELETED bindings/s2/unit/100-000-object-methods.s2 Index: bindings/s2/unit/100-000-object-methods.s2 ================================================================== --- bindings/s2/unit/100-000-object-methods.s2 +++ bindings/s2/unit/100-000-object-methods.s2 @@ -1,166 +0,0 @@ - -var o = {a:1, 2:'two', __typename:'Fred'}; -scope { - assert 'Fred' === typename o; - assert o.hasOwnProperty('a'); - assert !o.hasOwnProperty('hasOwnProperty'); - assert o.prototype.hasOwnProperty('hasOwnProperty'); - assert o.hasOwnProperty(2); - assert pragma(build-opt CWAL_OBASE_ISA_HASH) - ? !o.hasOwnProperty('2') : o.hasOwnProperty('2'); - unset o.2; - assert !o.hasOwnProperty(2); - assert 0 != o.compare({}); - assert 0 === o.compare(print,print); - assert 0 === o.compare(o); - assert 0 === o.compare(o,o); - assert 0 === o.compare(1,1); - assert o.compare(1,-1) > 0; - assert o.compare(-1,1) < 0; -} - -scope { - var k = o.propertyKeys(); - assert 'array' === typename k; - assert 'string' === typename k.0; - //print('o.propertyKeys() ==',k); -} - - -scope { - o.clearProperties(); - assert 'object' === typename o; - assert undefined === o.a; -} - -scope { - assert !o.hasOwnProperty('x'); - o.x = 1; - assert 1 === o.x; - assert o.hasOwnProperty('x'); - o.unset('x'); - assert undefined === o.x; - assert !o.hasOwnProperty('x'); -} - -scope { - assert o.mayIterate(); -} - -scope { - o.a = 3, o.b = 7, o.c = 42.24, o.d = [1,2,3], o.1 = -1; - const json = o.toJSONString(' '); - const o2 = eval -> json; - assert typename o === typename o2; - assert o2.d[1] === o.d[1]; - if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){ - /*only works if !CWAL_OBASE_ISA_HASH, else order is undefined*/ - assert o2.toJSONString(1) === json; - const abc = o.toJSONString('abc'); - assert abc.indexOf('abc')>0; - assert abc.toLower() === o2.toJSONString('ABC').toLower(); - } -} - -scope { - o.x = -1; - assert -1 === o.get('x'); - o.set('x',1); - assert 1 === o.get('x'); - o.set({Z: 'zzz'}); - assert 'zzz' === o.Z; - assert true === o.unset('x','Z'); - assert false === o.unset('x','Z'); - assert undefined === o.Z; - //o.compare(); // check that exception contains call-point line/col info -} - -scope { - o.s = o; - var counter = 0; - o.p = proc f(){ - counter = counter + 1; - return f; - }; - o.('p')(__FLC)(__FLC); - assert 2===counter; - counter = 0; - o.s.p(__FLC)(__FLC)(__FLC); - assert 3===counter; - counter = 0; - o['s'].('p')(__FLC); - o['s'].s.('p')(__FLC); - o['s'].s['p'](__FLC); - assert 3===counter; - o; -} - -scope { - var counter = 0, lastKey, lastVal; - - const f = proc(k,v){ - assert this.mayIterate()/*as of 20191212*/; - counter = counter + 1; - //print(k,v); - lastKey = k; - lastVal = v; - }; - assert o.mayIterate(); - o.eachProperty(f); - assert o.mayIterate(); - assert counter === o.propertyKeys().length(); - assert undefined !== lastKey; - assert undefined !== lastVal; - assert lastKey !== lastVal; -} - -scope { - /* Ensure that an object which inherits Function is call()able - and that 'this' resolution works right. */ - var x = 0; - var f = proc callee(){ - assert this inherits callee; - assert this !== callee /* 'this' is one step down the prototype chain */; - /* 'this' for non-property call() === the function resp. the call()able */; - ++x; - ++this.z; - }; - var y = { prototype: f, z: 1 }; - assert y inherits f; - y(); - assert 1 === x; - assert 2 === y.z; -} - -scope { - var o = { - }; - assert o.isEmpty(); - assert 0 === o.propertyCount(); - o.a = 1, o.b = 2; - assert !o.isEmpty(); - assert 2 === o.propertyCount(); - - var y = {}; - var x = o.copyPropertiesTo(y, {}); - assert x !== o; - assert x !== y; - assert 2 === x.propertyCount(); - foreach(x=>k,v){ - assert v === o[k]; - assert v === y[k]; - }; - - assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo(1)}.codeString(); - assert 'CWAL_RC_MISUSE' === catch{x.copyPropertiesTo(/*no args*/)}.codeString(); - assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo.call(0/*not a container*/, 1)}.codeString(); -} - -scope { - const o = {}; - assert o === o.withThis(proc(){}); - assert o === o.withThis(proc(){return}); - assert 1 === o.withThis(proc(){return 1}); -} - -o; // propagate this out to make sure it survives the propagation process w/o being cleaned up. DELETED bindings/s2/unit/100-050-overloading.s2 Index: bindings/s2/unit/100-050-overloading.s2 ================================================================== --- bindings/s2/unit/100-050-overloading.s2 +++ bindings/s2/unit/100-050-overloading.s2 @@ -1,394 +0,0 @@ -/* Demonstrate operator overloading... */ - -if(1){ - assert "3.00.1" === "3.0"+0.1 /* bug chasing */; - - var pCount = 0, mCount = 0, o = { - a: 0, - // Binary X+Y - 'operator+': proc(r){ - assert 1 === argv.length(); - ++pCount; - return this.a + r; - }, - // X+=Y - 'operator+=': proc(r){ - //print('operator+=',argv); - assert 1 === argv.length(); - ++pCount; - this.a += r; - return this; - }, - // X-Y - 'operator-': proc(r){ - //print('operator-',argv); - assert 1 === argv.length(); - ++mCount; - return this.a - r; - }, - // X-=Y - 'operator-=': proc(r){ - //print('operator+=',argv); - assert 1 === argv.length(); - ++mCount; - this.a -= r; - return this; - }, - // X++ - 'operator++': proc(){ - //assert this===argv.0 /* historical! */; - assert !argv.#; - ++pCount; - this.a++; - return this; - }, - // ++X - '++operator': proc(){ - assert !argv.0; - ++pCount; - ++this.a; - return this; - }, - // X-- - 'operator--': proc(){ - //assert this===argv.0 /* historical! */; - assert !argv.#; - ++mCount; - this.a--; - return this; - }, - // --X - '--operator': proc(){ - assert !argv.0; - ++mCount; - --this.a; - return this; - }, - // -X - '-operator': proc(){ - //print('-operator', argv); - return -this.a; - }, - // X==Y - 'operator==': proc(r){ - // very minimal impl which does not detect - // an RHS of the same class as this. - return ((typeinfo(name this)) === (typeinfo(name r))) - ? this.a === r.a - : this.a == r; - }, - // X!=Y - 'operator!=': proc(r){ - return !(this==r); - }, - // XY - 'operator>': proc(r){ - return ((typeinfo(name this)) === (typeinfo(name r))) - ? this.a > r.a - : this.a > r; - } - }; - //print("Properties:", o.propertyKeys()); - assert(!o.prototype.'operator+'); - assert ++o === o; - assert 1 === o.a; - assert 1 === pCount; - assert o++ === o; - assert 2 === o.a; - assert 2 === pCount; - assert --o === o; - assert 1 === o.a; - assert 1 === mCount; - assert o-- === o; - assert 0 === o.a; - assert 2 === mCount; - //assert 2 === (o += 2); - - //print('o =',o); - assert o === (o += 2); - //print('o =',o); - assert 4 === o + 2; - assert 2 === o.a; - assert 1 === o - 1; - //assert 'CWAL_RC_NOT_FOUND' === catch{-o/*no such operator*/}.codeString(); - assert 'CWAL_RC_NOT_FOUND' === catch{+o/*no such operator*/}.codeString(); - //print("Properties:", o.propertyKeys()); - assert 2 === o.a; - o -= 1; - assert 1 === o.a; - assert -1 === -o; - assert 1 === o.a; - - - assert !(o < 1); - assert o == 1; - assert o != 2; - assert o < 2; - assert o > 0; - - assert 2 === ++o.a; - assert 2 === o.a++; - assert 3 === o.a; - assert o == 3; - assert !(3 == 0) /* b/c only LHS is checked for overloads */; - - assert catch{o * 1 /* no such op */}; - assert catch{o / 1 /* no such op */}; - assert catch{o % 1 /* no such op */}; - assert catch{o << 1 /* no such op */}; - assert catch{o >> 1 /* no such op */}; - -} - -scope { - const sproto = "".prototype; - assert catch{"" * 3 /* no such op (yet) */}; - sproto.'operator*' = proc f(rhs){ - (rhs>0) || throw "Expecting positive RHS for STRING*N op."; - f.buf || (f.buf = buffer(100)); - f.buf.reset(); - for(var i = 0; i < rhs; ++i ){ - f.buf.append(this); - } - return f.buf.toString(); - } using {buffer: s2.Buffer.new}; - assert '***' === "*" * 3; - assert catch{'*' * -1 /* negative index */}; - assert "3.00.1" === "3.0"+0.1 /* checking for a misplaced bug */; - unset sproto.'operator*'; -} - -var three = 3; -scope { - var ar = []; - ar.'operator+=' = proc(r){ - this.push(r); - return this; - }; - ar.'operator<<' = proc(r){ - return this.push(r); - }; - ar += 1; - ar << 2; - assert '12' === ar.join(''); - - var out; - ar.'operator>>' = proc(r){ - //var code = r+" = this.pop()"; - //print('code =',code); - //return eval -> code; - return eval -> r+"=this.pop()"; - }; - 1 ? (ar >> nameof out) // yet another (obscure) use for nameof - : ar.pop(); - assert 2 === out; - assert 1 === ar.length(); - assert 1 === ar.0; - //print(out,ar); - //print(__FLC, refcount three); - ar[3] = three; - //print(__FLC, refcount three); - ;1;1;1; - //print(__FLC, refcount three); - ar.clear(); - //print(__FLC, refcount three); - - assert catch{ar & 1/* no such operator (yet) */}; - ar.'operator&' = proc(r){ - return this; - }; - assert ar === ar & 1; - - - var o = {a: [1,2]}; - o.a.'operator+=' = ar.'operator+='; - o.a += 3; - assert '123' === o.a.join(''); - -} -//print(__FLC, refcount three); -assert 3 === three; - -scope { - // Overloading math ops on functions... - var x, setX = proc(){return x = argv.0}; - setX.'operator+' = proc(arg){return this(arg)}; - setX+1; - assert 1 === x; - assert 0 === setX + 0; - assert 1 === setX + -1 + 1 * 2; -} - -scope { - // Overload-only -> op... - var x, setX = proc(){return x = argv.0}; - setX.'operator->' = proc(arg){return this(arg)}; - setX->1; - assert 1 === x; - assert 0 === setX -> 0; - assert -1 === setX ->( -1 * 1 ) /* -> has . precedence */; - - unset setX; - - // Overload-only =~ ("contains") and !~ ("does not contain") ops... - var o = { - a: [1,2,3], - 'operator=~':proc(arg){ - return this.a.indexOf(arg)>=0; - } - }; - assert o =~ 1; - assert o =~ 3; - assert !(o =~ -1); - - assert catch{o !~ 1 /* no such operator yet */}; - o.'operator!~' =proc(arg){ - return this.a.indexOf(arg)<0; - }; - - assert o !~ -1; - assert !(o !~ 1); - assert o =~ 1 && o !~ -1; // has comparison precedence -} - -scope { - // C++-style streams... - var b = new s2.Buffer(20); - // Remember that buffers are not containers: - //b.prototype.'operator<<' = proc(self,arg){ // built-in overload - // return this.append(arg); - //}; - b << "a" << "bc" << "def"; - assert "abcdef" === b.toString(); - b.reset(); - b << 1 << 2+3; - assert "15" === b.toString(); - //unset b.prototype.'operator<<'; - - // Alternate implementation: - var o = { - buf: b.reset(), - 'operator<<': proc(arg){ - this.buf.append(arg); - return this; - } - }; - o << "a" << "bc" << "def"; - assert "abcdef" === b.toString(); - b.reset(); - o << 1 << 2+3; - assert "15" === b.toString(); - //unset b.prototype.'operator<<'; -} - -scope { - var x = 0; - var obj = { - 'operator<<': proc(arg){ - x += arg; - return this; - } - }; - proc(a,b,c){ - obj << a << b << c; - assert 6 === x /* testing an argv propagation fix */; - proc(d,e,f){ - obj << d << e << f /* testing an argv propagation fix */; - assert 21 === x; - }(4,5,6); - }(1,2,3); -} - -scope { - var h = new s2.Hash(13); - h.insert(1, "hi"); - assert 'hi' === h # 1; - assert undefined === h # 0; - var x = 0; - assert 'hi' === h # (++x); - - assert 0 === catch{h#1 = 'error'}.message.indexOf('Invalid LHS') - /* # is not valid in an assignment op */; - - var h2 = new s2.Hash(13); - h2.insert(1,3); - h2.insert('a', 0); - h.insert(2, h2); - assert 3 === h # 2 # 1; - assert h # 2 # 1 === 3; - assert h # 2 # 0 === undefined; - assert h # 2 # 'a' === 0; - - h2.insert('f', proc f(){assert f===this; return 1}); - // # does not set 'this' like the dot op does - assert 1 === (h # 2 # 'f')(); - // If it did, (h2#f)() would have h2 has 'this', which is - // borderline disturbing. It also means this call syntax - // won't work: - // assert 1 === h # 2 # 'f'(); - - var obj = { h: h, x:0 }; - assert catch{obj#1}.message.indexOf('Hash')>0 - /* LHS of # must be-a Hash. Overloading of # was removed, - as it (A) will complicate extending # to support assignment - (insert()) and (B) it essentially negates the reason - for having the # operator: an access method for hashes which - makes them competative with Objects (using Hash.search() - has function call overhead which obj[prop] does not, making - objects faster for most cases). - */; - assert 'hi' === obj.h # 1; - assert undefined === obj.h # 0; - assert obj.h # 2 # 'a' === 0; - assert 3 === obj.h # 2 # (0+1); - assert 0 === catch{obj.h#1 = 'error'}.message.indexOf('Invalid LHS') - /* # is not valid in an assignment op */;; -} - -scope { - var o = { - x: 3, - 'operator::': proc(key){ - return this.hasOwnProperty(key); - } - }; - assert o::x; - assert !o::y; -} - -0 && scope { -// experimental, doesn't yet work how i would like -// Certain parts of this require enabling/disabling -// specific ifdefs in s2_eval.c and/or s2_ops.c - - var o = { - __typename: 'Bob', - 'operator->': proc(arg){ - return this.sub[arg]; - }, - sub:{ - __typename: 'Sub', - x: -1, - f: proc(){ - print(__FLC,'this =',this); - assert 'Bob' === typename this; - return 1; - } - } - }; - print(o->'f'); - assert 'function' === typename o->'f'; - assert 1 === o->f(); // works, yet... - assert 1 === o->'f'(); // works - o->x = 1; // assigns in o (or errors, depending on how we assign engine->dotOpLhs) - o->x++; // as well. - // i understand why, but a fix seems rather intrusive. i'd rather have it not behave - // like the dot, i think. - print(__FLC,'o =',o); -}; DELETED bindings/s2/unit/200-000-ifelse.s2 Index: bindings/s2/unit/200-000-ifelse.s2 ================================================================== --- bindings/s2/unit/200-000-ifelse.s2 +++ bindings/s2/unit/200-000-ifelse.s2 @@ -1,137 +0,0 @@ - - -/* note that the semicolons here are needed for the assert, not the if/else blocks */ -assert if(1){}; -assert !if(0){throw __FLC}; -assert !if(0){throw __FLC}else{}; -assert if(0){throw __FLC} else if(1){} else{throw __FLC}; - -assert if(0){throw __FLC} - else if(true)<<<_FI /*trued the second time*/;; _FI - else {throw __FCL}; -assert true /* Just checking tail consumption */; -assert !if(0){throw __FLC} - else if(1-2+1){throw __FLC} - else {}; - -var a; -if(0){throw __FLC} -else if(0){throw __FLC} -else{a=4*2-1} /* note no semicolon needed here */ -assert 7 === a; -assert 7 === a /* make sure previous one wasn't skipped */; - -a = 0; -//pragma(trace-token-stack 1); -if(0) throw __FLC; else if(1) a = 1; else throw __FLC; -//pragma(trace-token-stack 0); -assert 1 === a; - -a = -1; -if(1) a = 0; else if(1) {throw __FLC} else {throw __FLC} -assert 0 === a; - -a = 0; -if(0) throw __FLC; else if(0) {throw __FLC} else a = 1 ? 2 : 3; -assert 2 === a; - -a = 0; -if(0) throw __FLC; else if(0) {throw __FLC} else {a = 0 ? 2 : 3} -assert 3 === a; - -assert var ex = catch{ - if(0) 1; else if(0) else a = 1 ? 2 : 3; - // --------------^^^^ -}; -assert 'CWAL_SCR_SYNTAX' === ex.codeString(); -assert 0 < ex.message.indexOf('consecutive non-operators'); - -a = 0; -assert true === if(1) 1; else throw __FLC; -assert false === if(0) throw __FLC; else if(0) throw __FLC; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -a = 0; -assert false === if(0) throw __FLC; else if(0) {0}; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -a = 0; -assert true === if(0) throw __FLC; else if(1) {0}; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -a = 0; -assert true === if(0) 1; else if(1) 0; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -a = 0; -assert true === if(1) 1; else if(1) {throw __FLC}; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -a = 0; -assert true === if(1) 1; else if(1) throw __FLC; -a = 1; -assert 1 === a /* make sure final if() DTRT */; - -// checking newline-as-EOX handling... -if(1){} -var b; -assert typeinfo(islocal b); - -if(0){} else{} -var b2; -assert typeinfo(islocal b2); - -if(0){} else if(0){} -var b3; -assert typeinfo(islocal b3); - -if(0){} -var c; -assert typeinfo(islocal c); -/* - When using heredocs as if/else bodies, a semicolon/newline - between the heredoc's closing token and the "else" part is - optional. Typing it seems to feel more intuitive, but internally - heredocs are normally treated like script blocks, and a semicolon - after a script block has a different meaning here: - - if(1){...}; else{...} // syntax error - - NEVERMIND: i don't like this semicolon inconsistency. But now it's - doucmented :/ -*/ -var x; -if(1)<<3){break x} - //(x=x+1)>3 && break x - }; /* semicolon needed b/c of assignment expr */ - true // at EOF, semicolon is optional -} -assert 4===i; - -i = 0; -while(!i){ - var x =0; - i = while(true){ - if((x=x+1)>3){break x} - //(x=x+1)>3 && break x - } /* semicolon _normally_ needed b/c of assignment expr, - but special-casing of keywords ending in blocks - implicitly treat this EOL as an EOX */ -} -assert 4 === i; - -i = 0; -assert 4 === while(true){ - ((i = i + 1)<4) && continue; - break i; -}; - -i = while(false){}; -assert undefined === i; - -i = while(true){break}; -assert undefined === i; - -i = 0; -while(i<3)++i; -assert 3 === i; -assert 3 === i /* make sure the previous line wasn't snarfed by the loop */; - -i = 0; -while(i++<3); -assert 4 === i; -assert 4 === i /* make sure the previous line wasn't snarfed by the loop */; - -i = 0; -assert !catch{0 ? while(i<2) ++i : 1} /* no syntax error */; -assert !i; -assert !catch{1 ? while(i<2) ++i : 1} /* no syntax error */; -assert 2 === i; DELETED bindings/s2/unit/200-200-for.s2 Index: bindings/s2/unit/200-200-for.s2 ================================================================== --- bindings/s2/unit/200-200-for.s2 +++ bindings/s2/unit/200-200-for.s2 @@ -1,48 +0,0 @@ - -for(var i = 1, x = -1; - i >= -1; - i -= 1, x+=1){ - //print('for(): i =',i,'x =',x); -} -assert 'undefined' === typename i; - -var i = 0, x = for(;;){ - (i = i + 1)>3 && break i -}; -assert 4 === i; -assert x === i; - -x = for(i = 0;;){ - scope { - catch{ (i = i + 1)>4 && break i } - } -}; -assert 5 === i; -assert x === i; - -assert -1 === (1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/}); -assert -1 === eval 1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/}; -assert false === (0 && for(;;){/* if this infinite loops then skip mode is broken*/}); -assert false === eval 0 && for(;;){/* if this infinite loops then skip mode is broken*/}; - - -for(i = 0, x = 0; - i < 3; - ) ++i, x += 2; -assert 3 === i; -assert 3 === i /* make sure the previous line wasn't snarfed by the loop */; -assert 6 === x; - -for(i = 0, x = 0; i < 3; ++i -) x += 2; -assert 3 === i; -assert 3 === i /* make sure the previous line wasn't snarfed by the loop */; -assert 6 === x; - -for(i = 0, x = 0; i < 3; ++i ); -assert 3 === i; -assert 3 === i /* make sure the previous line wasn't snarfed by the loop */; -assert 0 === x; - -assert !catch{0 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */; -assert !catch{1 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */; DELETED bindings/s2/unit/200-300-dowhile.s2 Index: bindings/s2/unit/200-300-dowhile.s2 ================================================================== --- bindings/s2/unit/200-300-dowhile.s2 +++ bindings/s2/unit/200-300-dowhile.s2 @@ -1,42 +0,0 @@ - -var i; -i = 0; -do{++i}while(i<5); -assert 5 === i; -i = 0; -do ++i; while(i<5); -assert 5 === i; - -assert 'CWAL_SCR_SYNTAX' === catch{ - do ++i while(true) /* missing semicolon after ++i */ -}.codeString(); - -i = 0; -do{ - ++i -} -while(!i); -assert 1 === i; - -assert -1 === do{ - break -1; -}while(true); - -assert undefined === do ; while(false) - /* if a for/while loop body may be empty, why not a do loop, too? */ -; - - -i = 0; -do ++i>2 && break; while(true); -assert 3 === i; - -i = 0; -do i++>2 && break; while(true); -assert 4 === i /* yes, four */; - -i = 0; -assert !catch{0 ? do ++i; while(i<2) : 1} /* no syntax error */; -assert !i; -assert !catch{1 ? do ++i; while(i<2) : 1} /* no syntax error */; -assert 2 === i; DELETED bindings/s2/unit/300-000-functions.s2 Index: bindings/s2/unit/300-000-functions.s2 ================================================================== --- bindings/s2/unit/300-000-functions.s2 +++ bindings/s2/unit/300-000-functions.s2 @@ -1,301 +0,0 @@ -var x = function X(a=1,b=0){ - //print("argv =",argv); - assert 'array' === typename argv; - assert 'function' === typename X; - //print('"this" type =',typename this); - return a + b; -}; -assert 'function' === typename x; -assert 1 === x(1); -assert 3 === x(1,2); -//return; -assert 3 === x(1,2,3); - -var o = { f: x }; -assert -1 === o.f(0,-1,1); -unset o; - -var y = 1 || proc(){skipped}(args,skipped,too); - -// just to watch the dtor in debug output... -// print('unsetting func'); unset x; print('after unset'); - -unset x, y; -// Fixed :-D -//print('script location info from inside funcs will be wrong for a while:', -// catch proc(){throw __FLC}() -//); -assert __LINE+1 === catch { - proc(){throw 0}() -}.line; - -var recursive = proc r(a=1){ - return a>3 ? a : r(a+1); -}; - -assert 4 === recursive(); - -assert 4 === proc rec(a=1){ - return a>3 ? a : rec(a+1); -}(); -assert 'undefined' === typename rec; - -var x = function myfunc(a=myfunc.defaults.0, b=myfunc.defaults.1){ - return a + b; -}; -x.defaults = [1,-1]; -assert 0 === x(); -assert 1 === x(2); - -var o = { - name: 'fred', - f:function(){return this.name} -}; -assert 'fred' === o.f(); - -scope { - var x = 3, y = 4; - var f = function(c){ - return a + b + c; - }.importSymbols({a: x, b: y}); - assert 8 === f(1); - f.a = 1; - assert 8 === f(1) /* that's a different 'a' */; - var counter = 0; - f.eachProperty(proc(){ - counter = counter + 1; - //print(argv); - }); - assert 1 === counter - /* making sure that the importSymbols sym is hidden (does not get - iterated over). */; - f.clearProperties(); - assert 9 === f(2) /* f.clearProperties() does not nuke imported - symbols, as of 20160206. */; - f.importSymbols(false,{b: 3}) - /* initial bool arg specifies whether or not to clear - existing imported properties before the import. - Added 20171227. - */; - assert 10 === f(4); -} - -scope { - var o = {a:1}; - var f = function(a){ - return this.a + a; - }; - assert 2 === f.apply(o,[1]); - assert 2 === f.call(o,1); - -} - -scope { - // Make sure that skip mode is honored - // for default parameters which are passed - // in by the caller... - var f = proc(a, c=(throw 'default param threw'), b=1){ - assert 1===b; - assert 0===c; - assert -1===a; - }; - f(-1,0); - assert 0 === catch{f(1) /* param c=throw... gets processed */} - .message.indexOf('default param'); - - // make sure sourceCode() is working. - assert f.sourceCode().indexOf("default param threw")>8; - assert proc(){/*comment*/}.sourceCode() === "proc(){}"; - assert proc(/*comment*/){/*comment*/}.sourceCode() === "proc(){}"; -} - -scope { - /* The "using" pseudo-keyword... */ - var x = 30, f = proc() using(x) {return x}; - unset x; - assert 30 === f(); - var f2 = proc() using({y:20, F:f}) {return F() * y}; - assert 600 === f2(); - f.importSymbols({x: 40}); - unset f; - assert 800 === f2(); - unset f2; - - /* Alternate placement at the end of the function, primarily - because it makes migration from importSymbols() a simple - search/replace op: */ - var x = 30, y = 20, f = proc() {return x*y} using(x, y); - unset x, y; - assert 600 === f(); - var f2 = proc() {return F() * y} using({y:10, F:f}); - assert 6000 === f2(); - f.importSymbols({x: 40, y: 1}); - unset f; - assert 400 === f2(); - unset f2; - - /* Another alternate syntax which takes a single object literal. - It does not accept an object expression or a hash literal. - */ - assert 60 === proc() using {A: 30} { return A * argv.0 }(2); - assert -60 === proc() { return B * argv.0 } using {B: -30}(2); - - assert 'CWAL_RC_TYPE' === catch { - proc() using {#a:1} {} - }.codeString() /* hash literal is not allowed */; - - assert catch { - proc() using (true) {} - }.message.indexOf("eyword") > 0 /* keyword as identifier not allowed */; - - assert catch { - proc() using () {} - }.message.indexOf("mpty") > 0 /* empty expression not allowed */; - - /* Contrast empty () with empty {object}, which is allowed, though - not of much use... */ - assert 0 === proc() using {} {return 0}(); - - assert catch { - var x; - proc() using (x ?) {} - }.message.indexOf("nexpected") > 0 /* illegal token */; - - assert catch { - var x; - proc() using (x, 1) {} - }.message.indexOf("'integer'") > 0 /* illegal expr. type */; - - assert catch { - proc() using x{} - }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/; - - assert catch { - proc() {} using x - }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/; - - - // Demonstrate how using() deals with identifiers vs non-identifier/non-literal - // expressions: - var a = 0;; - assert catch { - proc() using(a.prototype, b) {} - }.message.indexOf("Unexpected token '.'") === 0 - /* fails b/c a leading identifier triggers import of that identifier - and does not eval a sub-expression. */; - assert proc(){} using((a.prototype)) /* works b/c it doesn't start with an identifier */; - assert proc(){} using(0.prototype) /* works b/c it doesn't start with an identifier */; - unset a; - var obj = {a:1, b:2}; - var f1 = proc(){return obj.a+obj.b} using(obj); - var f2 = proc(){return a+b} using((obj)); - unset obj; - assert 3 === f1() /* because using(obj) resolves obj as an identifier. */; - assert 3 === f2() /* because using((obj)) triggers a sub-expression parse and - imports the resulting properties. */; -} - -scope { - // testing a parsing fix for prefix ++/-- ... - var f = proc(){return o} using{o:{x:1}}; - assert 1 === f().x; - assert 1 === f().x++; - assert 3 === ++f().x; - assert 2 === --(f()).x; - //assert 3 === ++(f().x) /* doesn't work b/c (x) is resolved in a subexpression */; -} - -var u = scope { - /* 20191209: using() keyword */ - assert undefined === proc(){return using()}(); - var f = proc(){return using()} using{a: 1}; - var u = f(); - assert typeinfo(isobject u); - assert !u.prototype; - assert 1 === u.a; - u.b = 2; - assert 2 === f().b; - assert undefined === using(proc(){}); - assert 'CWAL_SCR_SYNTAX' === catch {using()}.codeString(); - assert 'CWAL_RC_TYPE' === catch using(f.apply).codeString(); - f = proc(){return using()} using{} - /* as fate would have it, using{} is legal, and creates an empty - imports object, but using() is not. The latter was an - intentional design decision but the former just a happy - accident. */; - u = f(); - assert typeinfo(isobject u); - u.b = 1; - assert 1 === f().b; - - /* imports must survive propagation out of a temporary value... */ - u = using(proc()using{a:999}{}); - ;;;;;;;; /* pedanticness: make sure sweepup triggers (though the temp function is already gone by now) */ - assert typeinfo(isobject u); - assert 999 === u.a; - - /** - 20191211: using (without parens) is legal in function bodies as a shorthand - for using() (it's also more efficient). - */ - assert catch { - using; - }.message.indexOf("Expecting (") === 0; - f = proc F()using{a:1}{ - assert typeinfo(islocal a); - assert using(F) === using(); - proc(){assert undefined===using /* not inherited from outer function */}(); - return scope {using.a /*from containing function*/} - }; - assert 1 === f(); - - /** - 20191211: adding a dot after the "using" clause in a function - definition specifies that the imported symbols (whether via - this "using" decl or a future importSymbols() call) are NOT to - be declared as local variables at call()-time. Instead, they - may be accessed via the call-local "using" keyword. - */ - f = proc F() using.{XXX: 1} { - assert !typeinfo(islocal XXX); - assert typeinfo(islocal F); - assert 1 === using.XXX; - assert 1 === using()['XXX']; - assert using.XXX === using(F).XXX; - return using['XXX']; - }; - assert 1 === f(); - - assert 1 === proc(){ - assert !typeinfo(islocal a); - return using.a; - } using.{a:1}(); - /* 20191218 bugfix test: - 1) Order of {body}using.{...} broke parser. - 2) S2_FUNCSTATE_F_NO_USING_DECL flag was not being set - on the function when that order applied. - */ - ; - - /* Confirm lifetime of imported symbols for functions defined - inside using()... */ - assert "!wakawaka!" === using(proc(){}using{a:"!wakawaka!"}).a; - assert "/foo/bar/baz" === scope using(proc()using{a:"/foo/bar/baz"}{}).a; - var uo = using(proc()using{a:"/foo/bar/baz", b: 1000}{}); - assert "/foo/bar/baz" === uo.a; - assert 1000 === uo.b; - unset uo; - assert using(proc(){}using{a:"!wakawaka!"}).a - === proc(){return using()}using{b:"!wakawaka!"}().b; - - /* imports can be added after the fact with importSymbols()... */ - f = proc(){return using()}; - assert undefined === f(); - f.importSymbols({a: 1000}); - assert 1000 === f().a; - /* imports must survive propagation out of this scope... */ - f() /* scope result */; -}; -assert typeinfo(isobject u); -assert 1000 === u.a; - DELETED bindings/s2/unit/400-000-new.s2 Index: bindings/s2/unit/400-000-new.s2 ================================================================== --- bindings/s2/unit/400-000-new.s2 +++ bindings/s2/unit/400-000-new.s2 @@ -1,222 +0,0 @@ -proc(){ - assert !typeinfo(isnewing); -}(); -assert !typeinfo(cannew 1); -assert !typeinfo(cannew {}); -assert typeinfo(cannew s2.Hash); -var v = new s2.Hash(13); -assert v inherits s2.Hash; -assert 13 === v.hashSize(); -//s2.dumpVal(v); - -assert typeinfo(cannew s2.Buffer); -v = new s2.Buffer(10); -assert v inherits s2.Buffer; -assert v.capacity() >= 10; -//s2.dumpVal(v); - -assert typeinfo(cannew s2.PathFinder); -v = new s2['PathFinder']() { - this.x = 1; -}; -assert v inherits s2.PathFinder; -assert undefined === v.search('PathFinder'); -assert 'string' === typeinfo(name v.search(__FILE)); -assert 1 === v.x; -//s2.dumpVal(v); - -const Array = { - prototype: [].prototype, - blah: 2, - __new: proc(){ - this.x = 1; - assert typeinfo(isnewing); - assert typeinfo(is-newing this); - assert argv && !typeinfo(isnewing argv); - if(1) assert typeinfo(isnewing) /* works as of 20171206 */; - assert typeinfo(isnewing) /* But now we're back in our 'new' scope. */; - proc(){assert !typeinfo(is-newing)/* different "this" */}(); - var rc = argv.slice(); - rc.prototype = this; - return rc; - } -}; -assert typeinfo(cannew Array); -v = new Array(-1,0,1); -assert v inherits Array; -assert 'array' === typeinfo(name v); -assert [].prototype === Array.prototype; -assert -1 === v.0; -assert 1 === v.2; -assert 2 === v.blah; -assert v.blah === (v[] = v.blah); // reminder to self: not a precedence bug - JS also requires extra parens here. -assert 3 === ++v.blah; -assert 2 === v.prototype.blah; -assert 2 === v.prototype.prototype.blah; -assert 3 === v.blah; -assert 4 === v.length(); -v[4] = -1; -assert 5 === v.length(); -assert -1 === v.4; -//s2.dumpVal(v); -//v.eachIndex(proc(v){print(typename v, v)}); - -const Object = { - prototype: {}.prototype, - __new: proc(){ - assert typeinfo(isnewing); - return;// {prototype: this}; // same effect but requires +1 temp Object allocation - } -}; -assert typeinfo(cannew Object); -v = new Object(); -assert v inherits Object; -assert 'function' === typename v.eachProperty; -assert v.isEmpty(); - -const String = { - prototype: "".prototype, - __new: proc(){ - // hmmm. Can't do POD types as PODs this way - // without other s2-level infrastructure. - this.value = argv.join(''); - assert typeinfo(isnewing); - } -}; -assert typeinfo(cannew String); -v = new String("a","b","c"); -assert v inherits String; -assert v !== String; -assert 'abc' === v.value; -assert undefined === v.prototype.value; - -// An alternate approach... -const String2 = { - __new: proc(){ - assert typeinfo(isnewing); - return argv.join(''); - } -}; -assert typeinfo(cannew String2); -v = new String2('a','b','c'); -assert v === 'abc'; - -const Boolean = { - __new: proc(v=(affirm 0 && "Boolean ctor requires an argument")){ - assert typeinfo(isnewing); - return !!v; - } -}; -assert typeinfo(cannew Boolean); -assert true === new Boolean(true); -assert false === new Boolean(false); -assert catch {new Boolean()}.message.indexOf('ctor') > 0; -assert catch new Boolean().message.indexOf('ctor') > 0; // also works - -const MyClass = { - prototype: s2.Hash, - __new: proc(){ - assert typeinfo(isnewing) /* in 'new' scope */; - if(1){ - assert typeinfo(isnewing); - const x = this; - proc(){ - assert !typeinfo(isnewing)/*not the same 'this' as the ctor call*/; - assert typeinfo(isnewing x)/*optionally accepts an expression*/; - }(); - } - var rc = new s2.Hash(); - rc.prototype = this; - foreach(@argv=>i,v) rc.insert('k'+i, v); - proc(){ - assert !typeinfo(isnewing) /* not in 'new' scope */; - new String() /* typeinfo(isnewing) check must pass */; - assert !typeinfo(isnewing) /* not in 'new' scope */; - }(); - new Boolean(1) { - assert !typeinfo(isnewing) /* not in the post-ctor block */; - }; - assert typeinfo(isnewing) /* back in 'new' scope */; - return rc; - } -}; -assert typeinfo(cannew MyClass); -v = new MyClass(1,2,3); -assert v inherits MyClass; -assert MyClass inherits s2.Hash; -assert v inherits s2.Hash; -// v's prototype is a hash, but v is not, so new properties -// set via its dot/[] ops don't land in the hash: -//v.eachEntry(proc(k,v){print(__FLC,' ',k,'=',v)}); -assert 2 === v#'k1'; -assert 'function' === typeinfo(name v.__new); -assert 3 === v.search('k2'); -assert 3 === v # 'k2'; - - -/* Exception subclasses are a bit trickier... */ -const E = { - prototype: exception(0).clearProperties(), - __typename: 'Exceptional', - __new: proc(codeOrMsg, - msgOrCode){ - assert typeinfo(isnewing); - (argv.length()>1 - ? exception(codeOrMsg,msgOrCode) - : exception(0,codeOrMsg) - ).copyPropertiesTo(this); - //e.copyPropertiesTo(this) - /* collect stack trace and friends */; - this.hi = 1; - } -}; -assert !E.stackStrace; -assert !E.script; -var e = catch throw new E('yo'); -assert e; -assert e.prototype === E; -assert e inherits E; -assert !E.stackStrace /* testing a bufix in s2_throw_value() */; -assert typeinfo(hasexception e); -assert !typeinfo(isexception e); - - -/* Make sure short-circuiting skips all it's supposed to... */ -assert !typeinfo(isdeclared Nope); -assert -1 === 0 ? new Nope() : -1; -assert -1 === 0 ? new Nope() { nope nope nope } : -1; -assert -1 === (1 ? new MyClass() { this.x = -1 } : new Nope() { nope nope nope }).x; - -// reminder to self: braces are needed around these catch blocks because -// otherwise catch does not convert syntax errors to exceptions. -assert 'CWAL_SCR_SYNTAX' === catch { new MyClass() {return 3} }.codeString(); -assert catch { new MyClass() { continue } }.message.indexOf('Unhandled') >= 0; -assert catch { new MyClass() { break } }.message.indexOf('Unhandled') >= 0; - -assert 3 === new MyClass(), 3 /* new must stop after the call() part... */; -assert 3 === new MyClass(){}, 3 /* ... or the post-init part */; - -if(0){ - // Functions as ctors is currently disabled - /** - This is more JavaScripty but also leads to each instance being - able to be call()'d, and that call landing in the constructor! - Unlike in JS, this ctor _is_ the prototype for new instances, - so add shared methods directly as properties of this ctor. - - i don't have a workaround for the call()able problem which also - leaves the resulting objects as (v inherits Func). :/ - */ - const Func = proc(a,b){ - this.a = a; - this.b = b; - }; - assert typeinfo(cannew Func); - var f = new Func(1,2); - assert f inherits Func; - assert f.a === 1; - assert f.b === 2; - assert 'object' === typeinfo(name f); - assert !typeinfo(isfunction f); - assert typeinfo(iscallable f) /* unfortunate */; -} DELETED bindings/s2/unit/500-000-array.s2 Index: bindings/s2/unit/500-000-array.s2 ================================================================== --- bindings/s2/unit/500-000-array.s2 +++ bindings/s2/unit/500-000-array.s2 @@ -1,310 +0,0 @@ -var ar = [1,2,3]; - -scope { - assert 3 === ar.#; - assert 3 === ar.#; - assert ar.# === 3; - assert '3' === ar.#.toString(); - assert 2 === ar.indexOf(3); - assert ar === ar.length(2) /* setter returns the array */; - assert 2 === ar.#; - assert 2 === ar[1]; - assert undefined === ar[2]; - - ar.prop = 1; - ar.clear(); - assert 1===ar.prop; - assert !ar.#; - ar.clear(true); - assert undefined===ar.prop; -} - -scope { - assert ar.isEmpty(); - assert 3 === ar.push(1,2,3); - assert !ar.isEmpty(); - assert 3 === ar.#; - var x = 0; - var eachCallback = proc(v,i){ - //print('in func x=',x,i); - x = i; - //print(x, argv, v, i); - } ; - ar.eachIndex( eachCallback ); - assert 2 === x; - eachCallback(0,1); - assert 1 === x; -} - -scope { - assert 3 === ar.#; - assert 3 === ar.2; - assert 3 === ar.pop(); - assert 2 === ar.#; - assert 1 === ar.shift(); - assert 1 === ar.#; - assert 2 === ar.0; - assert 0 === ar.indexOf(2); - ar.reverse(); - assert 2 === ar.0; -} - -scope { - // Array.indexOf() type-strict comparison flag... - var a = [0,'1',2]; - assert a.indexOf(1,false) === 1 /* not type-strict */; - assert a.indexOf(1,true) < 0 /* type-strict */; - assert a.indexOf('1',false) === 1 /* not type-strict */; - assert a.indexOf('1',true) === 1 /* type-strict */; - assert a.indexOf(1) < 0 /* default == type-strict */; -} - -scope { - assert 1 === ar.#; - assert 2 === ar.0; - ar.unshift(1,-1); - assert 3 === ar.#; - assert 2 === ar.2; - assert -1 === ar.1; - assert 1 === ar[0]; - assert '1,-1,2' === ar.join(','); - assert 3 === ar.setIndex(1,3); - assert '1,2,3' === ar.sort().join(','); - assert '3,2,1' === ar.sort(function(l,r){ - // reverse-order (l,h)... - return l==r ? 0 : l < r ? 1 : -1; - cannot happen; - // The long way: - l==r && return 0; - l < r && return 1; - return -1; - }).join(','); - assert '123' === ar.reverse().join(''); - assert ar === ar.unshift(-1); - assert '-1123' === ar.join(''); -} - -scope { - const a1 = [1,2,3]; - assert catch{a1.slice(0,-1)}.codeString() === 'CWAL_RC_RANGE'; - assert catch{a1.slice(-1,0)}.codeString() === 'CWAL_RC_RANGE'; - // slice() during sort is madness... - assert catch a1.sort(proc(){a1.slice()}).codeString() === 'CWAL_RC_LOCKED'; - - var a2 = a1.slice(0,1); - assert 'array' === typename a2; - assert 1 === a2.#; - assert 1 === a2.0; - - const a3 = a2.slice(3); - assert 0 === a3.#; -} - -scope { - const a1 = [1,2,3]; - var a2 = a1.slice(0, 1); - //print('a2=',a2); - assert 1=== a2.#; - - a2 = a1.slice(1,1); - assert 1=== a2.#; - assert 2 === a2.0; - - a2 = a1.slice(1, 6); - assert 2=== a2.#; - assert 3 === a2.1; -} - -scope { - const a = [-2,-1,0,1]; - assert -2 === a.shift(); - assert -1 === a.0; - assert 0 === a.shift(2); - assert 1 === a.#; - assert 1 === a.0; -} - -scope { - const ar = [0,proc f(){assert f===this; return this},1]; - assert ar.1 === ar.1() /* 'this' is not the array */; -} - -scope { // using arrays as prototypes... - var x = {0: 0, // interesting: whether or not this property - // gets set as an object prop or array index depends - // on whether it gets set before or after this object - // extends the array class... - prototype:[1,2] - }; - x.0 = -1 /* sets array index */; - assert -1 === x.0 /* array index */; - assert -1 === x.prototype.0 /* array index */; - if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){ - assert 0 === x.'0' /* string type bypasses array index check, - so this is an object property access.*/; - x.'0' = 1 /* also object property, not array index */; - assert -1 === x.0 /* string key '0' did not overwrite this */; - } - x[] = 3; - assert 3 === x.length(); - assert 3 === x.2; - - /* - Minor descrepancy vis-a-vis objects: objects never (via - assignment) add new properties to their prototypes, but instead - shadow any prototype prop with the same name. Arrays index - access does not behave that way: it modifies the array part of - the value directly (the prototype, in the above example). - */ -} - -scope { - // Check for propagation of non-exception errors from array.sort() - // callbacks... - const ex = catch [1,2].sort(proc(){ - ,/*intentional syntax error*/ - assert cannot happen; - }); - assert 'CWAL_SCR_SYNTAX' === ex.codeString(); -} - -scope { - // Ensure that attempts to iterate over an array from a sort() - // callback, or sort during iteration, are rejected... - const ar = [1,2,3]; - var ex = catch foreach(@ar=>v) ar.sort(); - assert 'CWAL_RC_IS_VISITING_LIST' === ex.codeString(); - ex = catch ar.sort(proc(){foreach(@ar=>v){1}}); - assert 'CWAL_RC_LOCKED' === ex.codeString(); - - // But multiple iteration must (as of 20191211) work... - var i = 0; - foreach(@ar=>v) foreach(@ar=>v) ++i; - assert i === ar.# * ar.#; - - // Assigning during traversal is OK: - foreach(@ar=>i,v) ar[i] = 2*v; - assert 6 === ar.2; - - // reverse() during traversal is allowed only because it's - // just a special case of assignment: - foreach(@ar=>v) ar.reverse(); - assert 6 === ar.0; - assert 2 === ar.2; - /** - One could rightfullyargue that sorting is also just a special - case of get/set, but its implementation is much more involved, - so traversing during sort, or vice versa, are currently - disallowed. That Way Lies Madness. - */ -} - -scope { - // Ensure that sort() never calls its callback for an empty- or - // length-1 array... - [].sort(proc(){cannot happen}); - [1].sort(proc(){cannot happen}); -} - -scope { // removeIndex() - var x = [1,2,3,4]; - assert true === x.removeIndex(1); - assert 3 === x.#; - assert 1 === x[0]; - assert 3 === x[1]; - assert 4 === x[2]; - assert undefined === x[3]; -} - -scope { // filter() - const isOdd = proc(v){ return v % 2 }; - var a = [1,2,3]; - var b = a.filter(isOdd); - var c = a.filter(isOdd, true); - assert 2 === b.#; - assert 1===b.0 && 3===b.1; - assert 1 === c.#; - assert 2 === c.0; - unset a, b, c; - - /** - 20181109: Array.filter() now accepts a Tuple 'this'. As of - 20190811, it returns a Tuple if called that way (it previously - returned an array). - */ - const t = [#1,2,3,4]; - assert typeinfo(istuple t); - var ta = t.filter(isOdd); - assert typeinfo(istuple ta); - assert 2 === ta.#; - assert 1 === ta.0; - assert 3 === ta.1; - assert [#1,3] === ta; - - ta = t.filter(isOdd,true); - assert typeinfo(istuple ta); - assert [#2,4] === ta; - - assert [#] === [#].filter(isOdd); - -} - -scope { // operator+= - var a = [] /* can't be const because of += assignment :/ */; - assert a === (a+=3); - assert 1 === a.#; - assert 3 === a.0; - assert a === (a += 'b'); - assert 2 === a.#; - assert 'b' === a.1; - const o = {a:[]}; - assert o.a === (o.a+=1); - assert (o.a += 2) === o.a; - assert 2===o.a.1; -} - -scope { - /* 20191211 changes to how iteration is reported... */ - var a = [1,2,3]; - a.x = -1; - assert !typeinfo(isiteratingprops a); - assert !typeinfo(isiterating a); - foreach(a=>k){ - assert typeinfo(mayiteratelist a); - assert !typeinfo(isiteratinglist a); - assert typeinfo(isiteratingprops a); - assert typeinfo(isiterating a); - foreach(@a=>v){ - assert typeinfo(isiteratinglist a); - assert typeinfo(isiteratingprops a); - assert typeinfo(mayiteratelist a); - assert typeinfo(isiterating a); - } - assert !typeinfo(isiteratinglist a); - assert typeinfo(isiterating a); - assert typeinfo(isiteratingprops a); - } - assert !typeinfo(isiteratingprops a); - assert !typeinfo(isiterating a); - - a.sort(proc(l,r){ - assert !typeinfo(mayiteratelist a); - return -l.compare(r); - }); - assert 3 === a.0; -} - -scope { - /* - Array comparison func must internally use floating point - comparisons. - - Changed 20191220, prompted by a bug repor against the muJS JS - engine: https://github.com/ccxvii/mujs/issues/122 - */ - const l = [{a: 0.5}, {a: -0.1}, {a: 0.7}]; - l.sort(proc(x,y) {return x.a - y.a}); - assert 0.7===l.2.a; - assert 0.5===l.1.a; - assert -0.1===l.0.a; -} DELETED bindings/s2/unit/600-000-hash.s2 Index: bindings/s2/unit/600-000-hash.s2 ================================================================== --- bindings/s2/unit/600-000-hash.s2 +++ bindings/s2/unit/600-000-hash.s2 @@ -1,212 +0,0 @@ -const Hash = s2.Hash.new; - -scope { - var h = Hash(); - assert h inherits s2.Hash; - var tableSize = h.hashSize(); - assert 'integer' === typename tableSize; - assert tableSize > 0; - unset tableSize; - assert 0 === h.entryCount(); - - h.insert(1,-1.0); - assert 1 === h.entryCount(); - assert -1.0 === h.search(1); - h.insert(0,1.0); - var c = 0; - h.eachEntry(function(k,v){ - assert 'integer' === typename k; - assert 'double' === typename v; - c = c + 1; - }); - assert h.entryCount() === c; - - assert h.containsEntry(1); - assert !h.containsEntry(2); - - var keys = h.entryKeys(), vals = h.entryValues(); - assert 'array' === typename keys; - assert 'array' === typename vals; - assert vals.length() == c; - assert keys.length() == c; - unset keys, vals; - - h.remove(-1); - assert 2 === h.entryCount(); - h.insert(1, 0.0); - assert 2 === h.entryCount(); - h.remove(1); - assert 1 === h.entryCount(); - h.clearEntries(); - assert 0 === h.entryCount(); -} - -scope { - var h1 = s2.Hash.new(11), h2 = s2.Hash.new(23); - h1.insert(1, 2); - h1.insert(2, 3); - h1.eachEntry(h2, h2.insert); - assert 2 === h2.entryCount(); - h1.clearEntries(); - assert !h1.entryCount(); - h2.eachEntry(h1); - assert 2 === h1.entryCount(); - - var o = {}; - h1.eachEntry(o, o.set); - assert 3 === o.2; - assert 2 === o.1; - - h2.clearEntries(); - h1.eachEntry(h2); - assert 2 === h2.entryCount(); - assert 3 === h2.search(2); - assert 2 === h2.search(1); - - h1.insert('f', proc f(){ - assert f === this; - return this; - }); - - assert h1#'f' === (h1#'f')(); - assert h1#'f' === h1#'f'(); -} - -scope { - var h = s2.Hash.new(11); - assert !h.hasEntries(); - assert 11 === h.hashSize(); - h.insert(1, 2); - assert h.hasEntries(); - h.insert(3, 4); - assert 2 === h.entryCount(); - h.resize(3); - assert 3 === h.hashSize(); - assert 4 === h # 3; - assert 2 === h # 1; - assert 2 === h.entryCount(); - assert h.hasEntries(); - assert 'CWAL_RC_RANGE' === catch{h.resize(-1)}.codeString(); - assert 'CWAL_RC_MISUSE' === catch{h.resize()}.codeString(); -} - -if(s2.getResultCodeHash){ - const rch = s2.getResultCodeHash(); - assert rch.entryCount() > 90 /* === 102 as of this writing */; - assert rch === s2.getResultCodeHash() /* result is cached */; - assert 'integer' === typename rch # 'CWAL_RC_OOM'; - assert 'string' === typename rch # 0 /* the only code with a well-defined value! */; -} - -scope{ - var src, h; - const reset = proc(){ - src = {a:1, b:-1, c:0}; - h = s2.Hash.new(5); - }; - reset(); - h.insert('a', 'aaaa'); - h.takeProperties(src,1); - assert 1 === h # 'a'; - assert undefined === src.a; - assert undefined === src.b; - assert undefined === src.c; - assert(src.isEmpty()); - - reset(); - h.insert('a', 'aaaa'); - h.takeProperties(src,-1); - assert 'aaaa' === h # 'a'; - assert 1 === src.a; - assert undefined === src.b; - assert undefined === src.c; - assert(!src.isEmpty()); - - reset(); - h.insert('a', 'aaaa'); - const ex = catch h.takeProperties(src,0); - assert ex; - assert 'CWAL_RC_ALREADY_EXISTS' === ex.codeString(); - assert 1 === src.a; - /* src.b and src.c might have been moved already: - internal order is undefined and mutable - at runtime. */ - assert !src.isEmpty(); -} - -scope { // 2021-07-24: X.takeProperties(X) (dest==src) - const h = {#a:1, b:2}; - assert 2 === h.#; - h.a = 3; - assert 3 === h.a; - assert 1 === h#'a'; - assert 1 === h.propertyCount(); - h.takeProperties(h); - assert 0 === h.propertyCount(); - assert 3 === h#'a'; - assert 2 === h.#; - h.a = 4; - assert 'CWAL_RC_ALREADY_EXISTS' - === catch{h.takeProperties(h,0)}.codeString(); - assert 2 === h.#; - h.takeProperties(h,-1); - assert 4 === h.a; - assert 3 === h#'a'; - assert 2 === h.#; -} - -assert 999 === scope { - /* - testing fix: - - https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194 - - If it's broken, this will crash if built in debug mode, - triggering a cwal-level assertion, possibly a different one - depending on the type of the scope's result value. - */ - {#a: 999}#'a' -}; - -scope { - /** 2020-02-06: dot-length operator (lhs.#) now, on hashes, - resolves to the number of hash entries. - - 2021-07-24: that now seems like a bug. - */ - assert 3 === {# a:1, b:1, c:1}.#; -} -scope { - /* 2020-02-18: {#x:=y} assigns x as a const hash entry */ - const h = {# a:1, b:=2}; - assert 1 === h#'a'; - assert 2 === h#'b'; - assert 3 === h.insert('a',3); - assert 'CWAL_RC_CONST_VIOLATION' === catch h.insert('b',1).codeString(); - assert 2 === h.#; - assert 2 === foreach(#h=>k,v) 'b'===k && break v; -} - -scope { - /** 2020-02-20: inline expansion of object properties into an - object literal, similar to JS's {...otherObj}. This is - currently explicitly disallowed for hash literals because of - potential semantic ambiguities in handling of hash vs. object - properties. - */ - const ex = catch{ {#@{a:1}} }; - assert 'CWAL_RC_TYPE' === ex.codeString(); - assert ex.message.indexOf('hash literal') > 0; -} - -scope { - /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain - property key types... */ - const h = {#}; - assert 'CWAL_RC_TYPE' === catch{ - h.insert(new s2.Buffer(),1); - }.codeString(); - assert 'CWAL_RC_TYPE' === catch{ - h.insert([#], 1); - }.codeString(); -} DELETED bindings/s2/unit/650-000-tuple.s2 Index: bindings/s2/unit/650-000-tuple.s2 ================================================================== --- bindings/s2/unit/650-000-tuple.s2 +++ bindings/s2/unit/650-000-tuple.s2 @@ -1,107 +0,0 @@ - -scope { - // Bugfix 20171111: disallow trailing semicolon in tuple value expressions: - assert catch{[#1,2;]}.message.indexOf('semicolon')>0; - assert catch{[#1;,2]}.message.indexOf('semicolon')>0; -} - -scope { - const T = s2.Tuple; - var t0 = new T(0); - assert 'tuple' === typename t0; - assert typeinfo(istuple t0); - assert !typeinfo(istuple T); - - assert t0 === new T(0) /* same instance! */; - assert 0 === t0.length(); - assert !t0.#; - var t1 = new T(1){ - this.0 = 'zero'; - }; - assert 1 == t1.length(); - assert 1 === t1.#; - assert 'zero' === t1.0; - assert t1 != t0; - assert t1 === new T(1){ this.0 = 'zero' }; - assert t1 ==/*overloaded*/ new T(1){ this.0 = 'zero' }; - assert 'CWAL_RC_TYPE' === catch{t1==0}.codeString(); - assert 'CWAL_RC_RANGE' === catch{t1.3}.codeString(); - var t2 = new T(2){this.0 = -1; this.1 = -2}; - - /* the comparison ops all disallow a non-tuple RHS */; - assert t1 < t2; - assert t2 > t1; - assert t2 != t1; - assert t2 !== t1; - assert "[-1, -2]" === t2.toJSONString(0); - assert "[-1, -2]" === t2.toString(); - assert 'CWAL_RC_TYPE' == catch {t1<1}.codeString(); - assert 'CWAL_RC_RANGE' == catch {new T(1<<16)}.codeString() - /* length currently limited to 16 bits (64k) */ - ; - assert catch {t1>1}.message.indexOf('annot compare')>0; - - var i = 0; - foreach(t2 => v){assert v<0; ++i}; - assert t2.#===i; - i = 0; - assert typeinfo(mayiterate t2); - assert !typeinfo(mayiterateprops t2); - assert typeinfo(mayiteratelist t2); - foreach(t2 => ndx,v){ - assert typeinfo(mayiteratelist t2) /* allowed as of 20191211 */; - assert ndx>=0; - assert v<0; - ++i; - }; - assert typeinfo(mayiterate t2); - assert !typeinfo(mayiterateprops t2); - assert typeinfo(mayiteratelist t2); - assert t2.length()===i; - t2.1 = t1; - t1.0 = t2; - - assert typeinfo(mayiteratelist [#]); - - var t3 = new T([1,2,3]); - assert 3 === t3.length(); - assert 3 === t3[2]; - - t3 = new T(t2); - assert 2 === t3.length(); - foreach(t3=>i,v) assert t2[i] === v; - - assert 0 === catch {new T(0,1)}.message.indexOf("Expecting"); - - // Tuple literals... - var tL = [#1, 2, [#3, 4, 5]]; - assert typeinfo(istuple tL); - assert typeinfo(istuple tL.2); - assert !typeinfo(istuple tL.1); - assert 5 === tL.2[2]; - assert '1.2.[3, 4, 5]' === tL.join('.') /* curious but true. */; - - // Tuples are treated like arrays for @rray expansion: - var a = [@[#1,2,3]]; - assert 3 === a.length(); - assert 3 === a.2; - - assert catch {new T(1,2)}.line > 0 /* make sure 'new' decorates C-thrown exceptions */; - - - {x:t1}.x; // let it propagate out for lifetime checking. - // ^^^ reminder to self: t1[0] has a circular refererence, so t1 - // will have a refcount>0 after it leaves this scope. It will be - // up to vacuuming to clean it up. -} - -scope { - /* More comparisons... */ - const t1 = [#1,2,3], - t2 = [#3,2,1]; - assert t1 < t2; - assert t2 > t1; - assert t2 === [#3.0,2.0,1.0] /* === strictness applies only to the tuples, not their contents. */; - assert t2 < [#3,2,1.01]; - assert t1 > [#1]; -} DELETED bindings/s2/unit/700-000-foreach.s2 Index: bindings/s2/unit/700-000-foreach.s2 ================================================================== --- bindings/s2/unit/700-000-foreach.s2 +++ bindings/s2/unit/700-000-foreach.s2 @@ -1,155 +0,0 @@ - -scope { - - var i = 0; - foreach({a:1,b:2} => k){ - ++i; - assert 'a'===k || 'b'===k; - } - assert 2 === i; - var obj = {a:1,b:2}; - i = 0; - foreach(obj => k,v){ - assert obj[k] === v; - if(2===++i){ - assert 'CWAL_RC_IS_VISITING'===catch{ - obj[k] = 1 /* assignment fails while iterating - (limitation of cwal's properties - model).*/ - }.codeString(); - } - } - assert 2 === i; - - i = var j = 0; - foreach(@[1,2,3] => ndx,v) i+=v, j+=ndx; - assert 6 === i; - assert 3 === j; - unset j; - - i = 0; - foreach(#{#a:1, b:2} => k,v){ - assert 'a'===k || 'b'===k; - assert 'a'===k ? 1===v : 2===v; - ++i; - } - assert 2 === i; - - i = 0; - foreach({}.prototype => k,v) ++i; - assert i > 10; - - assert undefined === foreach(@[]=>k) assert 0; - assert undefined === foreach({}=>k) assert 0; - assert undefined === foreach({#}=>k) assert 0; - - // Slightly special behaviour for enums, for consistency - // in handling them regardless of their property storage type... - // We "just happen to know" that enums switch from objects to hashes - // if they get "big enough" (counting their reverse mappings)... - // 2020-02-21: enums are now always hashes, to eliminate special-case - // handling such as this. - - var myEnum = enum {a,b,c,d,e}; - i = 0; - foreach(myEnum=>k){ - assert typeinfo(isstring k); - assert typeinfo(isunique myEnum[k]); - ++i; - } - assert 5 === i; - assert 5 === myEnum.#; - i = 0; - foreach(myEnum=>k,v){ - assert typeinfo(isstring k); - assert typeinfo(isunique v); - assert myEnum[k] === v; - assert myEnum[v] === k; - ++i; - } - assert i === 5; - unset myEnum; - - assert 3 === foreach(@[1,2,3] => v){ - v<3 ? continue : break v; - throw "impossible"; - }; - assert 2 === foreach(@[1,2,3]=>v) v%2 || break v; - - /* Empty arrays and tuples must be a no-op: */ - i = 0; - foreach(@[]=>i) assert false /*loop body is never eval'd*/; - assert 0===i; - foreach([#]=>i) assert false /*loop body is never eval'd*/; - assert 0===i; - -} - -scope { - /* foreach() in a ternary must not generate a syntax error... */ - assert 1 === 0 ? foreach(@[0]=>v) v : 1; - assert 0 === 1 ? foreach(@[0]=>v) break v : 1; -} - -scope { - var a = [1], i =0; - a[4] = 1; - foreach( @a => v ) ++i; - assert 5 === i /* array visitation now includes C-level NULL entries (as the undefined value). */; -} - -scope { // foreach(string=>...) - var n = 0; - foreach('© © '=>i,v){ - assert v === i%2 ? ' ' : '©'; - ++n; - } - assert 4 === n; - - n = 0; - var x = 'にちは'; - assert x[2] === foreach(x=>i,v){ - ++n; - 0===i && assert 'に'===v; - 1===i && assert 'ち'===v; - 2===i && break v; - assert x[i] === v; - }; - unset x; - assert 3 === n; - - /* We have one slightly different code path (opimization) for ASCII strings: */ - n = 0; - foreach('abcd'=>i,v){ - 0===i && assert 'a' === v; - 1===i && assert 'b' === v; - 2===i && assert 'c' === v; - 3===i && assert 'd' === v; - ++n; - } - assert 4 === n; - - /* For strings, if no key is provided we iterate over the values. - Contrast with objects, where we iterate over keys in that case. */ - n = 0; - foreach('にちは'=>v){ - assert 'に' === v || 'ち' === v || 'は' === v; - ++n; - } - assert 3 === n; - - /* Empty strings must be a no-op: */ - n = 0; - foreach(''=>i) assert false /*loop body is never eval'd*/; - assert 0===n; -} - -scope { - /* 20181119: allow a break in the foreach operand expression... */ - - assert 1 === foreach((0|||break 1)=>k){...}; - assert -1 === foreach(scope {break -1}=>k){...}; - assert catch { /* break from inside a call() must still fail */ - foreach(proc(){break}()=>k){...} - }.message.indexOf("'break'") > 0; -} DELETED bindings/s2/unit/800-000-exception.s2 Index: bindings/s2/unit/800-000-exception.s2 ================================================================== --- bindings/s2/unit/800-000-exception.s2 +++ bindings/s2/unit/800-000-exception.s2 @@ -1,182 +0,0 @@ -const oldStackTrace = pragma(exception-stacktrace true); -/** - Exceptions are implicitly tested throughout the earlier unit tests, - but on 20191228 a new trick was discovered which required making a - minor backwards-incompatible change (albeit a fix) and prompted the - addition of a new feature, so... -*/ - -scope { - /* 20191228: if the exception keyword is followed by anything - other than a '(', it resolves to the exception prototype. That - makes it tempting to add similar keywords for "object" and - "array" and such, but that's a deep, dark rabbit hole. - - This feature was added primarily as an inexpensive way to get - at the exception prototype from script code, noting that - exception(0).prototype captures the exception's source code - location and stack trace, so is not cheap. - */ - const e = exception(0); - assert e.prototype === exception; - assert e.codeString === exception.codeString; - assert e.prototype === eval{exception} /* "virtual EOF" check */; -} - -scope { - // Custom exception type. - const MyEx = { - prototype: exception /* for inherited exception method(s) */, - __typename: 'MyEx', // optional - __new: proc(msg = __FLC){ // constructor - /* Some prototype gymnastics are needed... */ - const p = this.prototype /* === MyEx */, - x = this.prototype = exception(msg) - /* captures stack trace ^^^^, but now "this" - no longer inherits MyEx, so... */; - x.prototype = p - /* ^^^^ without this part, "this" will inherit exception but NOT - MyEx. */; - /** - Optional (but arguable) cosmetic improvement: - x.line/column/script refer to this constructor function, - which may be a bit confusing in practice. We can instead - "steal" those from one level up in the stack trace (which - may, of course, also be "differently confusing" in practice)... - */ - if(const s = x.stackTrace.0){ - x.line = s.line; - x.column = s.column; - x.script = s.script; - } - } - }; - - assert undefined === MyEx.line; - - const check = proc(x){ - assert x inherits MyEx; - assert !typeinfo(isexception x) /* because x is not "directly" an exception, but: */; - assert typeinfo(hasexception x) /* because x inherits an exception */; - assert "MyEx" === typeinfo(name x); - assert typeinfo(isarray x.stackTrace) /* actually, it's inherited: */; - assert x.prototype.stackTrace === x.stackTrace; - //print(__FLC,'x.stackTrace =',x.stackTrace); - assert x.stackTrace.0.script === __FILE; - assert "blah!" === x.message /* derived from x.prototype */; - assert typeinfo(isinteger x.code) /* derived from x.prototype. */; - assert typeinfo(isinteger x.line) /* derived from x.prototype. */; - //print(__FLC,thisLine, x.line); - assert x.line > thisLine /* because of our finagling in the MyEx ctor */; - assert typeinfo(isinteger x.column) /* derived from x.prototype. */; - assert 'CWAL_RC_EXCEPTION' === x.codeString(); - assert !x.hasOwnProperty('script'); - assert x.script === __FILE; - //print(__FLC, x.prototype); - } using { - thisLine: __LINE - }; - - // And now... - check(catch throw new MyEx("blah!")); - check(new MyEx("blah!")); -} - -scope { - // Make sure that script-location info is captured for this case... - const e = catch proc(){throw "hi"}() - /* ^^^ need a level of indirection to get the - stackTrace property. stackTrace is not included if - it's only 1 level deep because it would simply - duplicate the line/col/script position set in the - other properties. - */; - assert typeinfo(isinteger e.line); - assert typeinfo(isinteger e.column); - assert typeinfo(isarray e.stackTrace); - assert e.stackTrace.0.script === __FILE; - assert typeinfo(isstring e.script); - assert "hi" === e.message; - /** - There's another use case we can't(?) test from here: if a - cwal_exception is created in native code and passed to - s2_throw_value(), it "should" also get script info if a script - is currently active. Testing that requires adding a native - function binding solely to test it with, so we'll punt on that - very hypothetical problem for now. Native-side exceptions which - pass through a script-side function call get decorated with - that info if they don't already have it. - */ -} - -scope { - /* Test that s2_cstr_to_rc() is working, i.e. that both implementations - of the hashing routine produce identical results. */ - foreach(@[ - // 'CWAL_RC_OK', this won't work work in this context - 'CWAL_RC_ERROR', - // 'CWAL_RC_OOM', // see below! - 'CWAL_RC_FATAL', - 'CWAL_RC_CONTINUE', - 'CWAL_RC_BREAK', - 'CWAL_RC_RETURN', - 'CWAL_RC_EXIT', - 'CWAL_RC_EXCEPTION', - 'CWAL_RC_ASSERT', - 'CWAL_RC_MISUSE', - 'CWAL_RC_NOT_FOUND', - 'CWAL_RC_ALREADY_EXISTS', - 'CWAL_RC_RANGE', - 'CWAL_RC_TYPE', - 'CWAL_RC_UNSUPPORTED', - 'CWAL_RC_ACCESS', - 'CWAL_RC_IS_VISITING', - 'CWAL_RC_IS_VISITING_LIST', - 'CWAL_RC_DISALLOW_NEW_PROPERTIES', - 'CWAL_RC_DISALLOW_PROP_SET', - 'CWAL_RC_DISALLOW_PROTOTYPE_SET', - 'CWAL_RC_CONST_VIOLATION', - 'CWAL_RC_LOCKED', - 'CWAL_RC_CYCLES_DETECTED', - 'CWAL_RC_DESTRUCTION_RUNNING', - 'CWAL_RC_FINALIZED', - 'CWAL_RC_HAS_REFERENCES', - 'CWAL_RC_INTERRUPTED', - 'CWAL_RC_CANCELLED', - 'CWAL_RC_IO', - 'CWAL_RC_CANNOT_HAPPEN', - 'CWAL_RC_JSON_INVALID_CHAR', - 'CWAL_RC_JSON_INVALID_KEYWORD', - 'CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE', - 'CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE', - 'CWAL_RC_JSON_INVALID_NUMBER', - 'CWAL_RC_JSON_NESTING_DEPTH_REACHED', - 'CWAL_RC_JSON_UNBALANCED_COLLECTION', - 'CWAL_RC_JSON_EXPECTED_KEY', - 'CWAL_RC_JSON_EXPECTED_COLON', - 'CWAL_SCR_CANNOT_CONSUME', - 'CWAL_SCR_INVALID_OP', - 'CWAL_SCR_UNKNOWN_IDENTIFIER', - 'CWAL_SCR_CALL_OF_NON_FUNCTION', - 'CWAL_SCR_MISMATCHED_BRACE', - 'CWAL_SCR_MISSING_SEPARATOR', - 'CWAL_SCR_UNEXPECTED_TOKEN', - 'CWAL_SCR_UNEXPECTED_EOF', - 'CWAL_SCR_DIV_BY_ZERO', - 'CWAL_SCR_SYNTAX', - 'CWAL_SCR_EOF', - 'CWAL_SCR_TOO_MANY_ARGUMENTS', - 'CWAL_SCR_EXPECTING_IDENTIFIER', - 'S2_RC_END_EACH_ITERATION', - 'S2_RC_TOSS' - ]=>k){ - if(exception(k,0).codeString()!==k) - throw "codeString check failed for "+k; - assert 1 /* to count the above check */; - } - affirm 'CWAL_RC_EXCEPTION' === exception('CWAL_RC_OOM',1).codeString() - /* this gets changed to CWAL_RC_EXCEPTION to avoid an erroneous OOM! */; - -} - -pragma(exception-stacktrace oldStackTrace); DELETED bindings/s2/unit/825-000-define.s2 Index: bindings/s2/unit/825-000-define.s2 ================================================================== --- bindings/s2/unit/825-000-define.s2 +++ bindings/s2/unit/825-000-define.s2 @@ -1,28 +0,0 @@ -assert typeinfo(isfunction define); -const o = {a:1}; -assert o === define('__OO',o); -assert o === __OO; -assert 'CWAL_RC_ALREADY_EXISTS' === - catch define('__OO',1).codeString(); -assert 'CWAL_RC_ACCESS' === - catch define('if',1).codeString(); -assert 'CWAL_SCR_SYNTAX' === - catch define('non-identifier',1).codeString(); -assert 'CWAL_RC_ALREADY_EXISTS' === - catch {var __OO = 1}.codeString(); -assert 1 === (1 ||| defined(wrong)) /*behaves in skip-mode*/; -assert 'CWAL_RC_UNSUPPORTED' === - catch define('x',undefined).codeString(); -assert 'CWAL_RC_RANGE' ===catch define('','').codeString(); -assert 'CWAL_RC_RANGE' === - catch define( - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', - 1 - ).codeString() /* name too long */; -define('__z',1); -define('__zz',2); -define('__zzz',3); -assert o === __OO; -assert 3 === __zzz; -assert 2 === __zz; -assert 1 === __z; DELETED bindings/s2/unit/850-000-pragma.s2 Index: bindings/s2/unit/850-000-pragma.s2 ================================================================== --- bindings/s2/unit/850-000-pragma.s2 +++ bindings/s2/unit/850-000-pragma.s2 @@ -1,81 +0,0 @@ -/** - 20191230: pragma(...) was introduced to query/modify engine-level - state. -*/ -const oldStackTrace = pragma(exception-stacktrace true); - -scope { - const E = proc(){return exception(0)}; - assert pragma(exception-stacktrace); - var e = E(); - assert typeinfo(isarray e.stackTrace); - assert pragma(exception-stacktrace 5); - assert true === pragma(exception-stacktrace); - assert pragma(exception-stacktrace null); - assert false === pragma(exception-stacktrace); - e = E(); - assert undefined === e.stackTrace; - assert false === pragma(exception-stacktrace true) /* result === old value */; - e = E(); - assert typeinfo(isarray e.stackTrace); -} - -pragma(exception-stacktrace false); - -scope { - assert 0 === pragma(refcount "")/*builtin value*/; - var x; - assert 0 === pragma(refcount x)/*undefined value (builtin)*/; - x = 1000; - assert 0 < pragma(refcount x) - /* What?... the refcount is 2 from the shell and 3 here. */; -} - -scope { - assert typeinfo(isinteger pragma(sweep-interval)); - assert typeinfo(isinteger pragma(vacuum-interval)); - - var i = pragma(sweep-interval); - assert i > 0; - pragma(sweep-interval 99); - assert 99 === pragma(sweep-interval); - pragma(sweep-interval i); - assert i === pragma(sweep-interval); - - i = pragma(vacuum-interval); - assert i > 0; - pragma(vacuum-interval 99); - assert 99 === pragma(vacuum-interval); - pragma(vacuum-interval i); - assert i === pragma(vacuum-interval); - - assert 'CWAL_RC_RANGE'===catch pragma(sweep-interval -1).codeString(); - assert 'CWAL_RC_RANGE'===catch pragma(vacuum-interval -1).codeString(); -} - -scope { - const i = pragma(trace-sweep); - assert typeinfo(isinteger i); - var t = pragma(trace-sweep 2); - assert i === t; - assert 2 === pragma(trace-sweep 0); - assert 0 === pragma(trace-sweep i); -} - -scope { - const i = pragma(trace-assert); - assert typeinfo(isinteger i); - const t = pragma(trace-assert 1); - // be careful with the order, to prevent - // generating extraneous output... - assert 1 === pragma(trace-assert 0); - assert i === t; - assert 0 === pragma(trace-assert i); -} - -/* Test for pragma(trace-token-stack) intentionally elided because it - generates output to stdout. It uses the exact same code as several - other trace-xxx pragmas, though. -*/ - -pragma(exception-stacktrace oldStackTrace); DELETED bindings/s2/unit/900-000-pathfinder.s2 Index: bindings/s2/unit/900-000-pathfinder.s2 ================================================================== --- bindings/s2/unit/900-000-pathfinder.s2 +++ bindings/s2/unit/900-000-pathfinder.s2 @@ -1,119 +0,0 @@ - -scope { - assert s2.PathFinder; - const pf = new s2.PathFinder(); - assert 'PathFinder' === typeinfo(name pf); - assert pf inherits s2.PathFinder; - pf.prefix = ['/etc', '/bin', '/usr/bin']; - assert '/etc/hosts' === pf.search('hosts'); - assert pf.search('ls').indexOf('/ls') >= 0; - assert undefined === pf.search('ls',-1); - assert undefined === pf.search('no-such-file-we-hope'); - - pf.prefix = [/*'',*/ 'unit','.']; - pf.suffix = [/*'',*/ '.s2']; - const emptyScript = '000-000-0empty'; - assert pf.search(emptyScript); - assert undefined === pf.search('no-such-script'); - assert typeinfo(isstring pf.search(__FILE)); - assert catch{ - s2.PathFinder.search('anything') - }.message.indexOf('is not a PathFinder') > 0; - - // Testing special-case handling of pf.search() args: - pf.'operator->'=pf.search; - assert typeinfo(isstring pf->'000-000-0empty'); - assert (pf->emptyScript).indexOf('.s2') > 0; - - pf.prefix = ['/']; - pf.suffix = ['']; - assert undefined === pf->'etc'; - assert undefined === pf.search('etc'); - assert typeinfo(isstring pf.search('etc',1)); - assert typeinfo(isstring pf.search('etc',-1)); -} - -scope { - /* - 20200118 bugfix: previously, if the search path had no entries - then it was not possible to find a match even if the - searched-for name was a precise match by itself or in - combination with any of the configured extensions. Now the - extension list is considered even if the directory list is - empty. - */ - const pf = new s2.PathFinder(); - assert pf.search(__FILE); - pf.suffix = ['.s2']; - assert __FILE === pf.search(__FILE.split('.').shift()); -} - -scope { - /* - 20200118: added PathFinder.tokenizePath() - - These tests will fail on Windows builds because those builds - only support ';' as the path delimiter. How best to deal with - that is as-yet-undecided (and low priority: to the best of my - knowledge, s2 has never been built on Windows). - */ - const F = s2.PathFinder; - var a, path = ":/a/b/c;/d/e/f:;:/g/h/i/.;;"; - a = F.tokenizePath(path); - assert 3 === a.#; - assert '/a/b/c' === a.0; - assert '/d/e/f' === a.1; - assert '/g/h/i/.' === a.2; - assert a === F.tokenizePath(path, a); - assert 6 === a.# /* appended entries to a */; - for(var i = 0; i < 3; ++i) assert a[i]===a[i+3]; - assert 0 === F.tokenizePath('').#; - assert 'CWAL_RC_MISUSE' === catch F.tokenizePath().codeString(); - assert 'CWAL_RC_MISUSE' === catch F.tokenizePath(0/*non-string*/).codeString(); - assert 'CWAL_RC_MISUSE' === catch F.tokenizePath('',0/*non-array*/).codeString(); -} - -if(const dir = './unit'; - s2.PathFinder.fileIsAccessible(dir) ){ - /* 20200119: verify that Unique-type unwrapping works as - documented for PathFinder.search(). */ - const e = enum { - dirOnly: -1, fileOnly: 0, both: 1 - }; - const p = new s2.PathFinder([dir], ['.s2']); - assert p.search(dir, e.dirOnly); - assert p.search(dir, e.both); - assert !p.search(dir, e.fileOnly); - const f = '000-000-0empty'; - assert !p.search(f, e.dirOnly); - assert p.search(f, e.both); - assert p.search(f, e.fileOnly); -} - -scope { - /* - 20200120: test support for passing strings to the constructor. - */ - var p = new s2.PathFinder('a:b;c'); - assert !p.suffix /* was not initialized by the ctor */; - assert 3 === p.prefix.#; - assert 'c' === p.prefix.2; - assert 'b' === p.prefix.1; - assert 'a' === p.prefix.0; - - p = new s2.PathFinder(null,'a:b;c'); - assert !p.prefix /* null/undefined ctor args are silently skipped */; - assert 3 === p.suffix.#; - assert 'c' === p.suffix.2; - - p = new s2.PathFinder(undefined,null); - assert !p.prefix; - assert !p.suffix; - - p = new s2.PathFinder('',''); - assert 0 === p.prefix.#; - assert 0 === p.suffix.#; - - assert 'CWAL_RC_TYPE'===catch{new s2.PathFinder(1)} - .codeString() /* non-string/array/null/undefined argument */; -} DELETED bindings/s2/unit/900-001-ob.s2 Index: bindings/s2/unit/900-001-ob.s2 ================================================================== --- bindings/s2/unit/900-001-ob.s2 +++ bindings/s2/unit/900-001-ob.s2 @@ -1,83 +0,0 @@ -// TODO: more output buffering tests, when i'm not so tired. -scope { - assert 'object' === typename s2.ob; - const ob = s2.ob; - assert 0 === ob.level(); - assert ob === ob.push(); - print('buffered'); - var b = ob.pop(1); - assert 0 === ob.level(); - assert b inherits s2.Buffer; - assert b.length()>0; - // Reminder to self: if assertion tracing is on, assertions will generate - // output to our buffer, making exact content checks here impractical. - // But we can do... - assert b.toString().indexOf('buffered') >= 0; - //print("Buffered",b.length(),"bytes."); -} - -scope{ - const ob = s2.ob; - const out = print; - assert 1 === ob.push().level(); - out("This will be flushed to stdout."); - ob.flush(); - out("level 1"); - var v1 = ob.takeString(); - assert ob === ob.push(100); - out("This will be flushed to level 1."); - ob.flush(); - out("level 2"); - var v2 = ob.takeString(); - assert ob === ob.pop(); - var v1b = ob.takeString(); - out("discarded"); - //ob.clear()// not needed b/c pop() will do this - ob.pop(); - assert v1 === 'level 1\n'; - assert v2 === 'level 2\n'; - assert v1b.indexOf('This will be flushed to level 1.\n')>=0 - /* use indexOf() insted of === comparison because s2sh -A (assert - tracing) breaks a direct comparison by injecting assertion - tracing output into the buffer. */; -} - -scope { - const ob = s2.ob; - const fHi = proc(){s2out<<"hi"}; - var v = ob.capture(fHi); - assert "hi" === v; - v = ob.capture("s2out<<'hi again'"); - assert "hi again" === v; - v = ob.capture("s2out<<'hi'", 1); - assert typeinfo(isbuffer v); - assert "hi" === v.takeString(); - v = ob.capture(fHi, 0); - assert undefined === v; - const level = ob.level(); - v = ob.capture(proc(){ - s2out << "hi"; - ob.push(); - s2out << " again"; - // intentionally leaving extra entry: capture() will pop it. - }); - assert ob.level() === level; - assert "hi again" === v; - - const b = new s2.Buffer(/*reserving memory will bypass - an allocation optimization*/); - assert b === ob.capture(eval=>{ - s2out << "hi"; - ob.push(); - s2out << " again"; - }, b); - assert "hi again" === b.takeString(); - - assert 'CWAL_RC_MISUSE' === catch ob.capture().codeString(); - assert 'CWAL_RC_MISUSE' === catch ob.capture(b).codeString() - /* Should arguably be CWAL_RC_TYPE, but the args validation isn't - that detailed. */; - - // We cannot test the too-many-ob.pop()-calls case here without - // screwing up downstream tests. -} DELETED bindings/s2/unit/900-002-switch.s2 Index: bindings/s2/unit/900-002-switch.s2 ================================================================== --- bindings/s2/unit/900-002-switch.s2 +++ bindings/s2/unit/900-002-switch.s2 @@ -1,39 +0,0 @@ -const doSwitch = proc(switch, key, evalStrings = false){ - affirm 'object' === typeinfo(name switch); - affirm undefined !== key; - var v = switch.(key), tv = typeinfo(name v); - ('undefined'===tv) && (tv = typeinfo(name (v = switch.default))); - v || return v; - ('function' === tv) && return v.call(switch); - (evalStrings && ('string'===tv)) && return eval -> v; - return v; -}; - -scope { - const switch = { - 1: 'foo', - 2: 'bar', - default: proc(){ return 'baz' } - }; - - assert 'foo' === doSwitch(switch,1); - assert 'bar' === doSwitch(switch,2); - assert 'baz' === doSwitch(switch,99); -} - -scope { - const sw2 = { - 1: '3+3', - bar: <<0 = SQL LIKE style (case sensitive) - */ - assert typeinfo(islocal g) - /* for symmetry with a test much further down... */; - // Must throw if given any non-string arguments: - assert catch g(undefined,""); - assert catch g("",undefined); - // Throw if glob is empty (input haystack be empty): - assert 0 === catch g("","").message.indexOf('Glob string'); - - assert g("*", ""); - assert g("%", "",0); - assert g("%", "",1); - - assert g("ab*", "abcd"); - assert g("ab*", "abcd", -1 /* === default */); - assert g("[a]bc", "abc"); - assert !g("[a]bc", "Abc"); - assert g("a[bB]c", "aBc"); - - assert g("ab%d", "abcd", 0); - assert g("ab%d", "abcd", 1); - assert g("ab%d", "AbcD", 0); - assert !g("ab%d", "AbcD", 1); - - assert g("_b_d", "AbcD", 0); - assert !g("A__d", "Abcd"); - assert g("A__d", "Abcd", 1); - assert !g("A__d", "AbcD", 1); - - assert g('*☺Z', "abc☺Z"); - assert g('___☺Z', "abc☺Z", 0); - assert g('___☺Z', "abc☺Z", 1); - - assert g('%んにち%', 'abcんにちdef',0); - assert g('%んにち%', 'abcんにちdef',1); - - assert g('*ん?ち*', 'abcんにちdef'); - assert g('%ん_ち%', 'abcんにちdef',0); - assert g('%ん_ち%', 'abcんにちdef',1); - - assert g('*んにち*', 'abcんにちdef'); - assert g('%cんにちd%', 'abcんにちdef',0); - assert !g('%CんにちD%', 'abcんにちdef',1); - - const sP = "".prototype; - const oldOp1 = sP.'operator=~', oldOp2 = sP.'operator!~'; - - sP.'operator=~' = proc(glob) using (g) { return g(glob, this) }; - sP.'operator!~' = proc(glob) { return !this.'operator=~'(glob) }; - unset g; - assert !typeinfo(islocal g) - /* just proving that g is no longer in scope for the following calls - operator calls. */; - - assert 'abcんにちdef' =~ '*んにち*'; - assert "abc☺Z" =~ '*☺Z'; - assert "Abc" !~ "[a]bc"; - - sP.'operator=~' = oldOp1; - sP.'operator!~' = oldOp2; -} DELETED bindings/s2/unit/910-000-s2sh.s2 Index: bindings/s2/unit/910-000-s2sh.s2 ================================================================== --- bindings/s2/unit/910-000-s2sh.s2 +++ bindings/s2/unit/910-000-s2sh.s2 @@ -1,11 +0,0 @@ - -if(s2.sealObject) { - const o = {a:1}; - assert 1 === o.a; - assert 2 === (o.b=2); - assert o === s2.sealObject(o); - assert 2 === o.b; - assert catch {o.b=3}.codeString() === 'CWAL_RC_DISALLOW_PROP_SET'; - assert catch s2.sealObject().codeString() === 'CWAL_RC_MISUSE'; - assert catch s2.sealObject(0).codeString() === 'CWAL_RC_TYPE'; -}; DELETED bindings/s2/unit/950-000-bughunt.s2 Index: bindings/s2/unit/950-000-bughunt.s2 ================================================================== --- bindings/s2/unit/950-000-bughunt.s2 +++ bindings/s2/unit/950-000-bughunt.s2 @@ -1,44 +0,0 @@ -// this script is for testing thing which have been known to -// trigger/cause cwal-level assertions... - -if(1){ // eval-holder: these must not crash anymore... - (var a = 'x'), (a = a + 'y'), a; - /** ^^^ - - Crashes sometimes. Unfortunately very picky about combination of - string interning and recycling options, as well as the current - state of the recyling subsystem. Enabling the "eval holder" - resolves it (see s2_eval.c's EVAL_USE_HOLDER), and its unclear - whether (but it seems doubtful that) a solution which doesn't - involve the eval holder is possible without an overhaul of the - eval engine to intricately manage refs of all values in the stack - machine. That would require touching (very carefully) every - routine which manipulates the stack machine. - */ - - scope { - (var a = 'x'), (a = a + 'y'), a - /* crashes other times (without the eval holder) */ - ;; - } -} - -if(1){ - // (Re)discovered and fixed 20180507: - var a, b; - a++ ? 1 : 2; b; - /* - ==> Unexpected token after postfix operator: (OpIncrPost) ==> (Identifier) - - That triggers after the ternary expr completes, but adding a - second semicolon after the ternary expr fixes it. Other postfix - ops also trigger it. - */ - assert 1 === a; -} - -assert 'CWAL_SCR_SYNTAX' === catch{new s2.Buffer( s2. /*trailing dot*/)}.codeString() -/* That used to (until 20171205) trigger an assert in the - line-counting code because the eval loop wasn't catching the - trailing dot in that context. */ -; DELETED bindings/s2/unit/Makefile Index: bindings/s2/unit/Makefile ================================================================== --- bindings/s2/unit/Makefile +++ bindings/s2/unit/Makefile @@ -1,2 +0,0 @@ -all: - $(MAKE) -C .. unit DELETED bindings/s2/unit2/000-000-sanity.s2 Index: bindings/s2/unit2/000-000-sanity.s2 ================================================================== --- bindings/s2/unit2/000-000-sanity.s2 +++ bindings/s2/unit2/000-000-sanity.s2 @@ -1,3 +0,0 @@ -assert 'Fossil' === typename Fossil; -assert 'Db' === typename Fossil.Db; -assert 'Stmt' === typename Fossil.Db.Stmt; DELETED bindings/s2/unit2/000-075-time.s2 Index: bindings/s2/unit2/000-075-time.s2 ================================================================== --- bindings/s2/unit2/000-075-time.s2 +++ bindings/s2/unit2/000-075-time.s2 @@ -1,16 +0,0 @@ - -scope { - const tm = Fossil.time; - var n = tm.now(); - var j = tm.unixToJulian(n); - //print(n, tm.julianToUnix(j)); - //assert n === tm.julianToUnix(j) /* sometimes fails on rounding errors! Soo.... */; - 0.prototype.'operator=~' = proc(r){ - affirm 'integer' === typename r; - return (this===r) || (this===(r-1)) || (this===(r+1)); - }; - assert n =~ tm.julianToUnix(j) /* round-trip sometime rounds badly, so we check for a +/-1 match */; - unset n.prototype.'operator=~' /* avoid polluting downstream script code */; - assert 19 === tm.julianToISO8601(j).length() /* w/o ms */; - assert 23 === tm.julianToISO8601(j,true).length() /* w/ ms */; -} DELETED bindings/s2/unit2/000-100-db.s2 Index: bindings/s2/unit2/000-100-db.s2 ================================================================== --- bindings/s2/unit2/000-100-db.s2 +++ bindings/s2/unit2/000-100-db.s2 @@ -1,187 +0,0 @@ -scope { - /* Basic sanity checks for the Db and Stmt bindings. */ - var db = Fossil.Db.open(':memory:'); - assert db inherits Fossil.Db; - assert 'Db' === typename db; - //db.eachProperty(print); - - var st = db.prepare("CREATE TABLE t(a,b)"); - assert st inherits Fossil.Db.Stmt; - assert 'Stmt' === typename st; - assert undefined === st.step(); - st.finalize(); - assert 0 === catch{st.finalize()}.message.indexOf("'this'"); - st = 0; // will (in this case) clean up the wrapping part of the binding immediately - - assert db === db.execMulti(<<:min AND a<$max', - bind: {':min':1, $max:10} /* note that the ':' resp '$' is mandatory, - but $ is an identifier char and need not - be quoted. */, - mode: 0, // 0 === fetch each row as an object - callback: proc(){ - counter = rowNumber; - assert 'object' === typename this; - lastCol = this.a; - //print("row", rowNumber, this) - } - }); - assert 1 === counter; - assert 3 === lastCol; - - st = db.prepare('select a a, b b from t where a>?1 AND a?1'/*checking if binding ?N works right*/); - st.bind([0, 10]); - counter = 0; - while(st.step()){++counter} - assert 2 === counter; - st.reset(); - st.bind(1, 1).bind(2,5); - counter = 0; - lastCol = 0; - while(var row = st.stepArray()){ - ++counter; - lastCol = row.1; - } - st.finalize(); - assert counter === 1; - assert lastCol === 4; - - counter = 0; - db.each({sql:'select * from t order by a', - callback:'++counter>0 && return false' - }); - assert 1 === counter; - - assert 42 === catch{ - db.each({sql:'select * from t order by a', - callback:'throw 42'}) - }.message; - - counter = 0; - db.exec("create temp table ttmmpp(a)", - "insert into ttmmpp values(1),(2),(3)" - ) - .each({sql:"select a from ttmmpp", - callback: 'counter += this.0'}) - ; - assert 6 === counter; - - // Make sure transactions basically work... - assert 0 === db.transactionState(); - db.begin(); - assert 1 === db.transactionState(); - db.exec("update ttmmpp set a=NULL"); - assert null === db.selectValue('select a from ttmmpp'); - db.begin(); // nested! - assert 2 === db.transactionState(); - db.exec("update ttmmpp set a=0"); - assert 0 === db.selectValue('select a from ttmmpp'); - db.rollback(); // only queues up the rollback b/c we are nested - assert -1 === db.transactionState()/* negated value of current transaction level */; - db.commit(); // actually rolls back b/c of previous rollback() call - assert 0 === db.transactionState(); - assert 1 === db.selectValues('select a from ttmmpp order by a').0; - assert 3 === db.selectValue('select a from ttmmpp order by a desc'); - - /* - Alternately (more simply) use db.transaction() to handle the - begin/commit/rollback() management. Give it a function and it - will begin(), call the function, and either commit or roll back - (if the function call throws/propagates an error). - */ - const rollbackMsg = "Will roll back."; - var ex = catch db.transaction(proc(){ - // In this callback, "this" is the db instance. - this.exec("update ttmmpp set a=NULL"); - assert null === this.selectValue('select a from ttmmpp'); - assert 1 === this.transactionState(); - throw rollbackMsg; - }); - assert 0 === db.transactionState(); - assert ex; - assert rollbackMsg === ex.message; - assert 1 === db.selectValue('select a from ttmmpp order by a'); - ex = catch db.transaction(proc(){ - assert 1 === this.transactionState(); - this.exec("update ttmmpp set a=NULL"); - }); - assert !ex; - assert 0 === db.transactionState(); - assert null === db.selectValue('select a from ttmmpp','default'); - - // Once more, this time nested... - ex = catch db.transaction(proc(){ - this.exec("update ttmmpp set a=1"); - this.transaction(proc(){ - assert 1 === this.selectValue('select a from ttmmpp'); - this.exec("update ttmmpp set a=2"); - assert 2 === db.transactionState(); - // Making sure script-stopper events are not downgraded - // to exceptions: - //assert !'just testing assert() propagation'; - //exit 'just testing exit() propagation'; - //fatal 'just testing fatal() propagation'; - throw 42; - }); - throw "Never reached."; - this.exec("update ttmmpp set a=3"); - }); - //print(__FLC,"ex =",ex); - assert 0 === db.transactionState(); - assert ex; - assert 42 === ex.message; - assert null === db.selectValue('select a from ttmmpp'); - - - // A Db is closed automatically when it is GC'd, but we - // can manually close them if needed: - db.close(); - assert 0 === catch{db.close()}.message.indexOf("'this'"); -} DELETED bindings/s2/unit2/000-200-context.s2 Index: bindings/s2/unit2/000-200-context.s2 ================================================================== --- bindings/s2/unit2/000-200-context.s2 +++ bindings/s2/unit2/000-200-context.s2 @@ -1,125 +0,0 @@ -assert 'Fossil' === typename Fossil; -assert 'Db' === typename Fossil.Db; -assert 'Stmt' === typename Fossil.Db.Stmt; -assert 'Context' === typename Fossil.Context; - -scope { - var f = new Fossil.Context({traceSql:false}); - //print('f =',f); - assert 'Context' === typename f; - assert f inherits Fossil.Context; - const DB = f.db; - assert DB inherits Fossil.Db; - assert DB.name === 'main' /* this is our "main" db */; - assert !DB.checkout; - assert !DB.repo; - assert !DB.config; - - assert f === f.openCheckout(); - assert DB inherits Fossil.Db; - assert DB.repo inherits Fossil.Db; - assert DB.checkout inherits Fossil.Db; - - assert DB.checkout.name = 'ckout'; - assert DB.repo.name === 'repo'; - assert DB.filename === ':memory:' - || DB.filename === '' /* similar mechanism with different - semantics vis-a-vis temp storage */; - assert DB.checkout.filename; - assert DB.filename !== DB.checkout.filename; - assert DB.repo.filename; - assert DB.repo.filename !== DB.checkout.filename; - - assert !DB.config; - f.openConfig(); - assert DB.config inherits Fossil.Db; - assert DB.config.name === 'cfg'; - assert DB.config.filename; - if(0){ - print('DB.name =',DB.name); - print('DB.filename =',DB.filename); - print('DB.repo.name =',DB.repo.name); - print('DB.repo.filename =',DB.repo.filename); - print('DB.checkout.name =',DB.checkout.name); - print('DB.checkout.filename =',DB.checkout.filename); - print('DB.config.name =',DB.config.name); - print('DB.config.filename =',DB.config.filename); - } - var counter = 0; - assert DB === DB.each({ - sql:<<<_SQL - SELECT mtime FROM event LIMIT 2 - _SQL, - callback:'++counter' - }); - assert 2 === counter; - unset counter; - - /* These UUIDs are only valid for the libfossil tree... */ - assert '99237c3636730f20ed07b227c5092c087aea8b0c' === f.symToUuid('rid:1'); - assert 1 === f.symToRid('99237c3636730f20'); - - const trunkUuid = f.symToUuid('trunk'); - assert (var b = f.loadBlob('trunk')) inherits s2.Buffer; - assert !b.isCompressed(); - assert b.length() > 0; - assert b.sha1() === trunkUuid; - - b.reset() << "fossil"; - assert b.sha3() === '226558c59bda6533fb0869abd36dec2e8fee1003247b98390b5cb639285c0b79'; - //const trunkZCard = b.toString(b.length()-33, 32); - //print(b,trunkZCard); - - b = f.artifactDiff('prev', trunkUuid, { - // all available options (not all combinations make sense and some trump others): - html: false, - text: true, - inline: false, - sbsWidth: 60, - contextLines: 5, - invert: false - }); - assert b inherits s2.Buffer; - assert b.length() > 10; - assert !b.isCompressed(); - unset b; - - assert 'object' === typename (var d = f.loadManifest('trunk')); - assert d.uuid === trunkUuid; - assert d.D > 1.0; - assert 'array' === typename d.F; - assert 40 === d.P.0.length(); - //assert d.Z === trunkZCard; // doh: fsl_deck does not expose the Z-card b/c it's only internally generated/used. - //print(d); - unset d; - -// if(typeinfo(isfunction Fossil.Db.selectValue /* installed by init script, but the -// valgrind tests don't load that. */)){ -// assert 0 < f.db.config.selectValue("select count(*) from global_config"); -// } - - scope { - var loginCookie = f.loginCookieName(); - assert 'string' === typename loginCookie; - assert 0 === loginCookie.indexOf('fossil-'); - assert 23 === loginCookie.length(); - } - - if(1){ - if(0){ - /* Has the same effect as else block below... */ - f.finalize(); - assert !f.db; - }else{ - assert f === f.close(); - assert !f.db; - assert undefined === f.finalize() /* explicitly frees all C-level resources (or leave it to GC) */; - } - assert 0 === catch{f.openCheckout()}.message.indexOf("'this' is not") /* native was disconnected in finalize() */; - //f.openCheckout(); - assert 0 === catch{DB.prepare('select 1')}.message.indexOf("'this' is not") - /* The native DB handle held by DB was (with valgrind's help!) disconnected - from it when f.close() or f.finalize() were called. - */; - } -} DELETED bindings/s2/unit2/Makefile Index: bindings/s2/unit2/Makefile ================================================================== --- bindings/s2/unit2/Makefile +++ bindings/s2/unit2/Makefile @@ -1,2 +0,0 @@ -all: - $(MAKE) -C .. unit DELETED bindings/s2/vg.make Index: bindings/s2/vg.make ================================================================== --- bindings/s2/vg.make +++ bindings/s2/vg.make @@ -1,106 +0,0 @@ -# We keep these in a separate file to avoid a rebuild when changing -# these flags. Damn... some automatic rules foil us here. -######################################################################## -# Valgrind sanity checks... -VG := $(call ShakeNMake.CALL.FIND_FILE,valgrind) -ifneq (,$(VG)) -VG_REPORT := VG.report.csv -VG_FLAGS ?= --leak-check=full -v --show-reachable=yes --track-origins=yes -#SCRIPT_LIST := $(shell ls -1 test-[0-9]*.s2 | sort) -# Whether or not to collect massif-based stats... -RUN_MASSIF := 1 -.PHONY: vg -# Reminder: don't use -A b/c it breaks output buffering tests! -VG_SHELL_FLAGS=--a -S -VG_UNIT_RUN_CMD = ./$(f-s2sh.BIN) $(VG_SHELL_FLAGS) -vg: $(f-s2sh.BIN) $(UNIT_GENERATED) - @echo "Running: $(VG) $(VG_FLAGS) $(VG_UNIT_RUN_CMD) ..."; \ - export LD_LIBRARY_PATH="$(TOP_SRCDIR):$${LD_LIBRARY_PATH}"; \ - massif_tmp=tmp.massif; \ - for i in $(sort $(UNIT_SCRIPT_LIST)) $(UNIT_GENERATED); do \ - vgout=$$i.vg; \ - xtraargs="-o $$i._out -f $$i"; \ - cmd="$(VG) $(VG_FLAGS) $(VG_UNIT_RUN_CMD) $$xtraargs"; \ - echo -n "**** Valgrinding [$$i]: "; \ - $$cmd 2>&1 | tee $$vgout | grep 'total heap usage' || exit $$?; \ - grep 'ERROR SUMMARY' $$vgout | grep -v ' 0 errors' && echo "See $$vgout!"; \ - vgout=$$i.massif; \ - cmd="$(VG) --tool=massif --time-unit=B --massif-out-file=$$vgout --heap-admin=0 $(VG_UNIT_RUN_CMD) $$xtraargs"; \ - test x1 = x$(RUN_MASSIF) || continue; \ - echo -n "**** Massifing [$$i]: "; \ - $$cmd > $$massif_tmp 2>&1 || exit $$?; \ - echo -n "==> $$vgout Peak RAM: "; \ - ms_print $$vgout | perl -n -e 'if(m/^(\s+)([KM]B)$$/){my $$x=$$2; $$_=<>; m/^(\d[^^]+)/; print $$1." ".$$x; exit 0;}'; \ - echo; \ - done; \ - rm -f $$massif_tmp - @test x1 = x$(RUN_MASSIF) || exit 0; \ - echo "Done running through s2 scripts. Collecting stats..."; \ - echo 'script,allocs,frees,totalMemory,peakMemUsage,peakMemUsageUnit' > $(VG_REPORT); \ - for i in $$(ls -1 unit/*.s2.vg unit2/*.s2.vg | sort) $(patsubst %.s2,%.s2.vg,$(UNIT_GENERATED)); do \ - base=$${i%%.vg}; \ - echo -n "$$base,"; \ - grep 'total heap usage' $$i | sed -e 's/,//g' -e 's/^\.\///' | awk '{printf "%d,%d,%d,",$$5,$$7,$$9}'; \ - ms_print $${base}.massif | perl -n -e 'if(m/^(\s+)([KM]B)$$/){my $$x=$$2; $$_=<>; m/^(\d[^^]+)/; print $$1.",".$$x; exit 0;}'; \ - echo; \ - done >> $(VG_REPORT); \ - rm -f unit/ms_print.tmp.* unit2/ms_print.tmp.* ms_print.tmp.*; \ - echo "Stats are in $(VG_REPORT):"; \ - tr ',' '\t' < $(VG_REPORT) - -CLEAN_FILES += $(wildcard \ - $(patsubst %.s2,%.s2.vg,$(UNIT_GENERATED)) \ - $(patsubst %,unit/%,*.vg *._out *.massif *.db *~ *.uncompressed *.z *.zip *.2) \ - $(patsubst %,unit2/%,*.vg *._out *.massif *.db *~ *.uncompressed *.z *.zip *.2) \ - ) -# 'vg' proxies which tweak various s2/cwal-level optimizations... -#vgp: VG_SHELL_FLAGS+=--p -#vgp: VG_REPORT:=VG.report-p.csv -#vgp: vg -vgr: VG_SHELL_FLAGS+=--R -C -S -vgr: VG_REPORT:=VG.report-r.csv -vgr: vg -vgs: VG_SHELL_FLAGS+=--S -R -C -vgs: VG_REPORT:=VG.report-s.csv -vgs: vg -#vgt: VG_SHELL_FLAGS+=--t -#vgt: VG_REPORT:=VG.report-t.csv -#vgt: vg -vgrs: VG_SHELL_FLAGS+=--R -C --S -vgrs: VG_REPORT:=VG.report-rs.csv -vgrs: vg -vgrsc: VG_SHELL_FLAGS+=--R --C --S -vgrsc: VG_REPORT:=VG.report-rsc.csv -vgrsc: vg -# vgrst: VG_SHELL_FLAGS+=--r --s --t -# vgrst: VG_REPORT:=VG.report-rst.csv -# vgrst: vg -# vgprst: VG_SHELL_FLAGS+=--r --s --t --p -# vgprst: VG_REPORT:=VG.report-prst.csv -# vgprst: vg -VG_REPORT_MEGA := VG.report-mega.csv -CLEAN_FILES += $(VG_REPORT_MEGA) -VG_COMMANDS := \ - $(MAKE) vg \ - && $(MAKE) vgr \ - && $(MAKE) vgs \ - && $(MAKE) vgrs \ - && $(MAKE) vgrsc - -vgall: - @start=$$(date); \ - echo "Start time: $$start"; \ - $(VG_COMMANDS) || exit; \ - echo "Run time: $$start - $$(date)"; \ - rm -f $(VG_REPORT_MEGA); \ - for i in VG.report*.csv; do \ - test -s $$i || continue; \ - echo $$i | grep t-mega >/dev/null && continue; \ - echo "Report: $$i"; \ - cut -d, -f1,2,4,5 $$i | tr ',' '\t'; \ - done > $(VG_REPORT_MEGA); \ - echo "Consolidated valgrind report is in [$(VG_REPORT_MEGA)]." - -endif -#/$(VG) -######################################################################## Index: compile_flags.txt ================================================================== --- compile_flags.txt +++ compile_flags.txt @@ -1,8 +1,10 @@ -std=c89 -I include +-I +src -isystem /usr/local/include -isystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include -isystem DELETED doc/Doxyfile.in Index: doc/Doxyfile.in ================================================================== --- doc/Doxyfile.in +++ doc/Doxyfile.in @@ -1,2525 +0,0 @@ -# Doxyfile 1.8.17 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = libfossil - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = NO - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = YES - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = NO - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = NO - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 5 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = NO - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = YES - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = YES - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = YES - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = YES - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = YES - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = YES - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = YES - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = YES - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text " - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = ../include - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.h \ - *.hpp \ - fsl_schema_*_cstr.c - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = YES - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = *test* - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = *_INCLUDED* - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = "sed -e 's/^\s*\*\*//'" - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = YES - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = NO - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files -# were built. This is equivalent to specifying the "-p" option to a clang tool, -# such as clang-check. These options will then be passed to the parser. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -CLANG_DATABASE_PATH = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = libfossil-API-html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = YES - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 1 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 300 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /