summaryrefslogtreecommitdiffstats
path: root/tool
diff options
context:
space:
mode:
Diffstat (limited to 'tool')
-rw-r--r--tool/GetFile.cs471
-rw-r--r--tool/GetTclKit.bat333
-rw-r--r--tool/Replace.cs223
-rwxr-xr-xtool/build-all-msvc.bat864
-rw-r--r--tool/build-shell.sh21
-rwxr-xr-xtool/cg_anno.tcl79
-rw-r--r--tool/checkSpacing.c84
-rw-r--r--tool/cktclsh.sh11
-rw-r--r--tool/custom.txt1246
-rw-r--r--tool/dbhash.c491
-rw-r--r--tool/dbtotxt.c188
-rw-r--r--tool/dbtotxt.md56
-rw-r--r--tool/enlargedb.c68
-rwxr-xr-xtool/extract-sqlite3h.tcl21
-rw-r--r--tool/extract.c46
-rw-r--r--tool/fast_vacuum.c234
-rw-r--r--tool/fragck.tcl149
-rw-r--r--tool/fuzzershell.c1267
-rw-r--r--tool/genfkey.README137
-rw-r--r--tool/genfkey.test353
-rw-r--r--tool/getlock.c134
-rw-r--r--tool/index_usage.c233
-rw-r--r--tool/kvtest-speed.sh35
-rw-r--r--tool/lemon.c5916
-rw-r--r--tool/lempar.c1068
-rw-r--r--tool/libvers.c15
-rw-r--r--tool/loadfts.c242
-rw-r--r--tool/logest.c173
-rw-r--r--tool/max-limits.c41
-rw-r--r--tool/merge-test.tcl99
-rw-r--r--tool/mkautoconfamal.sh101
-rwxr-xr-xtool/mkccode.tcl93
-rwxr-xr-xtool/mkctimec.tcl458
-rw-r--r--tool/mkkeywordhash.c716
-rw-r--r--tool/mkmsvcmin.tcl113
-rw-r--r--tool/mkopcodec.tcl51
-rw-r--r--tool/mkopcodeh.tcl325
-rw-r--r--tool/mkopts.tcl51
-rw-r--r--tool/mkpragmatab.tcl637
-rw-r--r--tool/mkshellc.tcl75
-rw-r--r--tool/mksourceid.c835
-rw-r--r--tool/mkspeedsql.tcl237
-rw-r--r--tool/mksqlite3c-noext.tcl367
-rw-r--r--tool/mksqlite3c.tcl488
-rw-r--r--tool/mksqlite3h.tcl165
-rw-r--r--tool/mksqlite3internalh.tcl148
-rw-r--r--tool/mktoolzip.tcl69
-rw-r--r--tool/mkvsix.tcl840
-rw-r--r--tool/offsets.c329
-rw-r--r--tool/omittest-msvc.tcl99
-rw-r--r--tool/omittest.tcl370
-rw-r--r--tool/opcodesum.tcl34
-rw-r--r--tool/pagesig.c92
-rw-r--r--tool/replace.tcl23
-rw-r--r--tool/restore_jrnl.tcl233
-rw-r--r--tool/rollback-test.c155
-rw-r--r--tool/run-speed-test.sh90
-rw-r--r--tool/showdb.c1251
-rw-r--r--tool/showjournal.c138
-rw-r--r--tool/showlocks.c99
-rw-r--r--tool/showshm.c158
-rw-r--r--tool/showstat4.c163
-rw-r--r--tool/showwal.c639
-rw-r--r--tool/soak1.tcl103
-rw-r--r--tool/spaceanal.tcl895
-rw-r--r--tool/speed-check.sh219
-rw-r--r--tool/speedtest.tcl275
-rw-r--r--tool/speedtest16.c171
-rw-r--r--tool/speedtest2.tcl207
-rw-r--r--tool/speedtest8.c260
-rw-r--r--tool/speedtest8inst1.c218
-rwxr-xr-xtool/spellsift.tcl74
-rw-r--r--tool/split-sqlite3c.tcl96
-rw-r--r--tool/sqldiff.c2055
-rw-r--r--tool/sqlite3_analyzer.c.in76
-rw-r--r--tool/sqltclsh.c.in51
-rw-r--r--tool/sqltclsh.tcl71
-rw-r--r--tool/src-verify.c956
-rw-r--r--tool/srcck1.c158
-rw-r--r--tool/srctree-check.tcl103
-rw-r--r--tool/stack_usage.tcl98
-rw-r--r--tool/stripccomments.c228
-rw-r--r--tool/symbols-mingw.sh33
-rw-r--r--tool/symbols.sh36
-rw-r--r--tool/varint.c123
-rw-r--r--tool/vdbe-compress.tcl143
-rw-r--r--tool/vdbe_profile.tcl96
-rw-r--r--tool/version-info.c106
-rw-r--r--tool/warnings-clang.sh14
-rw-r--r--tool/warnings.sh65
-rw-r--r--tool/win/sqlite.vsixbin0 -> 32825 bytes
91 files changed, 30870 insertions, 0 deletions
diff --git a/tool/GetFile.cs b/tool/GetFile.cs
new file mode 100644
index 0000000..1784a79
--- /dev/null
+++ b/tool/GetFile.cs
@@ -0,0 +1,471 @@
+/*
+** 2015 October 7
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C# code to download a single file based on a URI.
+*/
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+///////////////////////////////////////////////////////////////////////////////
+
+#region Assembly Metadata
+[assembly: AssemblyTitle("GetFile Tool")]
+[assembly: AssemblyDescription("Download a single file based on a URI.")]
+[assembly: AssemblyCompany("SQLite Development Team")]
+[assembly: AssemblyProduct("SQLite")]
+[assembly: AssemblyCopyright("Public Domain")]
+[assembly: ComVisible(false)]
+[assembly: Guid("5c4b3728-1693-4a33-a218-8e6973ca15a6")]
+[assembly: AssemblyVersion("1.0.*")]
+
+#if DEBUG
+[assembly: AssemblyConfiguration("Debug")]
+#else
+[assembly: AssemblyConfiguration("Release")]
+#endif
+#endregion
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace GetFile
+{
+ /// <summary>
+ /// This enumeration is used to represent all the possible exit codes from
+ /// this tool.
+ /// </summary>
+ internal enum ExitCode
+ {
+ /// <summary>
+ /// The file download was a success.
+ /// </summary>
+ Success = 0,
+
+ /// <summary>
+ /// The command line arguments are missing (i.e. null). Generally,
+ /// this should not happen.
+ /// </summary>
+ MissingArgs = 1,
+
+ /// <summary>
+ /// The wrong number of command line arguments was supplied.
+ /// </summary>
+ WrongNumArgs = 2,
+
+ /// <summary>
+ /// The URI specified on the command line could not be parsed as a
+ /// supported absolute URI.
+ /// </summary>
+ BadUri = 3,
+
+ /// <summary>
+ /// The file name portion of the URI specified on the command line
+ /// could not be extracted from it.
+ /// </summary>
+ BadFileName = 4,
+
+ /// <summary>
+ /// The temporary directory is either invalid (i.e. null) or does not
+ /// represent an available directory.
+ /// </summary>
+ BadTempPath = 5,
+
+ /// <summary>
+ /// An exception was caught in <see cref="Main" />. Generally, this
+ /// should not happen.
+ /// </summary>
+ Exception = 6,
+
+ /// <summary>
+ /// The file download was canceled. This tool does not make use of
+ /// the <see cref="WebClient.CancelAsync" /> method; therefore, this
+ /// should not happen.
+ /// </summary>
+ DownloadCanceled = 7,
+
+ /// <summary>
+ /// The file download encountered an error. Further information about
+ /// this error should be displayed on the console.
+ /// </summary>
+ DownloadError = 8
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ internal static class Program
+ {
+ #region Private Data
+ /// <summary>
+ /// This is used to synchronize multithreaded access to the
+ /// <see cref="previousPercent" /> and <see cref="exitCode"/>
+ /// fields.
+ /// </summary>
+ private static readonly object syncRoot = new object();
+
+ ///////////////////////////////////////////////////////////////////////
+
+ /// <summary>
+ /// This event will be signed when the file download has completed,
+ /// even if the file download itself was canceled or unsuccessful.
+ /// </summary>
+ private static EventWaitHandle doneEvent;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ /// <summary>
+ /// The previous file download completion percentage seen by the
+ /// <see cref="DownloadProgressChanged" /> event handler. This value
+ /// is never decreased, nor is it ever reset to zero.
+ /// </summary>
+ private static int previousPercent = 0;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ /// <summary>
+ /// This will be the exit code returned by this tool after the file
+ /// download completes, successfully or otherwise. This value is only
+ /// changed by the <see cref="DownloadFileCompleted" /> event handler.
+ /// </summary>
+ private static ExitCode exitCode = ExitCode.Success;
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////
+
+ #region Private Support Methods
+ /// <summary>
+ /// This method displays an error message to the console and/or
+ /// displays the command line usage information for this tool.
+ /// </summary>
+ /// <param name="message">
+ /// The error message to display, if any.
+ /// </param>
+ /// <param name="usage">
+ /// Non-zero to display the command line usage information.
+ /// </param>
+ private static void Error(
+ string message,
+ bool usage
+ )
+ {
+ if (message != null)
+ Console.WriteLine(message);
+
+ string fileName = Path.GetFileName(
+ Process.GetCurrentProcess().MainModule.FileName);
+
+ Console.WriteLine(String.Format(
+ "usage: {0} <uri> [fileName]", fileName));
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ /// <summary>
+ /// This method attempts to determine the file name portion of the
+ /// specified URI.
+ /// </summary>
+ /// <param name="uri">
+ /// The URI to process.
+ /// </param>
+ /// <returns>
+ /// The file name portion of the specified URI -OR- null if it cannot
+ /// be determined.
+ /// </returns>
+ private static string GetFileName(
+ Uri uri
+ )
+ {
+ if (uri == null)
+ return null;
+
+ string pathAndQuery = uri.PathAndQuery;
+
+ if (String.IsNullOrEmpty(pathAndQuery))
+ return null;
+
+ int index = pathAndQuery.LastIndexOf('/');
+
+ if ((index < 0) || (index == pathAndQuery.Length))
+ return null;
+
+ return pathAndQuery.Substring(index + 1);
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////
+
+ #region Private Event Handlers
+ /// <summary>
+ /// This method is an event handler that is called when the file
+ /// download completion percentage changes. It will display progress
+ /// on the console. Special care is taken to make sure that progress
+ /// events are not displayed out-of-order, even if duplicate and/or
+ /// out-of-order events are received.
+ /// </summary>
+ /// <param name="sender">
+ /// The source of the event.
+ /// </param>
+ /// <param name="e">
+ /// Information for the event being processed.
+ /// </param>
+ private static void DownloadProgressChanged(
+ object sender,
+ DownloadProgressChangedEventArgs e
+ )
+ {
+ if (e != null)
+ {
+ int percent = e.ProgressPercentage;
+
+ lock (syncRoot)
+ {
+ if (percent > previousPercent)
+ {
+ Console.Write('.');
+
+ if ((percent % 10) == 0)
+ Console.Write(" {0}% ", percent);
+
+ previousPercent = percent;
+ }
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ /// <summary>
+ /// This method is an event handler that is called when the file
+ /// download has completed, successfully or otherwise. It will
+ /// display the overall result of the file download on the console,
+ /// including any <see cref="Exception" /> information, if applicable.
+ /// The <see cref="exitCode" /> field is changed by this method to
+ /// indicate the overall result of the file download and the event
+ /// within the <see cref="doneEvent" /> field will be signaled.
+ /// </summary>
+ /// <param name="sender">
+ /// The source of the event.
+ /// </param>
+ /// <param name="e">
+ /// Information for the event being processed.
+ /// </param>
+ private static void DownloadFileCompleted(
+ object sender,
+ AsyncCompletedEventArgs e
+ )
+ {
+ if (e != null)
+ {
+ lock (syncRoot)
+ {
+ if (previousPercent < 100)
+ Console.Write(' ');
+ }
+
+ if (e.Cancelled)
+ {
+ Console.WriteLine("Canceled");
+
+ lock (syncRoot)
+ {
+ exitCode = ExitCode.DownloadCanceled;
+ }
+ }
+ else
+ {
+ Exception error = e.Error;
+
+ if (error != null)
+ {
+ Console.WriteLine("Error: {0}", error);
+
+ lock (syncRoot)
+ {
+ exitCode = ExitCode.DownloadError;
+ }
+ }
+ else
+ {
+ Console.WriteLine("Done");
+ }
+ }
+ }
+
+ if (doneEvent != null)
+ doneEvent.Set();
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////
+
+ #region Program Entry Point
+ /// <summary>
+ /// This is the entry-point for this tool. It handles processing the
+ /// command line arguments, setting up the web client, downloading the
+ /// file, and saving it to the file system.
+ /// </summary>
+ /// <param name="args">
+ /// The command line arguments.
+ /// </param>
+ /// <returns>
+ /// Zero upon success; non-zero on failure. This will be one of the
+ /// values from the <see cref="ExitCode" /> enumeration.
+ /// </returns>
+ private static int Main(
+ string[] args
+ )
+ {
+ //
+ // NOTE: Sanity check the command line arguments.
+ //
+ if (args == null)
+ {
+ Error(null, true);
+ return (int)ExitCode.MissingArgs;
+ }
+
+ if ((args.Length < 1) || (args.Length > 2))
+ {
+ Error(null, true);
+ return (int)ExitCode.WrongNumArgs;
+ }
+
+ //
+ // NOTE: Attempt to convert the first (and only) command line
+ // argument to an absolute URI.
+ //
+ Uri uri;
+
+ if (!Uri.TryCreate(args[0], UriKind.Absolute, out uri))
+ {
+ Error("Could not create absolute URI from argument.", false);
+ return (int)ExitCode.BadUri;
+ }
+
+ //
+ // NOTE: If a file name was specified on the command line, try to
+ // use it (without its directory name); otherwise, fallback
+ // to using the file name portion of the URI.
+ //
+ string fileName = (args.Length == 2) ?
+ Path.GetFileName(args[1]) : null;
+
+ if (String.IsNullOrEmpty(fileName))
+ {
+ //
+ // NOTE: Attempt to extract the file name portion of the URI
+ // we just created.
+ //
+ fileName = GetFileName(uri);
+
+ if (fileName == null)
+ {
+ Error("Could not extract file name from URI.", false);
+ return (int)ExitCode.BadFileName;
+ }
+ }
+
+ //
+ // NOTE: Grab the temporary path setup for this process. If it is
+ // unavailable, we will not continue.
+ //
+ string directory = Path.GetTempPath();
+
+ if (String.IsNullOrEmpty(directory) ||
+ !Directory.Exists(directory))
+ {
+ Error("Temporary directory is invalid or unavailable.", false);
+ return (int)ExitCode.BadTempPath;
+ }
+
+ try
+ {
+ //
+ // HACK: For use of the TLS 1.2 security protocol because some
+ // web servers fail without it. In order to support the
+ // .NET Framework 2.0+ at compilation time, must use its
+ // integer constant here.
+ //
+ ServicePointManager.SecurityProtocol =
+ (SecurityProtocolType)0xC00;
+
+ using (WebClient webClient = new WebClient())
+ {
+ //
+ // NOTE: Create the event used to signal completion of the
+ // file download.
+ //
+ doneEvent = new ManualResetEvent(false);
+
+ //
+ // NOTE: Hookup the event handlers we care about on the web
+ // client. These are necessary because the file is
+ // downloaded asynchronously.
+ //
+ webClient.DownloadProgressChanged +=
+ new DownloadProgressChangedEventHandler(
+ DownloadProgressChanged);
+
+ webClient.DownloadFileCompleted +=
+ new AsyncCompletedEventHandler(
+ DownloadFileCompleted);
+
+ //
+ // NOTE: Build the fully qualified path and file name,
+ // within the temporary directory, where the file to
+ // be downloaded will be saved.
+ //
+ fileName = Path.Combine(directory, fileName);
+
+ //
+ // NOTE: If the file name already exists (in the temporary)
+ // directory, delete it.
+ //
+ // TODO: Perhaps an error should be raised here instead?
+ //
+ if (File.Exists(fileName))
+ File.Delete(fileName);
+
+ //
+ // NOTE: After kicking off the asynchronous file download
+ // process, wait [forever] until the "done" event is
+ // signaled.
+ //
+ Console.WriteLine(
+ "Downloading \"{0}\" to \"{1}\"...", uri, fileName);
+
+ webClient.DownloadFileAsync(uri, fileName);
+ doneEvent.WaitOne();
+ }
+
+ lock (syncRoot)
+ {
+ return (int)exitCode;
+ }
+ }
+ catch (Exception e)
+ {
+ //
+ // NOTE: An exception was caught. Report it via the console
+ // and return failure.
+ //
+ Error(e.ToString(), false);
+ return (int)ExitCode.Exception;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/tool/GetTclKit.bat b/tool/GetTclKit.bat
new file mode 100644
index 0000000..cd295a0
--- /dev/null
+++ b/tool/GetTclKit.bat
@@ -0,0 +1,333 @@
+@ECHO OFF
+
+::
+:: GetTclKit.bat --
+::
+:: TclKit Download Tool
+::
+
+SETLOCAL
+
+REM SET __ECHO=ECHO
+REM SET __ECHO2=ECHO
+REM SET __ECHO3=ECHO
+IF NOT DEFINED _AECHO (SET _AECHO=REM)
+IF NOT DEFINED _CECHO (SET _CECHO=REM)
+IF NOT DEFINED _CECHO2 (SET _CECHO2=REM)
+IF NOT DEFINED _CECHO3 (SET _CECHO3=REM)
+IF NOT DEFINED _VECHO (SET _VECHO=REM)
+
+SET OVERWRITE=^>
+IF DEFINED __ECHO SET OVERWRITE=^^^>
+
+SET APPEND=^>^>
+IF DEFINED __ECHO SET APPEND=^^^>^^^>
+
+SET PROCESSOR=%1
+
+IF DEFINED PROCESSOR (
+ CALL :fn_UnquoteVariable PROCESSOR
+) ELSE (
+ GOTO usage
+)
+
+SET PROCESSOR=%PROCESSOR:AMD64=x64%
+
+%_VECHO% Processor = '%PROCESSOR%'
+
+SET DUMMY2=%2
+
+IF DEFINED DUMMY2 (
+ GOTO usage
+)
+
+IF NOT DEFINED ENVDIR (
+ SET ENVDIR=%CD%
+)
+
+%_VECHO% EnvDir = '%ENVDIR%'
+
+SET TOOLS=%~dp0
+SET TOOLS=%TOOLS:~0,-1%
+
+%_VECHO% Tools = '%TOOLS%'
+
+IF NOT DEFINED windir (
+ ECHO The windir environment variable must be set first.
+ GOTO errors
+)
+
+%_VECHO% WinDir = '%windir%'
+
+IF NOT DEFINED TEMP (
+ ECHO The TEMP environment variable must be set first.
+ GOTO errors
+)
+
+%_VECHO% Temp = '%TEMP%'
+
+IF NOT DEFINED TCLKIT_URI (
+ SET TCLKIT_URI=https://urn.to/r/tclsh/
+)
+
+%_VECHO% TclKitUri = '%TCLKIT_URI%'
+
+IF NOT DEFINED TCLKIT_PATCHLEVEL (
+ SET TCLKIT_PATCHLEVEL=8.6.6
+)
+
+%_VECHO% TclKitPatchLevel = '%TCLKIT_PATCHLEVEL%'
+
+IF NOT DEFINED TCLKIT_EXE_PATCHLEVEL (
+ SET TCLKIT_EXE_PATCHLEVEL=8.6.4
+)
+
+%_VECHO% TclKitExePatchLevel = '%TCLKIT_EXE_PATCHLEVEL%'
+
+IF /I "%PROCESSOR%" == "x86" (
+ CALL :fn_TclKitX86Variables
+
+ IF ERRORLEVEL 1 (
+ GOTO errors
+ )
+) ELSE IF /I "%PROCESSOR%" == "x64" (
+ CALL :fn_TclKitX64Variables
+
+ IF ERRORLEVEL 1 (
+ GOTO errors
+ )
+) ELSE (
+ GOTO usage
+)
+
+%_VECHO% TclKitVersion = '%TCLKIT_VERSION%'
+%_VECHO% TclKitPatchLevel = '%TCLKIT_PATCHLEVEL%'
+%_VECHO% TclKitExePatchLevel = '%TCLKIT_EXE_PATCHLEVEL%'
+%_VECHO% TclKitNoEnv = '%TCLKIT_NOENV%'
+%_VECHO% TclKitNoSdk = '%TCLKIT_NOSDK%'
+%_VECHO% TclKitExe = '%TCLKIT_EXE%'
+%_VECHO% TclKitLib = '%TCLKIT_LIB%'
+%_VECHO% TclKitLibStub = '%TCLKIT_LIB_STUB%'
+%_VECHO% TclKitSdk = '%TCLKIT_SDK%'
+%_VECHO% TclKitSdkZip = '%TCLKIT_SDK_ZIP%'
+%_VECHO% TclKitFiles = '%TCLKIT_FILES%'
+
+CALL :fn_ResetErrorLevel
+
+FOR %%T IN (csc.exe) DO (
+ SET %%T_PATH=%%~dp$PATH:T
+)
+
+%_VECHO% Csc.exe_PATH = '%csc.exe_PATH%'
+
+IF DEFINED csc.exe_PATH (
+ GOTO skip_addToPath
+)
+
+IF DEFINED FRAMEWORKDIR (
+ REM Use the existing .NET Framework directory...
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework64\v2.0.50727" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework64\v2.0.50727
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework64\v3.5" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework64\v3.5
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework64\v4.0.30319" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework64\v4.0.30319
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework\v2.0.50727" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework\v2.0.50727
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework\v3.5" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework\v3.5
+) ELSE IF EXIST "%windir%\Microsoft.NET\Framework\v4.0.30319" (
+ SET FRAMEWORKDIR=%windir%\Microsoft.NET\Framework\v4.0.30319
+) ELSE (
+ ECHO No suitable version of the .NET Framework appears to be installed.
+ GOTO errors
+)
+
+%_VECHO% FrameworkDir = '%FRAMEWORKDIR%'
+
+IF NOT EXIST "%FRAMEWORKDIR%\csc.exe" (
+ ECHO The file "%FRAMEWORKDIR%\csc.exe" is missing.
+ GOTO errors
+)
+
+CALL :fn_PrependToPath FRAMEWORKDIR
+
+:skip_addToPath
+
+IF NOT EXIST "%TEMP%\GetFile.exe" (
+ %__ECHO% csc.exe "/out:%TEMP%\GetFile.exe" /target:exe "%TOOLS%\GetFile.cs"
+
+ IF ERRORLEVEL 1 (
+ ECHO Compilation of "%TOOLS%\GetFile.cs" failed.
+ GOTO errors
+ )
+)
+
+FOR %%F IN (%TCLKIT_FILES%) DO (
+ IF NOT EXIST "%TEMP%\%%F" (
+ %__ECHO% "%TEMP%\GetFile.exe" "%TCLKIT_URI%%%F"
+
+ IF ERRORLEVEL 1 (
+ ECHO Download of "%%F" from "%TCLKIT_URI%" failed.
+ GOTO errors
+ )
+ )
+)
+
+IF DEFINED TCLKIT_NOENV GOTO skip_sdkUnZip
+IF DEFINED TCLKIT_NOSDK GOTO skip_sdkUnZip
+
+IF NOT EXIST "%TEMP%\%TCLKIT_SDK%" (
+ %__ECHO% MKDIR "%TEMP%\%TCLKIT_SDK%"
+
+ IF ERRORLEVEL 1 (
+ ECHO Could not create directory "%TEMP%\%TCLKIT_SDK%".
+ GOTO errors
+ )
+)
+
+%__ECHO% "%TEMP%\unzip.exe" -n "%TEMP%\%TCLKIT_SDK_ZIP%" -d "%TEMP%\%TCLKIT_SDK%"
+
+IF ERRORLEVEL 1 (
+ ECHO Could not unzip "%TEMP%\%TCLKIT_SDK_ZIP%" to "%TEMP%\%TCLKIT_SDK%".
+ GOTO errors
+)
+
+:skip_sdkUnZip
+
+IF DEFINED TCLKIT_NOENV GOTO skip_sdkEnvironment
+
+%__ECHO% ECHO SET TCLSH_CMD=%TEMP%\%TCLKIT_EXE%%OVERWRITE%"%ENVDIR%\SetTclKitEnv.bat"
+
+IF DEFINED TCLKIT_NOSDK GOTO skip_sdkVariables
+
+%__ECHO% ECHO SET TCLINCDIR=%TEMP%\%TCLKIT_SDK%\include%APPEND%"%ENVDIR%\SetTclKitEnv.bat"
+%__ECHO% ECHO SET TCLLIBDIR=%TEMP%\%TCLKIT_SDK%\lib%APPEND%"%ENVDIR%\SetTclKitEnv.bat"
+%__ECHO% ECHO SET LIBTCLPATH=%TEMP%\%TCLKIT_SDK%\lib%APPEND%"%ENVDIR%\SetTclKitEnv.bat"
+%__ECHO% ECHO SET LIBTCL=%TCLKIT_LIB%%APPEND%"%ENVDIR%\SetTclKitEnv.bat"
+%__ECHO% ECHO SET LIBTCLSTUB=%TCLKIT_LIB_STUB%%APPEND%"%ENVDIR%\SetTclKitEnv.bat"
+
+:skip_sdkVariables
+
+ECHO.
+ECHO Wrote "%ENVDIR%\SetTclKitEnv.bat".
+ECHO Please run it to set the necessary Tcl environment variables.
+ECHO.
+
+:skip_sdkEnvironment
+
+GOTO no_errors
+
+:fn_TclKitX86Variables
+ REM
+ REM NOTE: By default, use latest available version of the TclKit SDK
+ REM for x86. However, the "default" TclKit executable for x86
+ REM is still used here because it is the only one "well-known"
+ REM to be available for download.
+ REM
+ IF NOT DEFINED TCLKIT_PATCHLEVEL (
+ ECHO The TCLKIT_PATCHLEVEL environment variable must be set first.
+ CALL :fn_SetErrorLevel
+ GOTO :EOF
+ )
+ SET TCLKIT_VERSION=%TCLKIT_PATCHLEVEL:.=%
+ SET TCLKIT_VERSION=%TCLKIT_VERSION:~0,2%
+ IF DEFINED TCLKIT_EXE_PATCHLEVEL (
+ SET TCLKIT_EXE=tclkit-%TCLKIT_EXE_PATCHLEVEL%.exe
+ ) ELSE (
+ SET TCLKIT_EXE=tclkit-%TCLKIT_PATCHLEVEL%.exe
+ )
+ SET TCLKIT_LIB=libtclkit%TCLKIT_PATCHLEVEL:.=%.lib
+ SET TCLKIT_LIB_STUB=libtclstub%TCLKIT_VERSION:.=%.a
+ SET TCLKIT_SDK=libtclkit-sdk-x86-%TCLKIT_PATCHLEVEL%
+ SET TCLKIT_SDK_ZIP=%TCLKIT_SDK%.zip
+ SET TCLKIT_FILES=%TCLKIT_EXE%
+ IF NOT DEFINED TCLKIT_NOENV IF NOT DEFINED TCLKIT_NOSDK (
+ SET TCLKIT_FILES=%TCLKIT_FILES% unzip.exe %TCLKIT_SDK_ZIP%
+ )
+ GOTO :EOF
+
+:fn_TclKitX64Variables
+ REM
+ REM NOTE: By default, use latest available version of the TclKit SDK
+ REM for x64. However, the "default" TclKit executable for x86
+ REM is still used here because it is the only one "well-known"
+ REM to be available for download.
+ REM
+ IF NOT DEFINED TCLKIT_PATCHLEVEL (
+ ECHO The TCLKIT_PATCHLEVEL environment variable must be set first.
+ CALL :fn_SetErrorLevel
+ GOTO :EOF
+ )
+ SET TCLKIT_VERSION=%TCLKIT_PATCHLEVEL:.=%
+ SET TCLKIT_VERSION=%TCLKIT_VERSION:~0,2%
+ IF DEFINED TCLKIT_EXE_PATCHLEVEL (
+ SET TCLKIT_EXE=tclkit-%TCLKIT_EXE_PATCHLEVEL%.exe
+ ) ELSE (
+ SET TCLKIT_EXE=tclkit-%TCLKIT_PATCHLEVEL%.exe
+ )
+ SET TCLKIT_LIB=libtclkit%TCLKIT_PATCHLEVEL:.=%.lib
+ SET TCLKIT_LIB_STUB=libtclstub%TCLKIT_VERSION:.=%.a
+ SET TCLKIT_SDK=libtclkit-sdk-x64-%TCLKIT_PATCHLEVEL%
+ SET TCLKIT_SDK_ZIP=%TCLKIT_SDK%.zip
+ SET TCLKIT_FILES=%TCLKIT_EXE%
+ IF NOT DEFINED TCLKIT_NOENV IF NOT DEFINED TCLKIT_NOSDK (
+ SET TCLKIT_FILES=%TCLKIT_FILES% unzip.exe %TCLKIT_SDK_ZIP%
+ )
+ GOTO :EOF
+
+:fn_UnquoteVariable
+ IF NOT DEFINED %1 GOTO :EOF
+ SETLOCAL
+ SET __ECHO_CMD=ECHO %%%1%%
+ FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
+ SET VALUE=%%V
+ )
+ SET VALUE=%VALUE:"=%
+ REM "
+ ENDLOCAL && SET %1=%VALUE%
+ GOTO :EOF
+
+:fn_PrependToPath
+ IF NOT DEFINED %1 GOTO :EOF
+ SETLOCAL
+ SET __ECHO_CMD=ECHO %%%1%%
+ FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
+ SET VALUE=%%V
+ )
+ SET VALUE=%VALUE:"=%
+ REM "
+ ENDLOCAL && SET PATH=%VALUE%;%PATH%
+ GOTO :EOF
+
+:fn_ResetErrorLevel
+ VERIFY > NUL
+ GOTO :EOF
+
+:fn_SetErrorLevel
+ VERIFY MAYBE 2> NUL
+ GOTO :EOF
+
+:usage
+ ECHO.
+ ECHO Usage: %~nx0 ^<processor^>
+ ECHO.
+ ECHO The only supported values for processor are "x86" and "x64".
+ GOTO errors
+
+:errors
+ CALL :fn_SetErrorLevel
+ ENDLOCAL
+ ECHO.
+ ECHO Failure, errors were encountered.
+ GOTO end_of_file
+
+:no_errors
+ CALL :fn_ResetErrorLevel
+ ENDLOCAL
+ ECHO.
+ ECHO Success, no errors were encountered.
+ GOTO end_of_file
+
+:end_of_file
+%__ECHO% EXIT /B %ERRORLEVEL%
diff --git a/tool/Replace.cs b/tool/Replace.cs
new file mode 100644
index 0000000..3475a47
--- /dev/null
+++ b/tool/Replace.cs
@@ -0,0 +1,223 @@
+/*
+** 2016 February 26
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains C# code to perform regular expression replacements
+** using the standard input and output channels.
+*/
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+///////////////////////////////////////////////////////////////////////////////
+
+#region Assembly Metadata
+[assembly: AssemblyTitle("Replace Tool")]
+[assembly: AssemblyDescription("Replace text using standard input/output.")]
+[assembly: AssemblyCompany("SQLite Development Team")]
+[assembly: AssemblyProduct("SQLite")]
+[assembly: AssemblyCopyright("Public Domain")]
+[assembly: ComVisible(false)]
+[assembly: Guid("95a0513f-8863-48cd-a76f-cb80868cb578")]
+[assembly: AssemblyVersion("1.0.*")]
+
+#if DEBUG
+[assembly: AssemblyConfiguration("Debug")]
+#else
+[assembly: AssemblyConfiguration("Release")]
+#endif
+#endregion
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace Replace
+{
+ /// <summary>
+ /// This enumeration is used to represent all the possible exit codes from
+ /// this tool.
+ /// </summary>
+ internal enum ExitCode
+ {
+ /// <summary>
+ /// The file download was a success.
+ /// </summary>
+ Success = 0,
+
+ /// <summary>
+ /// The command line arguments are missing (i.e. null). Generally,
+ /// this should not happen.
+ /// </summary>
+ MissingArgs = 1,
+
+ /// <summary>
+ /// The wrong number of command line arguments was supplied.
+ /// </summary>
+ WrongNumArgs = 2,
+
+ /// <summary>
+ /// The "matchingOnly" flag could not be converted to a value of the
+ /// <see cref="Boolean"/> type.
+ /// </summary>
+ BadMatchingOnlyFlag = 3,
+
+ /// <summary>
+ /// An exception was caught in <see cref="Main" />. Generally, this
+ /// should not happen.
+ /// </summary>
+ Exception = 4
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ internal static class Replace
+ {
+ #region Private Support Methods
+ /// <summary>
+ /// This method displays an error message to the console and/or
+ /// displays the command line usage information for this tool.
+ /// </summary>
+ /// <param name="message">
+ /// The error message to display, if any.
+ /// </param>
+ /// <param name="usage">
+ /// Non-zero to display the command line usage information.
+ /// </param>
+ private static void Error(
+ string message,
+ bool usage
+ )
+ {
+ if (message != null)
+ Console.WriteLine(message);
+
+ string fileName = Path.GetFileName(
+ Process.GetCurrentProcess().MainModule.FileName);
+
+ Console.WriteLine(String.Format(
+ "usage: {0} <regExPattern> <regExSubSpec> <matchingOnly>",
+ fileName));
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////
+
+ #region Program Entry Point
+ /// <summary>
+ /// This is the entry-point for this tool. It handles processing the
+ /// command line arguments, reading from the standard input channel,
+ /// replacing any matching lines of text, and writing to the standard
+ /// output channel.
+ /// </summary>
+ /// <param name="args">
+ /// The command line arguments.
+ /// </param>
+ /// <returns>
+ /// Zero upon success; non-zero on failure. This will be one of the
+ /// values from the <see cref="ExitCode" /> enumeration.
+ /// </returns>
+ private static int Main(
+ string[] args
+ )
+ {
+ //
+ // NOTE: Sanity check the command line arguments.
+ //
+ if (args == null)
+ {
+ Error(null, true);
+ return (int)ExitCode.MissingArgs;
+ }
+
+ if (args.Length != 3)
+ {
+ Error(null, true);
+ return (int)ExitCode.WrongNumArgs;
+ }
+
+ try
+ {
+ //
+ // NOTE: Create a regular expression from the first command
+ // line argument. Then, grab the replacement string,
+ // which is the second argument.
+ //
+ Regex regEx = new Regex(args[0]);
+ string replacement = args[1];
+
+ //
+ // NOTE: Attempt to convert the third argument to a boolean.
+ //
+ bool matchingOnly;
+
+ if (!bool.TryParse(args[2], out matchingOnly))
+ {
+ Error(null, true);
+ return (int)ExitCode.BadMatchingOnlyFlag;
+ }
+
+ //
+ // NOTE: Grab the standard input and output channels from the
+ // console.
+ //
+ TextReader inputTextReader = Console.In;
+ TextWriter outputTextWriter = Console.Out;
+
+ //
+ // NOTE: Loop until end-of-file is hit on the standard input
+ // stream.
+ //
+ while (true)
+ {
+ //
+ // NOTE: Read a line from the standard input channel. If
+ // null is returned here, there is no more input and
+ // we are done.
+ //
+ string inputLine = inputTextReader.ReadLine();
+
+ if (inputLine == null)
+ break;
+
+ //
+ // NOTE: Perform regular expression replacements on this
+ // line, if any. Then, write the modified line to
+ // the standard output channel.
+ //
+ string outputLine = regEx.Replace(inputLine, replacement);
+
+ if (!matchingOnly || !String.Equals(
+ inputLine, outputLine, StringComparison.Ordinal))
+ {
+ outputTextWriter.WriteLine(outputLine);
+ }
+ }
+
+ //
+ // NOTE: At this point, everything has succeeded.
+ //
+ return (int)ExitCode.Success;
+ }
+ catch (Exception e)
+ {
+ //
+ // NOTE: An exception was caught. Report it via the console
+ // and return failure.
+ //
+ Error(e.ToString(), false);
+ return (int)ExitCode.Exception;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/tool/build-all-msvc.bat b/tool/build-all-msvc.bat
new file mode 100755
index 0000000..8f9a1b7
--- /dev/null
+++ b/tool/build-all-msvc.bat
@@ -0,0 +1,864 @@
+@ECHO OFF
+
+::
+:: build-all-msvc.bat --
+::
+:: Multi-Platform Build Tool for MSVC
+::
+
+REM
+REM This batch script is used to build the SQLite DLL for multiple platforms
+REM and configurations using MSVC. The built SQLite DLLs, their associated
+REM import libraries, and optionally their symbols files, are placed within
+REM the directory specified on the command line, in sub-directories named for
+REM their respective platforms and configurations. This batch script must be
+REM run from inside a Visual Studio Command Prompt for the desired version of
+REM Visual Studio ^(the initial platform configured for the command prompt does
+REM not really matter^). Exactly one command line argument is required, the
+REM name of an existing directory to be used as the final destination directory
+REM for the generated output files, which will be placed in sub-directories
+REM created therein. Ideally, the directory specified should be empty.
+REM
+REM Example:
+REM
+REM CD /D C:\dev\sqlite\core
+REM CALL tool\build-all-msvc.bat C:\Temp
+REM
+REM In the example above, "C:\dev\sqlite\core" represents the root of the
+REM source tree for SQLite and "C:\Temp" represents the final destination
+REM directory for the generated output files.
+REM
+REM Please note that the SQLite build process performed by the Makefile
+REM associated with this batch script requires a Tcl shell to be present
+REM in a directory contained in the PATH environment variable unless a
+REM pre-existing amalgamation file is used.
+REM
+REM There are several environment variables that may be set to modify the
+REM behavior of this batch script and its associated Makefile. The list of
+REM platforms to build may be overriden by using the PLATFORMS environment
+REM variable, which should contain a list of platforms ^(e.g. x86 x86_amd64
+REM x86_arm^). All platforms must be supported by the version of Visual Studio
+REM being used. The list of configurations to build may be overridden by
+REM setting the CONFIGURATIONS environment variable, which should contain a
+REM list of configurations to build ^(e.g. Debug Retail^). Neither of these
+REM variable values may contain any double quotes, surrounding or embedded.
+REM
+REM Finally, the NCRTLIBPATH, NUCRTLIBPATH, and NSDKLIBPATH environment
+REM variables may be set to specify the location of the CRT, Universal CRT, and
+REM Windows SDK, respectively, that may be needed to compile executables native
+REM to the architecture of the build machine during any cross-compilation that
+REM may be necessary, depending on the platforms to be built. These values in
+REM these three variables should be surrounded by double quotes if they contain
+REM spaces.
+REM
+REM There are a few other environment variables that impact the build process
+REM when set ^(to anything^), they are:
+REM
+REM USE_AUTOCONF_MAKEFILE
+REM
+REM When set, the "autoconf" Makefile for MSVC will be used instead of the main
+REM Makefile for MSVC. It must exist at "%ROOT%\autoconf\Makefile.msc".
+REM
+REM NOCLEAN
+REM
+REM When set, the "clean" target will not be used during each build iteration.
+REM However, the target binaries, if any, will still be deleted manually prior
+REM to being rebuilt. Setting this environment variable is only rarely needed
+REM and could cause issues in some circumstances; therefore, setting it is not
+REM recommended.
+REM
+REM NOSYMBOLS
+REM
+REM When set, copying of symbol files ^(*.pdb^) created during the build will
+REM be skipped and they will not appear in the final destination directory.
+REM Setting this environment variable is never strictly needed and could cause
+REM issues in some circumstances; therefore, setting it is not recommended.
+REM
+REM NOMEMDEBUG
+REM
+REM When set, disables use of MEMDEBUG when building binaries for the "Debug"
+REM configuration.
+REM
+REM BUILD_ALL_SHELL
+REM
+REM When set, the command line shell will be built for each selected platform
+REM and configuration as well. In addition, the command line shell binaries
+REM will be copied, with their symbols, to the final destination directory.
+REM
+REM USE_WINV63_NSDKLIBPATH
+REM
+REM When set, modifies how the NSDKLIBPATH environment variable is built, based
+REM on the WindowsSdkDir environment variable. It forces this batch script to
+REM assume the Windows 8.1 SDK location should be used.
+REM
+REM USE_WINV100_NSDKLIBPATH
+REM
+REM When set, modifies how the NSDKLIBPATH environment variable is built, based
+REM on the WindowsSdkDir environment variable. It causes this batch script to
+REM assume the Windows 10.0 SDK location should be used.
+REM
+REM NMAKE_ARGS
+REM NMAKE_ARGS_DEBUG
+REM NMAKE_ARGS_RETAIL
+REM
+REM When set, these values are expanded and passed to the NMAKE command line,
+REM after its other arguments. These may be used to specify additional NMAKE
+REM options, for example:
+REM
+REM SET NMAKE_ARGS=FOR_WINRT=1
+REM SET NMAKE_ARGS_DEBUG=MEMDEBUG=1
+REM SET NMAKE_ARGS_RETAIL=WIN32HEAP=1
+REM
+REM Using the above command before running this tool will cause the compiled
+REM binaries to target the WinRT environment, which provides a subset of the
+REM Win32 API.
+REM
+REM DLL_FILE_NAME
+REM DLL_PDB_FILE_NAME
+REM LIB_FILE_NAME
+REM EXE_FILE_NAME
+REM EXE_PDB_FILE_NAME
+REM
+REM When set, these values will override the associated target file name used
+REM for the build.
+REM
+SETLOCAL
+
+REM SET __ECHO=ECHO
+REM SET __ECHO2=ECHO
+REM SET __ECHO3=ECHO
+IF NOT DEFINED _AECHO (SET _AECHO=REM)
+IF NOT DEFINED _CECHO (SET _CECHO=REM)
+IF NOT DEFINED _CECHO2 (SET _CECHO2=REM)
+IF NOT DEFINED _CECHO3 (SET _CECHO3=REM)
+IF NOT DEFINED _VECHO (SET _VECHO=REM)
+
+SET REDIRECT=^>
+IF DEFINED __ECHO SET REDIRECT=^^^>
+
+%_AECHO% Running %0 %*
+
+REM SET DFLAGS=/L
+
+%_VECHO% DFlags = '%DFLAGS%'
+
+SET FFLAGS=/V /F /G /H /I /R /Y /Z
+
+%_VECHO% FFlags = '%FFLAGS%'
+
+SET ROOT=%~dp0\..
+SET ROOT=%ROOT:\\=\%
+
+%_VECHO% Root = '%ROOT%'
+
+REM
+REM NOTE: The first and only argument to this batch file should be the output
+REM directory where the platform-specific binary directories should be
+REM created.
+REM
+SET BINARYDIRECTORY=%1
+
+IF NOT DEFINED BINARYDIRECTORY (
+ GOTO usage
+)
+
+%_VECHO% BinaryDirectory = '%BINARYDIRECTORY%'
+
+SET DUMMY=%2
+
+IF DEFINED DUMMY (
+ GOTO usage
+)
+
+REM
+REM NOTE: From this point, we need a clean error level. Reset it now.
+REM
+CALL :fn_ResetErrorLevel
+
+REM
+REM NOTE: Change the current directory to the root of the source tree, saving
+REM the current directory on the directory stack.
+REM
+%_CECHO2% PUSHD "%ROOT%"
+%__ECHO2% PUSHD "%ROOT%"
+
+IF ERRORLEVEL 1 (
+ ECHO Could not change directory to "%ROOT%".
+ GOTO errors
+)
+
+REM
+REM NOTE: This batch file requires the ComSpec environment variable to be set,
+REM typically to something like "C:\Windows\System32\cmd.exe".
+REM
+IF NOT DEFINED ComSpec (
+ ECHO The ComSpec environment variable must be defined.
+ GOTO errors
+)
+
+REM
+REM NOTE: This batch file requires the VcInstallDir environment variable to be
+REM set. Tyipcally, this means this batch file needs to be run from an
+REM MSVC command prompt.
+REM
+IF NOT DEFINED VCINSTALLDIR (
+ ECHO The VCINSTALLDIR environment variable must be defined.
+ GOTO errors
+)
+
+REM
+REM NOTE: If the list of platforms is not already set, use the default list.
+REM
+IF NOT DEFINED PLATFORMS (
+ SET PLATFORMS=x86 x86_amd64 x86_arm
+)
+
+%_VECHO% Platforms = '%PLATFORMS%'
+
+REM
+REM NOTE: If the list of configurations is not already set, use the default
+REM list.
+REM
+IF NOT DEFINED CONFIGURATIONS (
+ SET CONFIGURATIONS=Debug Retail
+)
+
+%_VECHO% Configurations = '%CONFIGURATIONS%'
+
+REM
+REM NOTE: If the command used to invoke NMAKE is not already set, use the
+REM default.
+REM
+IF NOT DEFINED NMAKE_CMD (
+ IF DEFINED USE_AUTOCONF_MAKEFILE (
+ SET NMAKE_CMD=nmake -B -f autoconf\Makefile.msc
+ ) ELSE (
+ SET NMAKE_CMD=nmake -B -f Makefile.msc
+ )
+)
+
+%_VECHO% NmakeCmd = '%NMAKE_CMD%'
+%_VECHO% NmakeArgs = '%NMAKE_ARGS%'
+%_VECHO% NmakeArgsDebug = '%NMAKE_ARGS_DEBUG%'
+%_VECHO% NmakeArgsRetail = '%NMAKE_ARGS_RETAIL%'
+
+REM
+REM NOTE: Setup environment variables to translate between the MSVC platform
+REM names and the names to be used for the platform-specific binary
+REM directories.
+REM
+SET amd64_NAME=x64
+SET arm_NAME=ARM
+SET x64_NAME=x64
+SET x86_NAME=x86
+SET x86_amd64_NAME=x64
+SET x86_arm_NAME=ARM
+SET x86_x64_NAME=x64
+
+%_VECHO% amd64_Name = '%amd64_NAME%'
+%_VECHO% arm_Name = '%arm_NAME%'
+%_VECHO% x64_Name = '%x64_NAME%'
+%_VECHO% x86_Name = '%x86_NAME%'
+%_VECHO% x86_amd64_Name = '%x86_amd64_NAME%'
+%_VECHO% x86_arm_Name = '%x86_arm_NAME%'
+%_VECHO% x86_x64_Name = '%x86_x64_NAME%'
+
+REM
+REM NOTE: Check for the external tools needed during the build process ^(i.e.
+REM those that do not get compiled as part of the build process itself^)
+REM along the PATH.
+REM
+IF DEFINED TCLSH_CMD (
+ SET TCLSH_FILE=%TCLSH_CMD%
+) ELSE (
+ SET TCLSH_FILE=tclsh.exe
+)
+
+FOR %%T IN (%TCLSH_FILE%) DO (
+ SET %%T_PATH=%%~dp$PATH:T
+)
+
+REM
+REM NOTE: A Tcl shell executable is required during the SQLite build process
+REM unless a pre-existing amalgamation file is used.
+REM
+IF NOT DEFINED %TCLSH_FILE%_PATH (
+ ECHO The Tcl shell executable "%TCLSH_FILE%" is required to be in the PATH.
+ GOTO errors
+)
+
+REM
+REM NOTE: Setup the default names for the build targets we are creating. Any
+REM ^(or all^) of these may end up being overridden.
+REM
+IF NOT DEFINED DLL_FILE_NAME (
+ SET DLL_FILE_NAME=sqlite3.dll
+)
+
+IF NOT DEFINED DLL_PDB_FILE_NAME (
+ SET DLL_PDB_FILE_NAME=sqlite3.pdb
+)
+
+IF NOT DEFINED LIB_FILE_NAME (
+ SET LIB_FILE_NAME=sqlite3.lib
+)
+
+IF NOT DEFINED EXE_FILE_NAME (
+ SET EXE_FILE_NAME=sqlite3.exe
+)
+
+IF NOT DEFINED EXE_PDB_FILE_NAME (
+ SET EXE_PDB_FILE_NAME=sqlite3sh.pdb
+)
+
+REM
+REM NOTE: Set the TOOLPATH variable to contain all the directories where the
+REM external tools were found in the search above.
+REM
+CALL :fn_CopyVariable %TCLSH_FILE%_PATH TOOLPATH
+
+%_VECHO% ToolPath = '%TOOLPATH%'
+
+REM
+REM NOTE: Setting the Windows SDK library path is only required for MSVC
+REM 2012, 2013, and 2015.
+REM
+CALL :fn_UnsetVariable SET_NSDKLIBPATH
+
+REM
+REM NOTE: Setting the Universal CRT library path is only required for MSVC
+REM 2015.
+REM
+CALL :fn_UnsetVariable SET_NUCRTLIBPATH
+
+REM
+REM NOTE: Check for MSVC 2012, 2013, and 2015 specially because the Windows
+REM SDK directory handling is slightly different for those versions.
+REM
+IF "%VisualStudioVersion%" == "11.0" (
+ REM
+ REM NOTE: If the Windows SDK library path has already been set, do not set
+ REM it to something else later on.
+ REM
+ IF NOT DEFINED NSDKLIBPATH (
+ SET SET_NSDKLIBPATH=1
+ )
+) ELSE IF "%VisualStudioVersion%" == "12.0" (
+ REM
+ REM NOTE: If the Windows SDK library path has already been set, do not set
+ REM it to something else later on.
+ REM
+ IF NOT DEFINED NSDKLIBPATH (
+ SET SET_NSDKLIBPATH=1
+ )
+) ELSE IF "%VisualStudioVersion%" == "14.0" (
+ REM
+ REM NOTE: If the Windows SDK library path has already been set, do not set
+ REM it to something else later on.
+ REM
+ IF NOT DEFINED NSDKLIBPATH (
+ SET SET_NSDKLIBPATH=1
+ )
+
+ REM
+ REM NOTE: If the Universal CRT library path has already been set, do not set
+ REM it to something else later on.
+ REM
+ IF NOT DEFINED NUCRTLIBPATH (
+ SET SET_NUCRTLIBPATH=1
+ )
+)
+
+REM
+REM NOTE: This is the name of the sub-directory where the UCRT libraries may
+REM be found. It is only used when compiling against the UCRT.
+REM
+IF DEFINED UCRTVersion (
+ SET NUCRTVER=%UCRTVersion%
+) ELSE (
+ SET NUCRTVER=10.0.10586.0
+)
+
+REM
+REM NOTE: This is the name of the sub-directory where the Windows 10.0 SDK
+REM libraries may be found. It is only used when compiling with the
+REM Windows 10.0 SDK.
+REM
+IF DEFINED WindowsSDKLibVersion (
+ SET WIN10SDKVER=%WindowsSDKLibVersion:\=%
+) ELSE (
+ SET WIN10SDKVER=%NUCRTVER%
+)
+
+REM
+REM NOTE: Check if this is the Windows Phone SDK. If so, a different batch
+REM file is necessary to setup the build environment. Since the variable
+REM values involved here may contain parenthesis, using GOTO instead of
+REM an IF block is required.
+REM
+IF DEFINED WindowsPhoneKitDir GOTO set_vcvarsall_phone
+SET VCVARSALL=%VCINSTALLDIR%\vcvarsall.bat
+GOTO set_vcvarsall_done
+:set_vcvarsall_phone
+SET VCVARSALL=%VCINSTALLDIR%\WPSDK\WP80\vcvarsphoneall.bat
+:set_vcvarsall_done
+SET VCVARSALL=%VCVARSALL:\\=\%
+
+REM
+REM NOTE: This is the outer loop. There should be exactly one iteration per
+REM platform.
+REM
+FOR %%P IN (%PLATFORMS%) DO (
+ REM
+ REM NOTE: Using the MSVC platform name, lookup the simpler platform name to
+ REM be used for the name of the platform-specific binary directory via
+ REM the environment variables setup earlier.
+ REM
+ CALL :fn_CopyVariable %%P_NAME PLATFORMNAME
+
+ REM
+ REM NOTE: This is the second loop. There should be exactly one iteration.
+ REM This loop is necessary because the PlatformName environment
+ REM variable was set above and that value is needed by some of the
+ REM commands contained in the inner loop. If these commands were
+ REM directly contained in the outer loop, the PlatformName environment
+ REM variable would be stuck with its initial empty value instead.
+ REM
+ FOR /F "tokens=2* delims==" %%D IN ('SET PLATFORMNAME') DO (
+ REM
+ REM NOTE: Attempt to clean the environment of all variables used by MSVC
+ REM and/or Visual Studio. This block may need to be updated in the
+ REM future to account for additional environment variables.
+ REM
+ CALL :fn_UnsetVariable CommandPromptType
+ CALL :fn_UnsetVariable DevEnvDir
+ CALL :fn_UnsetVariable DNX_HOME
+ CALL :fn_UnsetVariable ExtensionSdkDir
+ CALL :fn_UnsetVariable Framework35Version
+ CALL :fn_UnsetVariable Framework40Version
+ CALL :fn_UnsetVariable FrameworkDir
+ CALL :fn_UnsetVariable FrameworkDir32
+ CALL :fn_UnsetVariable FrameworkVersion
+ CALL :fn_UnsetVariable FrameworkVersion32
+ CALL :fn_UnsetVariable FSHARPINSTALLDIR
+ CALL :fn_UnsetVariable INCLUDE
+ CALL :fn_UnsetVariable LIB
+ CALL :fn_UnsetVariable LIBPATH
+ CALL :fn_UnsetVariable NETFXSDKDir
+ CALL :fn_UnsetVariable Platform
+ CALL :fn_UnsetVariable UCRTVersion
+ CALL :fn_UnsetVariable UniversalCRTSdkDir
+ REM CALL :fn_UnsetVariable VCINSTALLDIR
+ CALL :fn_UnsetVariable VSINSTALLDIR
+ CALL :fn_UnsetVariable WindowsLibPath
+ CALL :fn_UnsetVariable WindowsPhoneKitDir
+ CALL :fn_UnsetVariable WindowsSdkDir
+ CALL :fn_UnsetVariable WindowsSdkDir_35
+ CALL :fn_UnsetVariable WindowsSdkDir_old
+ CALL :fn_UnsetVariable WindowsSDKLibVersion
+ CALL :fn_UnsetVariable WindowsSDKVersion
+ CALL :fn_UnsetVariable WindowsSDK_ExecutablePath_x86
+ CALL :fn_UnsetVariable WindowsSDK_ExecutablePath_x64
+
+ REM
+ REM NOTE: Reset the PATH here to the absolute bare minimum required.
+ REM
+ CALL :fn_ResetPath
+
+ REM
+ REM NOTE: This is the inner loop. There are normally two iterations, one
+ REM for each supported build configuration, e.g. Debug or Retail.
+ REM
+ FOR %%B IN (%CONFIGURATIONS%) DO (
+ REM
+ REM NOTE: When preparing the debug build, set the DEBUG and MEMDEBUG
+ REM environment variables to be picked up by the MSVC makefile
+ REM itself.
+ REM
+ %_AECHO% Building the %%B configuration for platform %%P with name %%D...
+
+ IF /I "%%B" == "Debug" (
+ REM
+ REM NOTE: Using this level for the DEBUG environment variable should
+ REM disable all compiler optimizations and prevent use of the
+ REM NDEBUG define. Additionally, both SQLITE_ENABLE_API_ARMOR
+ REM and SQLITE_DEBUG defines should be enabled.
+ REM
+ SET DEBUG=3
+
+ REM
+ REM NOTE: Setting this to non-zero should enable the SQLITE_MEMDEBUG
+ REM define.
+ REM
+ IF NOT DEFINED NOMEMDEBUG (
+ SET MEMDEBUG=1
+ )
+ ) ELSE (
+ CALL :fn_UnsetVariable DEBUG
+ CALL :fn_UnsetVariable MEMDEBUG
+ )
+
+ REM
+ REM NOTE: Copy the extra NMAKE arguments for this configuration into the
+ REM common variable used by the actual commands.
+ REM
+ CALL :fn_CopyVariable NMAKE_ARGS_%%B NMAKE_ARGS_CFG
+
+ REM
+ REM NOTE: Launch a nested command shell to perform the following steps:
+ REM
+ REM 1. Setup the MSVC environment for this platform using the
+ REM official batch file.
+ REM
+ REM 2. Make sure that no stale build output files are present.
+ REM
+ REM 3. Build the "sqlite3.dll" and "sqlite3.lib" binaries for this
+ REM platform.
+ REM
+ REM 4. Copy the "sqlite3.dll" and "sqlite3.lib" binaries for this
+ REM platform to the platform-specific directory beneath the
+ REM binary directory.
+ REM
+ REM 5. Unless prevented from doing so, copy the "sqlite3.pdb"
+ REM symbols file for this platform to the platform-specific
+ REM directory beneath the binary directory.
+ REM
+ "%ComSpec%" /C (
+ REM
+ REM NOTE: Attempt to setup the MSVC environment for this platform.
+ REM
+ %_CECHO3% CALL "%VCVARSALL%" %%P
+ %__ECHO3% CALL "%VCVARSALL%" %%P
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to call "%VCVARSALL%" for platform %%P.
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: If this batch file is not running in "what-if" mode, check to
+ REM be sure we were actually able to setup the MSVC environment
+ REM as current versions of their official batch file do not set
+ REM the exit code upon failure.
+ REM
+ IF NOT DEFINED __ECHO3 (
+ IF NOT DEFINED WindowsPhoneKitDir (
+ IF NOT DEFINED WindowsSdkDir (
+ ECHO Cannot build, Windows SDK not found for platform %%P.
+ GOTO errors
+ )
+ )
+ )
+
+ REM
+ REM NOTE: When using MSVC 2012, 2013, or 2015, the native SDK path
+ REM cannot simply be the "lib" sub-directory beneath the location
+ REM specified in the WindowsSdkDir environment variable because
+ REM that location does not actually contain the necessary library
+ REM files for x86. This must be done for each iteration because
+ REM it relies upon the WindowsSdkDir environment variable being
+ REM set by the batch file used to setup the MSVC environment.
+ REM
+ IF DEFINED SET_NSDKLIBPATH (
+ REM
+ REM NOTE: The Windows Phone SDK has a slightly different directory
+ REM structure and must be handled specially here.
+ REM
+ IF DEFINED WindowsPhoneKitDir (
+ CALL :fn_CopyVariable WindowsPhoneKitDir NSDKLIBPATH
+ CALL :fn_AppendVariable NSDKLIBPATH \lib\x86
+ ) ELSE IF DEFINED WindowsSdkDir (
+ CALL :fn_CopyVariable WindowsSdkDir NSDKLIBPATH
+
+ REM
+ REM NOTE: The Windows 8.x and Windows 10.0 SDKs have a slightly
+ REM different directory naming conventions.
+ REM
+ IF DEFINED USE_WINV100_NSDKLIBPATH (
+ CALL :fn_AppendVariable NSDKLIBPATH \..\10\lib\%WIN10SDKVER%\um\x86
+ CALL :fn_CopyVariable WindowsSdkDir PSDKLIBPATH
+ CALL :fn_AppendVariable PSDKLIBPATH lib\%WIN10SDKVER%\um\%%D
+ ) ELSE IF DEFINED USE_WINV63_NSDKLIBPATH (
+ CALL :fn_AppendVariable NSDKLIBPATH \lib\winv6.3\um\x86
+ ) ELSE IF "%VisualStudioVersion%" == "12.0" (
+ CALL :fn_AppendVariable NSDKLIBPATH \..\8.0\lib\win8\um\x86
+ ) ELSE IF "%VisualStudioVersion%" == "14.0" (
+ CALL :fn_AppendVariable NSDKLIBPATH \..\8.0\lib\win8\um\x86
+ ) ELSE (
+ CALL :fn_AppendVariable NSDKLIBPATH \lib\win8\um\x86
+ )
+ )
+ )
+
+ REM
+ REM NOTE: When using MSVC 2015, setting the Universal CRT library path
+ REM for x86 may be required as well. This must also be done for
+ REM each iteration because it relies upon the UniversalCRTSdkDir
+ REM environment variable being set by the batch file used to
+ REM setup the MSVC environment.
+ REM
+ IF DEFINED SET_NUCRTLIBPATH (
+ IF DEFINED UniversalCRTSdkDir (
+ CALL :fn_CopyVariable UniversalCRTSdkDir NUCRTLIBPATH
+ CALL :fn_AppendVariable NUCRTLIBPATH \lib\%NUCRTVER%\ucrt\x86
+ )
+ )
+
+ REM
+ REM NOTE: Unless prevented from doing so, invoke NMAKE with the MSVC
+ REM makefile to clean any stale build output from previous
+ REM iterations of this loop and/or previous runs of this batch
+ REM file, etc.
+ REM
+ IF NOT DEFINED NOCLEAN (
+ CALL :fn_MakeClean %%D
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to clean for platform %%P.
+ GOTO errors
+ )
+ ) ELSE (
+ REM
+ REM NOTE: Even when the cleaning step has been disabled, we still
+ REM need to remove the build output for all the files we are
+ REM specifically wanting to build for each platform.
+ REM
+ %_AECHO% Cleaning final core library output files only...
+ %__ECHO% DEL /Q *.lo "%DLL_FILE_NAME%" "%LIB_FILE_NAME%" "%DLL_PDB_FILE_NAME%" 2%REDIRECT% NUL
+ )
+
+ REM
+ REM NOTE: Call NMAKE with the MSVC makefile to build the "sqlite3.dll"
+ REM binary. The x86 compiler will be used to compile the native
+ REM command line tools needed during the build process itself.
+ REM Also, disable looking for and/or linking to the native Tcl
+ REM runtime library.
+ REM
+ CALL :fn_MakeDll %%D
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to build %%B "%DLL_FILE_NAME%" for platform %%P.
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: Copy the "sqlite3.dll" file to the appropriate directory for
+ REM the build and platform beneath the binary directory.
+ REM
+ %__ECHO% XCOPY "%DLL_FILE_NAME%" "%BINARYDIRECTORY%\%%B\%%D\" %FFLAGS% %DFLAGS%
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to copy "%DLL_FILE_NAME%" to "%BINARYDIRECTORY%\%%B\%%D\".
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: Copy the "sqlite3.lib" file to the appropriate directory for
+ REM the build and platform beneath the binary directory.
+ REM
+ %__ECHO% XCOPY "%LIB_FILE_NAME%" "%BINARYDIRECTORY%\%%B\%%D\" %FFLAGS% %DFLAGS%
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to copy "%LIB_FILE_NAME%" to "%BINARYDIRECTORY%\%%B\%%D\".
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: Copy the "sqlite3.pdb" file to the appropriate directory for
+ REM the build and platform beneath the binary directory unless we
+ REM are prevented from doing so.
+ REM
+ IF NOT DEFINED NOSYMBOLS (
+ IF EXIST "%DLL_PDB_FILE_NAME%" (
+ %__ECHO% XCOPY "%DLL_PDB_FILE_NAME%" "%BINARYDIRECTORY%\%%B\%%D\" %FFLAGS% %DFLAGS%
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to copy "%DLL_PDB_FILE_NAME%" to "%BINARYDIRECTORY%\%%B\%%D\".
+ GOTO errors
+ )
+ )
+ )
+
+ REM
+ REM NOTE: If requested, also build the shell executable.
+ REM
+ IF DEFINED BUILD_ALL_SHELL (
+ REM
+ REM NOTE: If necessary, make sure any previous build output for the
+ REM shell executable is deleted.
+ REM
+ IF DEFINED NOCLEAN (
+ REM
+ REM NOTE: Even when the cleaning step has been disabled, we still
+ REM need to remove the build output for all the files we are
+ REM specifically wanting to build for each platform.
+ REM
+ %_AECHO% Cleaning final shell executable output files only...
+ %__ECHO% DEL /Q "%EXE_FILE_NAME%" "%EXE_PDB_FILE_NAME%" 2%REDIRECT% NUL
+ )
+
+ REM
+ REM NOTE: Call NMAKE with the MSVC makefile to build the "sqlite3.exe"
+ REM binary. The x86 compiler will be used to compile the native
+ REM command line tools needed during the build process itself.
+ REM Also, disable looking for and/or linking to the native Tcl
+ REM runtime library.
+ REM
+ CALL :fn_MakeExe %%D
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to build %%B "%EXE_FILE_NAME%" for platform %%P.
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: Copy the "sqlite3.exe" file to the appropriate directory
+ REM for the build and platform beneath the binary directory.
+ REM
+ %__ECHO% XCOPY "%EXE_FILE_NAME%" "%BINARYDIRECTORY%\%%B\%%D\" %FFLAGS% %DFLAGS%
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to copy "%EXE_FILE_NAME%" to "%BINARYDIRECTORY%\%%B\%%D\".
+ GOTO errors
+ )
+
+ REM
+ REM NOTE: Copy the "sqlite3sh.pdb" file to the appropriate directory
+ REM for the build and platform beneath the binary directory
+ REM unless we are prevented from doing so.
+ REM
+ IF NOT DEFINED NOSYMBOLS (
+ IF EXIST "%EXE_PDB_FILE_NAME%" (
+ %__ECHO% XCOPY "%EXE_PDB_FILE_NAME%" "%BINARYDIRECTORY%\%%B\%%D\" %FFLAGS% %DFLAGS%
+
+ IF ERRORLEVEL 1 (
+ ECHO Failed to copy "%EXE_PDB_FILE_NAME%" to "%BINARYDIRECTORY%\%%B\%%D\".
+ GOTO errors
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+
+ REM
+ REM NOTE: Handle any errors generated during the nested command shell.
+ REM
+ IF ERRORLEVEL 1 (
+ GOTO errors
+ )
+)
+
+REM
+REM NOTE: Restore the saved current directory from the directory stack.
+REM
+%_CECHO2% POPD
+%__ECHO2% POPD
+
+IF ERRORLEVEL 1 (
+ ECHO Could not restore directory.
+ GOTO errors
+)
+
+REM
+REM NOTE: If we get to this point, we have succeeded.
+REM
+GOTO no_errors
+
+:fn_MakeClean
+ %__ECHO% %NMAKE_CMD% clean "PLATFORM=%1" XCOMPILE=1 USE_NATIVE_LIBPATHS=1 NO_TCL=1 %NMAKE_ARGS% %NMAKE_ARGS_CFG%
+ GOTO :EOF
+
+:fn_MakeDll
+ %__ECHO% %NMAKE_CMD% "%DLL_FILE_NAME%" "PLATFORM=%1" XCOMPILE=1 USE_NATIVE_LIBPATHS=1 NO_TCL=1 %NMAKE_ARGS% %NMAKE_ARGS_CFG%
+ GOTO :EOF
+
+:fn_MakeExe
+ %__ECHO% %NMAKE_CMD% "%EXE_FILE_NAME%" "PLATFORM=%1" XCOMPILE=1 USE_NATIVE_LIBPATHS=1 NO_TCL=1 %NMAKE_ARGS% %NMAKE_ARGS_CFG%
+ GOTO :EOF
+
+:fn_ShowVariable
+ SETLOCAL
+ SET __ECHO_CMD=ECHO %%%2%%
+ FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
+ IF NOT "%%V" == "" (
+ IF NOT "%%V" == "%%%2%%" (
+ %_VECHO% %1 = '%%V'
+ )
+ )
+ )
+ ENDLOCAL
+ GOTO :EOF
+
+:fn_ResetErrorLevel
+ VERIFY > NUL
+ GOTO :EOF
+
+:fn_SetErrorLevel
+ VERIFY MAYBE 2> NUL
+ GOTO :EOF
+
+:fn_CopyVariable
+ IF NOT DEFINED %1 GOTO :EOF
+ IF "%2" == "" GOTO :EOF
+ SETLOCAL
+ SET __ECHO_CMD=ECHO %%%1%%
+ FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
+ SET VALUE=%%V
+ )
+ ENDLOCAL && SET %2=%VALUE%
+ GOTO :EOF
+
+:fn_UnsetVariable
+ SETLOCAL
+ SET VALUE=%1
+ IF DEFINED VALUE (
+ SET VALUE=
+ ENDLOCAL
+ SET %VALUE%=
+ ) ELSE (
+ ENDLOCAL
+ )
+ CALL :fn_ResetErrorLevel
+ GOTO :EOF
+
+:fn_ResetPath
+ SET PATH=%TOOLPATH%;%SystemRoot%\System32;%SystemRoot%
+ GOTO :EOF
+
+:fn_AppendVariable
+ SET __ECHO_CMD=ECHO %%%1%%
+ IF DEFINED %1 (
+ FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
+ SET %1=%%V%~2
+ )
+ ) ELSE (
+ SET %1=%~2
+ )
+ SET __ECHO_CMD=
+ CALL :fn_ResetErrorLevel
+ GOTO :EOF
+
+:usage
+ ECHO.
+ ECHO Usage: %~nx0 ^<binaryDirectory^>
+ ECHO.
+ GOTO errors
+
+:errors
+ CALL :fn_SetErrorLevel
+ ENDLOCAL
+ ECHO.
+ ECHO Failure, errors were encountered.
+ GOTO end_of_file
+
+:no_errors
+ CALL :fn_ResetErrorLevel
+ ENDLOCAL
+ ECHO.
+ ECHO Success, no errors were encountered.
+ GOTO end_of_file
+
+:end_of_file
+%__ECHO% EXIT /B %ERRORLEVEL%
diff --git a/tool/build-shell.sh b/tool/build-shell.sh
new file mode 100644
index 0000000..a57f753
--- /dev/null
+++ b/tool/build-shell.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# This script demonstrates how to do a full-featured build of the sqlite3
+# command-line shell on Linux.
+#
+# SQLite source code should be in a sibling directory named "sqlite". For
+# example, put SQLite sources in ~/sqlite/sqlite and run this script from
+# ~/sqlite/bld. There should be an appropriate Makefile in the current
+# directory as well.
+#
+make sqlite3.c
+gcc -o sqlite3 -g -Os -I. \
+ -DSQLITE_THREADSAFE=0 \
+ -DSQLITE_ENABLE_VFSTRACE \
+ -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_FTS4 \
+ -DSQLITE_ENABLE_RTREE \
+ -DHAVE_READLINE \
+ ../sqlite/src/shell.c \
+ ../sqlite/src/test_vfstrace.c \
+ sqlite3.c -ldl -lreadline -lncurses
diff --git a/tool/cg_anno.tcl b/tool/cg_anno.tcl
new file mode 100755
index 0000000..50a3ca8
--- /dev/null
+++ b/tool/cg_anno.tcl
@@ -0,0 +1,79 @@
+#!/bin/sh
+# \
+exec tclsh "$0" ${1+"$@"}
+#
+# A wrapper around cg_annotate that sets appropriate command-line options
+# and rearranges the output so that annotated files occur in a consistent
+# sorted order. Used by the speed-check.tcl script.
+#
+
+set in [open "|cg_annotate --show=Ir --auto=yes --context=40 $argv" r]
+set dest !
+set out(!) {}
+set linenum 0
+set cntlines 0 ;# true to remember cycle counts on each line
+set seenSqlite3 0 ;# true if we have seen the sqlite3.c file
+while {![eof $in]} {
+ set line [string map {\t { }} [gets $in]]
+ if {[regexp {^-- Auto-annotated source: (.*)} $line all name]} {
+ set dest $name
+ if {[string match */sqlite3.c $dest]} {
+ set cntlines 1
+ set seenSqlite3 1
+ } else {
+ set cntlines 0
+ }
+ } elseif {[regexp {^-- line (\d+) ------} $line all ln]} {
+ set line [lreplace $line 2 2 {#}]
+ set linenum [expr {$ln-1}]
+ } elseif {[regexp {^The following files chosen for } $line]} {
+ set dest !
+ }
+ append out($dest) $line\n
+ if {$cntlines} {
+ incr linenum
+ if {[regexp {^ *([0-9,]+) } $line all x]} {
+ set x [string map {, {}} $x]
+ set cycles($linenum) $x
+ }
+ }
+}
+foreach x [lsort [array names out]] {
+ puts $out($x)
+}
+
+# If the sqlite3.c file has been seen, then output a summary of the
+# cycle counts for each file that went into making up sqlite3.c
+#
+if {$seenSqlite3} {
+ close $in
+ set in [open sqlite3.c]
+ set linenum 0
+ set fn sqlite3.c
+ set pattern1 {^/\*+ Begin file ([^ ]+) \*}
+ set pattern2 {^/\*+ Continuing where we left off in ([^ ]+) \*}
+ while {![eof $in]} {
+ set line [gets $in]
+ incr linenum
+ if {[regexp $pattern1 $line all newfn]} {
+ set fn $newfn
+ } elseif {[regexp $pattern2 $line all newfn]} {
+ set fn $newfn
+ } elseif {[info exists cycles($linenum)]} {
+ incr fcycles($fn) $cycles($linenum)
+ }
+ }
+ close $in
+ puts {**********************************************************************}
+ set lx {}
+ set sum 0
+ foreach {fn cnt} [array get fcycles] {
+ lappend lx [list $cnt $fn]
+ incr sum $cnt
+ }
+ puts [format {%20s %14d %8.3f%%} TOTAL $sum 100]
+ foreach entry [lsort -index 0 -integer -decreasing $lx] {
+ foreach {cnt fn} $entry break
+ puts [format {%20s %14d %8.3f%%} $fn $cnt [expr {$cnt*100.0/$sum}]]
+ }
+}
diff --git a/tool/checkSpacing.c b/tool/checkSpacing.c
new file mode 100644
index 0000000..ce38b08
--- /dev/null
+++ b/tool/checkSpacing.c
@@ -0,0 +1,84 @@
+/*
+** This program checks for formatting problems in source code:
+**
+** * Any use of tab characters
+** * White space at the end of a line
+** * Blank lines at the end of a file
+**
+** Any violations are reported.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CR_OK 0x001
+#define WSEOL_OK 0x002
+
+static void checkSpacing(const char *zFile, unsigned flags){
+ FILE *in = fopen(zFile, "rb");
+ int i;
+ int seenSpace;
+ int seenTab;
+ int ln = 0;
+ int lastNonspace = 0;
+ char zLine[2000];
+ if( in==0 ){
+ printf("cannot open %s\n", zFile);
+ return;
+ }
+ while( fgets(zLine, sizeof(zLine), in) ){
+ seenSpace = 0;
+ seenTab = 0;
+ ln++;
+ for(i=0; zLine[i]; i++){
+ if( zLine[i]=='\t' && seenTab==0 ){
+ printf("%s:%d: tab (\\t) character\n", zFile, ln);
+ seenTab = 1;
+ }else if( zLine[i]=='\r' ){
+ if( (flags & CR_OK)==0 ){
+ printf("%s:%d: carriage-return (\\r) character\n", zFile, ln);
+ }
+ }else if( zLine[i]==' ' ){
+ seenSpace = 1;
+ }else if( zLine[i]!='\n' ){
+ lastNonspace = ln;
+ seenSpace = 0;
+ }
+ }
+ if( seenSpace && (flags & WSEOL_OK)==0 ){
+ printf("%s:%d: whitespace at end-of-line\n", zFile, ln);
+ }
+ }
+ fclose(in);
+ if( lastNonspace<ln ){
+ printf("%s:%d: blank lines at end of file (%d)\n",
+ zFile, ln, ln - lastNonspace);
+ }
+}
+
+int main(int argc, char **argv){
+ int i;
+ unsigned flags = WSEOL_OK;
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ while( z[0]=='-' ) z++;
+ if( strcmp(z,"crok")==0 ){
+ flags |= CR_OK;
+ }else if( strcmp(z, "wseol")==0 ){
+ flags &= ~WSEOL_OK;
+ }else if( strcmp(z, "help")==0 ){
+ printf("Usage: %s [options] FILE ...\n", argv[0]);
+ printf(" --crok Do not report on carriage-returns\n");
+ printf(" --wseol Complain about whitespace at end-of-line\n");
+ printf(" --help This message\n");
+ }else{
+ printf("unknown command-line option: [%s]\n", argv[i]);
+ printf("use --help for additional information\n");
+ }
+ }else{
+ checkSpacing(argv[i], flags);
+ }
+ }
+ return 0;
+}
diff --git a/tool/cktclsh.sh b/tool/cktclsh.sh
new file mode 100644
index 0000000..1928a40
--- /dev/null
+++ b/tool/cktclsh.sh
@@ -0,0 +1,11 @@
+# Fail with an error if the TCLSH named in $2 is not tclsh version $1 or later.
+#
+echo "set vers $1" >cktclsh$1.tcl
+echo 'if {$tcl_version<$vers} {exit 1}' >>cktclsh$1.tcl
+if ! $2 cktclsh$1.tcl
+then
+ echo "ERROR: This makefile target requires tclsh $1 or later."
+ rm cktclsh$1.tcl
+ exit 1
+fi
+rm cktclsh$1.tcl
diff --git a/tool/custom.txt b/tool/custom.txt
new file mode 100644
index 0000000..b9c8a98
--- /dev/null
+++ b/tool/custom.txt
@@ -0,0 +1,1246 @@
+aa
+aaa
+abc
+abcdefg
+abd
+abf
+Abortable
+acc
+accessor
+accum
+acd
+activecpu
+Adaptor
+Additionallly
+addop
+addoptrace
+addr
+adjustements
+af
+aff
+afp
+afterward
+Agg
+agg
+agginfo
+alikes
+Alloc
+alloc
+alloca
+allocator
+allocators
+alphabetics
+alphanumerics
+alternateform
+altertab
+altform
+amalgamator
+amongst
+analyse
+antipenultimate
+ap
+api
+appdef
+appendall
+appendchar
+appendf
+ar
+arg
+argc
+argcount
+arglist
+argn
+args
+argv
+arrayname
+ascii
+asm
+async
+atoi
+atomics
+auth
+authorizer
+autocheckpoint
+autocommit
+autoconf
+autoext
+autoextension
+autoinc
+autoincrement
+autoincremented
+autoindex
+autoinstall
+autovac
+autovacuum
+autovacuumed
+autovacuuming
+auxdata
+awk
+Ax
+backend
+backends
+backfill
+backfilled
+backfilling
+backtrace
+backtraces
+backtracing
+bb
+bba
+bcb
+bcc
+beginthreadex
+behavior
+behavioral
+behaviors
+benigncnt
+bg
+bigblob
+bitcount
+bitfield
+bitmask
+bitmasks
+bitset
+bitvec
+bitvecs
+bitwise
+blobwrite
+blockquote
+Bloomfilter
+bom
+boolean
+booleans
+Borland
+br
+breadthfirstsearch
+breakpoint
+bt
+btree
+btrees
+buf
+bufpt
+butoindex
+bytearray
+bytecode
+bytecodevtab
+byteorder
+cacheflush
+cachegrind
+Cachesize
+calc'ing
+callgrind
+cardinalities
+cardinality
+carray
+cb
+cd
+cdbaa
+ce
+ceil
+cellpadding
+cellspacing
+center
+chacha
+changecounter
+changeset
+characgter
+characterset
+checkpointed
+Checkpointer
+checkpointer
+checkpointers
+checkpointing
+checksummed
+checksums
+chmod
+chng
+chown
+chroot
+chsize
+chunksize
+cid
+cis
+ckalloc'd
+ckpt
+cksum
+cksumvfs
+clientdata
+closedir
+clownshoe
+cmd
+cmp
+cmpaff
+Cmpr
+cnt
+codec
+codepage
+collseq
+colname
+compileoption
+concat
+config
+confstr
+connetion
+consective
+considertion
+const
+coredump
+coroutine
+coroutines
+cov
+crashparams
+csr
+csv
+Cte
+ctime
+Ctrl
+ctrl
+ctx
+ctype
+cume
+Cx
+cx
+Cygwin
+cygwin
+dan
+darkstar
+databasename
+databse
+datasource
+datatypes
+datetime
+dbfuzz
+dbinfo
+dbname
+dbpage
+dbpagevfs
+dbs
+dbsize
+dbsqlfuzz
+dbstat
+dbtotxt
+De
+de
+deadman
+deallocate
+deallocated
+deallocates
+deallocating
+deallocation
+decltype
+decrementing
+defense
+defenses
+defn
+defragment
+defragmentation
+defragmented
+deinitialization
+Deinitialize
+deinitialize
+demovfs
+dependences
+dequote
+dequoted
+dequoting
+dereferenced
+dereferences
+desc
+deserialization
+deserialize
+deserialized
+deserializes
+deserializing
+dest
+destructor
+destructors
+deterministically
+dev
+devsim
+devsym
+df
+Dfdatasync
+dflt
+dir
+directonly
+dirent
+dirs
+disjunct
+disjunction
+disjuncts
+diskfull
+divy
+dl
+dll
+dlopen
+dlsym
+docid
+docids
+dont
+dontcache
+dotfile
+dotlock
+doublearray
+drh
+dryrun
+dstr
+dt
+Duint
+dup
+Durian
+dword
+Dx
+dylib
+Dyn
+Ec
+ec
+ecel
+editability
+ef
+efc
+eg
+Ei
+elif
+emcc
+emscripten
+encodings
+endeavor
+endfor
+endian
+endianness
+endif
+endthreadex
+enum
+eof
+eph
+Ephem
+eq
+eqp
+equaling
+equalities
+errcode
+errmsg
+errno
+errorcode
+erroroffset
+errstr
+esc
+esign
+et
+etfs
+etilqs
+eval
+exe
+expander
+explainer
+expmask
+expr
+exprlist
+extern
+fakeheap
+fallocate
+fanout
+faultsim
+favor
+favors
+fb
+fc
+fchmod
+fchown
+fclose
+fcntl
+fd
+fdatasync
+feb
+fef
+Feijoa
+ferror
+ffff
+ffffeff
+ffffffe
+fffffff
+ffffffff
+fffffffffffffff
+fflush
+fg
+fgets
+fi
+fibonacci
+fid
+Fifo
+filecount
+filectrl
+filemapping
+filesize
+filesystem
+filesystems
+finalised
+finalizer
+findfirst
+findnext
+fixup
+fk
+fkctr
+fkey
+flattener
+fmt
+fopen
+foreach
+formatter
+fprintf
+fputs
+fread
+fred
+fred's
+freeblock
+freeblocks
+freelist
+freepage
+freespace
+frombind
+fs
+fsanitize
+fsctl
+fsdir
+fseek
+fstat
+fstree
+fsync
+fsynced
+fsyncs
+ftell
+ftruncate
+fts
+fullfsync
+fullname
+fullschema
+fullshm
+fullsync
+fullsyncs
+func
+funcs
+fuzzer
+fuzzers
+fwrite
+Fx
+gcc
+gcov
+gdb
+getcwd
+getenv
+gethostuuid
+getpagesize
+getpid
+getrusage
+getsubtype
+getter
+getters
+gibabyte
+gid
+glibc
+globbing
+gmtime
+Gosub
+Goto
+goto
+groupbyparent
+Groupid
+growable
+grp
+hdl
+hdr
+hexdb
+hexdouble
+hexio
+highwater
+hijklmno
+honor
+honored
+honoring
+hostid
+href
+html
+htsize
+hw
+Hwtime
+icecube
+ideographical
+idx
+idxaff
+idxnum
+idxstr
+idx'th
+ieee
+ifdef
+iff
+ifndef
+imm
+impl
+incr
+incrblob
+incrementing
+indexable
+indexname
+infop
+ing
+init
+initfail
+initializer
+initializers
+initiallly
+inlined
+inlines
+inlining
+ino
+inode
+inodes
+inopertune
+installable
+intarray
+inteface
+interoperate
+interp
+interpretable
+intkey
+intptr
+intreal
+intrinsics
+invalidations
+invariants
+io
+ioerr
+iotrace
+ipk
+iplan
+isalnum
+isalpha
+isatty
+isdigit
+isempty
+isexplain
+ismemdb
+isnan
+isspace
+isxdigit
+i'th
+iversion
+jfd
+jj
+jointype
+jointypes
+journaled
+journaling
+journalled
+journalling
+journalmode
+jrnl
+jsl
+json
+jt
+julian
+julianday
+keyinfo
+keywordhash
+kibibytes
+kvstorage
+kvvfs
+lappend
+lasterrno
+lbl
+ldl
+le
+leafdata
+leftjustify
+len
+leveling
+lexeme
+lexemes
+lhs
+li
+libm
+libversion
+lifecycle
+lindex
+lineno
+Linenoise
+linux
+lised
+lld
+llvm
+lm
+ln
+lncurses
+loadext
+localhost
+localtime
+lockd
+lockdata
+lockingcontext
+lockless
+lockproxy
+locktype
+logmsg
+longvalue
+longwords
+lookahead
+Lookaside
+lookaside
+lookups
+losslessly
+lpthread
+lrc
+lreadline
+lru
+lseek
+lt
+lvalue
+lwr
+makefile
+makefiles
+malloc
+malloc'd
+malloced
+mallocs
+manditory
+manpage
+matchinfo
+materializations
+mathfuncs
+maxsize
+mbcs
+Mcafee
+md
+Meeus
+mem
+memcmp
+memcpy
+memdb
+memdebug
+memget
+memmove
+mempool
+memset
+memstatus
+memsys
+memvfs
+mergeable
+mergepatch
+middleware
+millisec
+mincore
+mingw
+mis
+miscoded
+mj
+mkctimec
+mkdir
+mkkeywordhash
+mkopcodec
+mkopcodeh
+mkpragmatab
+mmap
+mmapped
+mno
+modeof
+Movepage
+mprintf
+msd
+msdos
+msec
+msg
+msgs
+msize
+msvc
+mtime
+mult
+multibyte
+multiplex'ed
+multiplexor
+multithreaded
+multiwrite
+mutex
+mutexes
+mutexing
+mx
+mxpathname
+myprefix
+nal
+namecontext
+namespace
+natively
+nbr
+nbsp
+ncell
+ncol
+ncycle
+nd
+ndlt
+neighbors
+neq
+nestable
+newrowid
+nfs
+nlt
+nnn
+nocase
+nochange
+nochng
+nofollow
+nolock
+nomem
+nomutex
+nonblocking
+nonroot
+noop
+noshm
+notational
+notheld
+notnull
+nowrap
+nr
+ntile
+nul
+nullable
+nullif
+nullvalue
+Num
+objproc
+oc
+offsetof
+ofst
+ogham
+oid
+Ok
+ok
+ol
+onecolumn
+onepass
+onoff
+onwards
+oom
+opcodesnever
+openable
+opendir
+optimizers
+optimizible
+orconf
+orderby
+orderbylist
+os
+Oswrite
+overread
+overreads
+overrideable
+oversize
+overwriteable
+ovewrite
+ovfl
+pagecache
+pagecaches
+pagecount
+pagelist
+pageno
+pagesize
+pagetype
+Param
+params
+passwd
+patchset
+pathname
+pathnames
+pc
+pcache
+pclose
+pcx
+pgno
+pgoffset
+pgsize
+pgsz
+pid
+pluggable
+pmasz
+pn
+popen
+pos
+posix
+Postgres
+Powersafe
+powersafe
+pq
+pqr
+pqrstuvw
+pragma
+pragmaed
+pragma's
+pragmas
+pre
+pread
+preallocate
+preallocated
+precisions
+precompiled
+precomputed
+prefetch
+prefetched
+preformated
+preformatted
+prepend
+prepended
+prepending
+prepends
+prepopulate
+preprocess
+preprocessed
+preprocessing
+preprocessor
+prereq
+prereqs
+preupdate
+primarykey
+printf
+printfs
+prng
+proc
+procs
+profiler
+proleptic
+proxying
+proxys
+pseudocode
+pseudotable
+psow
+psz
+pthread
+pthreads
+ptr
+ptrmap
+ptrs
+purgeable
+putsnl
+pwrite
+pz
+qbox
+Qcircle
+quotaed
+quotefix
+radix
+randomblob
+randstr
+rarr
+rc
+rcauth
+Rcvr
+rds
+readdir
+readline
+readlock
+readonly
+Readr
+realloc
+reallocs
+realvalue
+rebalance
+rebalanced
+recoverability
+redefinable
+redux
+reenabled
+reentrant
+refcount
+refcounts
+regs
+reindexed
+reinitializer
+reinitializes
+rekey
+rekeyed
+relevancies
+relink
+relink
+relock
+renormalize
+reoptimize
+reparse
+reparse
+reparsed
+reparsing
+reportable
+reprepare
+reprepare
+reprepared
+reprepares
+representable
+repurpose
+Req'd
+requeries
+requote
+reregister
+reseek
+reservebytes
+resumable
+retarget
+retargeted
+retrys
+returntype
+rfc
+rhs
+Ri
+Rivest
+ro
+rootpage
+rowid
+rowids
+Rowkey
+rownumber
+rowset
+runtime
+rw
+rwc
+samplelib
+sampleno
+sandboxed
+sandboxing
+savepoint
+savepoints
+scanf
+scanstats
+scanstatus
+schemas
+sectorsize
+selecttrace
+selftest
+setrlimit
+setsubtype
+settitle
+sharable
+shm
+shmlock
+sibs
+sig
+signaling
+significand
+sizehint
+Sizeof
+sizeof
+snprintf
+Solaris
+sorterref
+soundex
+sourceid
+speedtest
+sprintf
+sql
+sqlar
+sqlite
+sqliteplocks
+sqliterc
+sqllog
+sqllogglobal
+sqlthread
+sqr
+sqrt
+src
+srcck
+statfs
+stderr
+stdin
+stdout
+stmt
+stmts
+str
+strace
+strcasecmp
+strcmp
+strdup
+strerror
+strftime
+strglob
+stricmp
+stringify
+strlen
+strlike
+strncmp
+strncpy
+strnicmp
+stronly
+strstr
+struct
+structs
+subbitmap
+subcases
+subclassed
+subclauses
+subcomponents
+subdirectory
+subelement
+subexpression
+subexpressions
+subfunction
+subfunctions
+subitem
+subjournals
+sublevels
+subnode
+suboptimal
+subpages
+subprocedures
+subprog
+subprogs
+subq
+subqueries
+subquery
+Subrtn
+subsec
+subsecond
+subsequence
+substr
+substring
+substrings
+subsubstructure
+subsubterms
+Subtask
+subtasks
+subterm
+subterms
+subtransaction
+subtransactions
+subtree
+subtrees
+subtype
+subtypes
+sumint
+superclass
+superlock
+superset
+superunlock
+symlinks
+synching
+sys
+syscall
+sz
+szosfile
+tablename
+tailval
+tailvar
+tbl
+tblname
+Tcl
+tcl
+Tclcmd
+tclcmd
+tclsh
+tclsqlite
+tclvar
+td
+tempfilename
+testcase
+testctrl
+testfixture
+testtag
+testvfs
+textarray
+th
+threadid
+threadsafe
+threadsafety
+throughs
+tht
+timediff
+tkt
+tm
+tmp
+tmpdir
+tmpfs
+tnum
+Todo
+tokenize
+tokenizer
+tokenizing
+tolower
+toobig
+toupper
+treetrace
+treeview
+trimleft
+trimright
+truesize
+trys
+Tsd
+Ts'o
+tunable
+tvfs
+txn
+txt
+Typecheck
+typedef
+typedefed
+typedefs
+typename
+typenames
+typeof
+tz
+uber
+uid
+uint
+ul
+umask
+Un
+un
+unallocated
+unanalyzed
+unary
+unbuffered
+unclosed
+uncompiled
+uncomputed
+undefining
+underfull
+unexpanded
+unfinalized
+unfreed
+unhex
+unicode
+unindexed
+uninit
+uninitialize
+unintuitive
+unioned
+unissued
+unix
+unixepoch
+unlink
+unlinked
+unlinking
+unlinks
+unmap
+unmapped
+unmapping
+unoptimized
+unparsed
+unreduced
+unref
+unreferenced
+unrefs
+unregister
+unregistering
+unregisters
+unresolvable
+unsynced
+unterminated
+untracked
+untrusted
+Upfrom
+uppercasing
+upr
+Upsert
+upsert
+upto
+uptr
+uri
+userauth
+userdata
+Userid
+usleep
+utc
+Utf
+utf
+util
+uuu
+uuuuu
+uuzzzz
+va
+valgrind
+vanishingly
+vappendf
+vararg
+varargs
+varint
+varints
+varname
+vcolumn
+vdbe
+vdbeapi
+vdbe's
+vdbes
+vdbesort
+ve
+verifications
+vfs
+vfslog
+vfsname
+vfs's
+vfstrace
+vm
+vmprintf
+vmstep
+vsnprintf
+vt
+vtab
+vtabs
+Vugt
+vvv
+vvvv
+vvvvv
+vvvvvv
+vwait
+vxworks
+wal
+wasm
+wherecode
+whereexpr
+wheretrace
+whitespace
+Willmann
+withoutrowid
+wr
+wrapup
+writeable
+writecrash
+writefile
+wsd
+ww
+wwww
+wwzzzzyy
+wxyz
+xa
+xac
+xb
+xbf
+xc
+xd
+xdg
+xe
+xf
+xfe
+xfer
+xff
+xfff
+xfffd
+xfffe
+xffffffff
+x'hhhhhh
+xinfo
+xlc
+xtype
+xxxx
+xxxxx
+xxxxxx
+xxxxxxx
+xxxxxxxx
+xyz
+xyzzy
+yy
+yyxxxxxx
+yyy
+yyyyy
+yyyyyy
+zeroblob
+Zeroblobs
+zerodata
+zeropad
+zipfile
+zipvfs
+zplan
+zulu
+zzzz
+zzzzyyyy
diff --git a/tool/dbhash.c b/tool/dbhash.c
new file mode 100644
index 0000000..78685dc
--- /dev/null
+++ b/tool/dbhash.c
@@ -0,0 +1,491 @@
+/*
+** 2016-06-07
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This is a utility program that computes an SHA1 hash on the content
+** of an SQLite database.
+**
+** The hash is computed over just the content of the database. Free
+** space inside of the database file, and alternative on-disk representations
+** of the same content (ex: UTF8 vs UTF16) do not affect the hash. So,
+** for example, the database file page size, encoding, and auto_vacuum setting
+** can all be changed without changing the hash.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#include "sqlite3.h"
+
+/* Context for the SHA1 hash */
+typedef struct SHA1Context SHA1Context;
+struct SHA1Context {
+ unsigned int state[5];
+ unsigned int count[2];
+ unsigned char buffer[64];
+};
+
+/*
+** All global variables are gathered into the "g" singleton.
+*/
+struct GlobalVars {
+ const char *zArgv0; /* Name of program */
+ unsigned fDebug; /* Debug flags */
+ sqlite3 *db; /* The database connection */
+ SHA1Context cx; /* SHA1 hash context */
+} g;
+
+/*
+** Debugging flags
+*/
+#define DEBUG_FULLTRACE 0x00000001 /* Trace hash to stderr */
+
+/******************************************************************************
+** The Hash Engine
+**
+** Modify these routines (and appropriate state fields in global variable 'g')
+** in order to compute a different (better?) hash of the database.
+*/
+/*
+ * blk0() and blk() perform the initial expand.
+ * I got the idea of expanding during the round function from SSLeay
+ *
+ * blk0le() for little-endian and blk0be() for big-endian.
+ */
+#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r))
+#define rol(x,k) SHA_ROT(x,k,32-(k))
+#define ror(x,k) SHA_ROT(x,32-(k),k)
+
+#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \
+ |(rol(block[i],8)&0x00FF00FF))
+#define blk0be(i) block[i]
+#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \
+ ^block[(i+2)&15]^block[i&15],1))
+
+/*
+ * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
+ *
+ * Rl0() for little-endian and Rb0() for big-endian. Endianness is
+ * determined at run-time.
+ */
+#define Rl0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define Rb0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R1(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R2(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2);
+#define R3(v,w,x,y,z,i) \
+ z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2);
+#define R4(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2);
+
+/*
+ * Hash a single 512-bit block. This is the core of the algorithm.
+ */
+#define a qq[0]
+#define b qq[1]
+#define c qq[2]
+#define d qq[3]
+#define e qq[4]
+
+void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]){
+ unsigned int qq[5]; /* a, b, c, d, e; */
+ static int one = 1;
+ unsigned int block[16];
+ memcpy(block, buffer, 64);
+ memcpy(qq,state,5*sizeof(unsigned int));
+
+ /* Copy g.cx.state[] to working vars */
+ /*
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ */
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ if( 1 == *(unsigned char*)&one ){
+ Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3);
+ Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7);
+ Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11);
+ Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15);
+ }else{
+ Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3);
+ Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7);
+ Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11);
+ Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15);
+ }
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+}
+
+
+/* Initialize the SHA1 hash */
+static void hash_init(void){
+ /* SHA1 initialization constants */
+ g.cx.state[0] = 0x67452301;
+ g.cx.state[1] = 0xEFCDAB89;
+ g.cx.state[2] = 0x98BADCFE;
+ g.cx.state[3] = 0x10325476;
+ g.cx.state[4] = 0xC3D2E1F0;
+ g.cx.count[0] = g.cx.count[1] = 0;
+}
+
+/* Add new content to the SHA1 hash */
+static void hash_step(const unsigned char *data, unsigned int len){
+ unsigned int i, j;
+
+ j = g.cx.count[0];
+ if( (g.cx.count[0] += len << 3) < j ){
+ g.cx.count[1] += (len>>29)+1;
+ }
+ j = (j >> 3) & 63;
+ if( (j + len) > 63 ){
+ (void)memcpy(&g.cx.buffer[j], data, (i = 64-j));
+ SHA1Transform(g.cx.state, g.cx.buffer);
+ for(; i + 63 < len; i += 64){
+ SHA1Transform(g.cx.state, &data[i]);
+ }
+ j = 0;
+ }else{
+ i = 0;
+ }
+ (void)memcpy(&g.cx.buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and compute and output the message digest. */
+static void hash_finish(const char *zName){
+ unsigned int i;
+ unsigned char finalcount[8];
+ unsigned char digest[20];
+ static const char zEncode[] = "0123456789abcdef";
+ char zOut[41];
+
+ for (i = 0; i < 8; i++){
+ finalcount[i] = (unsigned char)((g.cx.count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ hash_step((const unsigned char *)"\200", 1);
+ while ((g.cx.count[0] & 504) != 448){
+ hash_step((const unsigned char *)"\0", 1);
+ }
+ hash_step(finalcount, 8); /* Should cause a SHA1Transform() */
+ for (i = 0; i < 20; i++){
+ digest[i] = (unsigned char)((g.cx.state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+ for(i=0; i<20; i++){
+ zOut[i*2] = zEncode[(digest[i]>>4)&0xf];
+ zOut[i*2+1] = zEncode[digest[i] & 0xf];
+ }
+ zOut[i*2]= 0;
+ printf("%s %s\n", zOut, zName);
+}
+/* End of the hashing logic
+*******************************************************************************/
+
+/*
+** Print an error resulting from faulting command-line arguments and
+** abort the program.
+*/
+static void cmdlineError(const char *zFormat, ...){
+ va_list ap;
+ fprintf(stderr, "%s: ", g.zArgv0);
+ va_start(ap, zFormat);
+ vfprintf(stderr, zFormat, ap);
+ va_end(ap);
+ fprintf(stderr, "\n\"%s --help\" for more help\n", g.zArgv0);
+ exit(1);
+}
+
+/*
+** Print an error message for an error that occurs at runtime, then
+** abort the program.
+*/
+static void runtimeError(const char *zFormat, ...){
+ va_list ap;
+ fprintf(stderr, "%s: ", g.zArgv0);
+ va_start(ap, zFormat);
+ vfprintf(stderr, zFormat, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+/*
+** Prepare a new SQL statement. Print an error and abort if anything
+** goes wrong.
+*/
+static sqlite3_stmt *db_vprepare(const char *zFormat, va_list ap){
+ char *zSql;
+ int rc;
+ sqlite3_stmt *pStmt;
+
+ zSql = sqlite3_vmprintf(zFormat, ap);
+ if( zSql==0 ) runtimeError("out of memory");
+ rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0);
+ if( rc ){
+ runtimeError("SQL statement error: %s\n\"%s\"", sqlite3_errmsg(g.db),
+ zSql);
+ }
+ sqlite3_free(zSql);
+ return pStmt;
+}
+static sqlite3_stmt *db_prepare(const char *zFormat, ...){
+ va_list ap;
+ sqlite3_stmt *pStmt;
+ va_start(ap, zFormat);
+ pStmt = db_vprepare(zFormat, ap);
+ va_end(ap);
+ return pStmt;
+}
+
+/*
+** Compute the hash for all rows of the query formed from the printf-style
+** zFormat and its argument.
+*/
+static void hash_one_query(const char *zFormat, ...){
+ va_list ap;
+ sqlite3_stmt *pStmt; /* The query defined by zFormat and "..." */
+ int nCol; /* Number of columns in the result set */
+ int i; /* Loop counter */
+
+ /* Prepare the query defined by zFormat and "..." */
+ va_start(ap, zFormat);
+ pStmt = db_vprepare(zFormat, ap);
+ va_end(ap);
+ nCol = sqlite3_column_count(pStmt);
+
+ /* Compute a hash over the result of the query */
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ for(i=0; i<nCol; i++){
+ switch( sqlite3_column_type(pStmt,i) ){
+ case SQLITE_NULL: {
+ hash_step((const unsigned char*)"0",1);
+ if( g.fDebug & DEBUG_FULLTRACE ) fprintf(stderr, "NULL\n");
+ break;
+ }
+ case SQLITE_INTEGER: {
+ sqlite3_uint64 u;
+ int j;
+ unsigned char x[8];
+ sqlite3_int64 v = sqlite3_column_int64(pStmt,i);
+ memcpy(&u, &v, 8);
+ for(j=7; j>=0; j--){
+ x[j] = u & 0xff;
+ u >>= 8;
+ }
+ hash_step((const unsigned char*)"1",1);
+ hash_step(x,8);
+ if( g.fDebug & DEBUG_FULLTRACE ){
+ fprintf(stderr, "INT %s\n", sqlite3_column_text(pStmt,i));
+ }
+ break;
+ }
+ case SQLITE_FLOAT: {
+ sqlite3_uint64 u;
+ int j;
+ unsigned char x[8];
+ double r = sqlite3_column_double(pStmt,i);
+ memcpy(&u, &r, 8);
+ for(j=7; j>=0; j--){
+ x[j] = u & 0xff;
+ u >>= 8;
+ }
+ hash_step((const unsigned char*)"2",1);
+ hash_step(x,8);
+ if( g.fDebug & DEBUG_FULLTRACE ){
+ fprintf(stderr, "FLOAT %s\n", sqlite3_column_text(pStmt,i));
+ }
+ break;
+ }
+ case SQLITE_TEXT: {
+ int n = sqlite3_column_bytes(pStmt, i);
+ const unsigned char *z = sqlite3_column_text(pStmt, i);
+ hash_step((const unsigned char*)"3", 1);
+ hash_step(z, n);
+ if( g.fDebug & DEBUG_FULLTRACE ){
+ fprintf(stderr, "TEXT '%s'\n", sqlite3_column_text(pStmt,i));
+ }
+ break;
+ }
+ case SQLITE_BLOB: {
+ int n = sqlite3_column_bytes(pStmt, i);
+ const unsigned char *z = sqlite3_column_blob(pStmt, i);
+ hash_step((const unsigned char*)"4", 1);
+ hash_step(z, n);
+ if( g.fDebug & DEBUG_FULLTRACE ){
+ fprintf(stderr, "BLOB (%d bytes)\n", n);
+ }
+ break;
+ }
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+}
+
+
+/*
+** Print sketchy documentation for this utility program
+*/
+static void showHelp(void){
+ printf("Usage: %s [options] FILE ...\n", g.zArgv0);
+ printf(
+"Compute a SHA1 hash on the content of database FILE. System tables such as\n"
+"sqlite_stat1, sqlite_stat4, and sqlite_sequence are omitted from the hash.\n"
+"Options:\n"
+" --debug N Set debugging flags to N (experts only)\n"
+" --like PATTERN Only hash tables whose name is LIKE the pattern\n"
+" --schema-only Only hash the schema - omit table content\n"
+" --without-schema Only hash table content - omit the schema\n"
+ );
+}
+
+int main(int argc, char **argv){
+ const char *zDb = 0; /* Name of the database currently being hashed */
+ int i; /* Loop counter */
+ int rc; /* Subroutine return code */
+ char *zErrMsg; /* Error message when opening database */
+ sqlite3_stmt *pStmt; /* An SQLite query */
+ const char *zLike = 0; /* LIKE pattern of tables to hash */
+ int omitSchema = 0; /* True to compute hash on content only */
+ int omitContent = 0; /* True to compute hash on schema only */
+ int nFile = 0; /* Number of input filenames seen */
+
+ g.zArgv0 = argv[0];
+ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"debug")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ g.fDebug = strtol(argv[++i], 0, 0);
+ }else
+ if( strcmp(z,"help")==0 ){
+ showHelp();
+ return 0;
+ }else
+ if( strcmp(z,"like")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ if( zLike!=0 ) cmdlineError("only one --like allowed");
+ zLike = argv[++i];
+ }else
+ if( strcmp(z,"schema-only")==0 ){
+ omitContent = 1;
+ }else
+ if( strcmp(z,"without-schema")==0 ){
+ omitSchema = 1;
+ }else
+ {
+ cmdlineError("unknown option: %s", argv[i]);
+ }
+ }else{
+ nFile++;
+ if( nFile<i ) argv[nFile] = argv[i];
+ }
+ }
+ if( nFile==0 ){
+ cmdlineError("no input files specified - nothing to do");
+ }
+ if( omitSchema && omitContent ){
+ cmdlineError("only one of --without-schema and --omit-schema allowed");
+ }
+ if( zLike==0 ) zLike = "%";
+
+ for(i=1; i<=nFile; i++){
+ static const int openFlags =
+ SQLITE_OPEN_READWRITE | /* Read/write so hot journals can recover */
+ SQLITE_OPEN_URI
+ ;
+ zDb = argv[i];
+ rc = sqlite3_open_v2(zDb, &g.db, openFlags, 0);
+ if( rc ){
+ fprintf(stderr, "cannot open database file '%s'\n", zDb);
+ continue;
+ }
+ rc = sqlite3_exec(g.db, "SELECT * FROM sqlite_schema", 0, 0, &zErrMsg);
+ if( rc || zErrMsg ){
+ sqlite3_close(g.db);
+ g.db = 0;
+ fprintf(stderr, "'%s' is not a valid SQLite database\n", zDb);
+ continue;
+ }
+
+ /* Start the hash */
+ hash_init();
+
+ /* Hash table content */
+ if( !omitContent ){
+ pStmt = db_prepare(
+ "SELECT name FROM sqlite_schema\n"
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
+ " AND name NOT LIKE 'sqlite_%%'\n"
+ " AND name LIKE '%q'\n"
+ " ORDER BY name COLLATE nocase;\n",
+ zLike
+ );
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ /* We want rows of the table to be hashed in PRIMARY KEY order.
+ ** Technically, an ORDER BY clause is required to guarantee that
+ ** order. However, though not guaranteed by the documentation, every
+ ** historical version of SQLite has always output rows in PRIMARY KEY
+ ** order when there is no WHERE or GROUP BY clause, so the ORDER BY
+ ** can be safely omitted. */
+ hash_one_query("SELECT * FROM \"%w\"", sqlite3_column_text(pStmt,0));
+ }
+ sqlite3_finalize(pStmt);
+ }
+
+ /* Hash the database schema */
+ if( !omitSchema ){
+ hash_one_query(
+ "SELECT type, name, tbl_name, sql FROM sqlite_schema\n"
+ " WHERE tbl_name LIKE '%q'\n"
+ " ORDER BY name COLLATE nocase;\n",
+ zLike
+ );
+ }
+
+ /* Finish and output the hash and close the database connection. */
+ hash_finish(zDb);
+ sqlite3_close(g.db);
+ }
+ return 0;
+}
diff --git a/tool/dbtotxt.c b/tool/dbtotxt.c
new file mode 100644
index 0000000..fbd6e3d
--- /dev/null
+++ b/tool/dbtotxt.c
@@ -0,0 +1,188 @@
+/*
+** Copyright 2008 D. Richard Hipp and Hipp, Wyrick & Company, Inc.
+** All Rights Reserved
+**
+******************************************************************************
+**
+** This file implements a stand-alone utility program that converts
+** a binary file (usually an SQLite database) into a text format that
+** is compact and friendly to human-readers.
+**
+** Usage:
+**
+** dbtotxt [OPTIONS] FILENAME
+**
+** where OPTIONS are zero or more of:
+**
+** --for-cli prepending '.open --hexdb' to the output
+**
+** --script The input file is expected to start with a
+** zero-terminated SQL string. Output the
+** ".open --hexdb" header, then the database
+** then the SQL.
+**
+** --pagesize N set the database page size for later reading
+**
+** The translation of the database appears on standard output. If the
+** --pagesize command-line option is omitted, then the page size is taken
+** from the database header.
+**
+** Compactness is achieved by suppressing lines of all zero bytes. This
+** works well at compressing test databases that are mostly empty. But
+** the output will probably be lengthy for a real database containing lots
+** of real content. For maximum compactness, it is suggested that test
+** databases be constructed with "zeroblob()" rather than "randomblob()"
+** used for filler content and with "PRAGMA secure_delete=ON" selected to
+** zero-out deleted content.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+/* Return true if the line is all zeros */
+static int allZero(unsigned char *aLine){
+ int i;
+ for(i=0; i<16 && aLine[i]==0; i++){}
+ return i==16;
+}
+
+int main(int argc, char **argv){
+ int pgsz = 0; /* page size */
+ int forCli = 0; /* whether to prepend with .open */
+ int bSQL = 0; /* Expect and SQL prefix */
+ long szFile; /* Size of the input file in bytes */
+ FILE *in; /* Input file */
+ int nSQL; /* Number of bytes of script */
+ int i, j; /* Loop counters */
+ int nErr = 0; /* Number of errors */
+ const char *zInputFile = 0; /* Name of the input file */
+ const char *zBaseName = 0; /* Base name of the file */
+ int lastPage = 0; /* Last page number shown */
+ int iPage; /* Current page number */
+ unsigned char *aData = 0; /* All data */
+ unsigned char *aLine; /* A single line of the file */
+ unsigned char *aHdr; /* File header */
+ unsigned char bShow[256]; /* Characters ok to display */
+ memset(bShow, '.', sizeof(bShow));
+ for(i=' '; i<='~'; i++){
+ if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i;
+ }
+ for(i=1; i<argc; i++){
+ if( argv[i][0]=='-' ){
+ const char *z = argv[i];
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"pagesize")==0 ){
+ i++;
+ pgsz = atoi(argv[i]);
+ if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ){
+ fprintf(stderr, "Page size must be a power of two between"
+ " 512 and 65536.\n");
+ nErr++;
+ }
+ continue;
+ }else if( strcmp(z,"for-cli")==0 ){
+ forCli = 1;
+ continue;
+ }else if( strcmp(z,"script")==0 ){
+ forCli = 1;
+ bSQL = 1;
+ continue;
+ }
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ nErr++;
+ }else if( zInputFile ){
+ fprintf(stderr, "Already using a different input file: [%s]\n", argv[i]);
+ nErr++;
+ }else{
+ zInputFile = argv[i];
+ }
+ }
+ if( zInputFile==0 ){
+ fprintf(stderr, "No input file specified.\n");
+ nErr++;
+ }
+ if( nErr ){
+ fprintf(stderr,
+ "Usage: %s [--pagesize N] [--script] [--for-cli] FILENAME\n", argv[0]);
+ exit(1);
+ }
+ in = fopen(zInputFile, "rb");
+ if( in==0 ){
+ fprintf(stderr, "Cannot open input file [%s]\n", zInputFile);
+ exit(1);
+ }
+ fseek(in, 0, SEEK_END);
+ szFile = ftell(in);
+ rewind(in);
+ if( szFile<100 ){
+ fprintf(stderr, "File too short. Minimum size is 100 bytes.\n");
+ exit(1);
+ }
+ aData = malloc( szFile+16 );
+ if( aData==0 ){
+ fprintf(stderr, "Failed to allocate %ld bytes\n", szFile);
+ exit(1);
+ }
+ if( fread(aData, szFile, 1, in)!=1 ){
+ fprintf(stderr, "Cannot read file info memory\n");
+ exit(1);
+ }
+ memset(aData+szFile, 0, 16);
+ fclose(in);
+ if( bSQL ){
+ for(i=0; i<szFile && aData[i]!=0; i++){}
+ if( i==szFile ){
+ fprintf(stderr, "No zero terminator on SQL script\n");
+ exit(1);
+ }
+ nSQL = i+1;
+ if( szFile - nSQL<100 ){
+ fprintf(stderr, "Less than 100 bytes in the database\n");
+ exit(1);
+ }
+ }else{
+ nSQL = 0;
+ }
+ aHdr = aData + nSQL;
+ if( pgsz==0 ){
+ pgsz = (aHdr[16]<<8) | aHdr[17];
+ if( pgsz==1 ) pgsz = 65536;
+ if( pgsz<512 || (pgsz&(pgsz-1))!=0 ){
+ fprintf(stderr, "Invalid page size in header: %d\n", pgsz);
+ exit(1);
+ }
+ }
+ zBaseName = zInputFile;
+ for(i=0; zInputFile[i]; i++){
+ if( zInputFile[i]=='/' && zInputFile[i+1]!=0 ) zBaseName = zInputFile+i+1;
+ }
+ if( forCli ){
+ printf(".open --hexdb\n");
+ }
+ printf("| size %d pagesize %d filename %s\n",(int)szFile,pgsz,zBaseName);
+ for(i=nSQL; i<szFile; i+=16){
+ aLine = aData+i;
+ if( allZero(aLine) ) continue;
+ iPage = i/pgsz + 1;
+ if( lastPage!=iPage ){
+ printf("| page %d offset %d\n", iPage, (iPage-1)*pgsz);
+ lastPage = iPage;
+ }
+ printf("| %5d:", i-(iPage-1)*pgsz);
+ for(j=0; j<16; j++) printf(" %02x", aLine[j]);
+ printf(" ");
+ for(j=0; j<16; j++){
+ unsigned char c = (unsigned char)aLine[j];
+ fputc( bShow[c], stdout);
+ }
+ fputc('\n', stdout);
+ }
+ printf("| end %s\n", zBaseName);
+ if( nSQL>0 ){
+ printf("%s\n", aData);
+ }
+ free( aData );
+ return 0;
+}
diff --git a/tool/dbtotxt.md b/tool/dbtotxt.md
new file mode 100644
index 0000000..f2bd7c9
--- /dev/null
+++ b/tool/dbtotxt.md
@@ -0,0 +1,56 @@
+<h1 align="center">The dbtotxt Tool</h1>
+
+The dbtotxt utility program reads an SQLite database file and writes its
+raw binary content to screen as a hex dump for testing and debugging
+purposes.
+
+The hex-dump output is formatted in such a way as to be easily readable
+both by humans and by software. The dbtotxt utility has long been a part
+of the TH3 test suite. The output of dbtotxt can be embedded in TH3 test
+scripts and used to generate very specific database files, perhaps with
+deliberately introduced corruption. The cov1/corrupt*.test modules in
+TH3 make extensive use of dbtotxt.
+
+More recently (2018-12-13) the dbtotxt utility has been added to the SQLite
+core and the command-line shell (CLI) has been augmented to be able to read
+dbtotxt output. The CLI dot-command is:
+
+> .open --hexdb ?OPTIONAL-FILENAME?
+
+If the OPTIONAL-FILENAME is included, then content is read from that file.
+If OPTIONAL-FILENAME is omitted, then the text is taken from the input stream,
+terminated by the "| end" line of the dbtotxt text. This allows small test
+databases to be embedded directly in scripts. Consider this example:
+
+>
+ .open --hexdb
+ | size 8192 pagesize 4096 filename x9.db
+ | page 1 offset 0
+ | 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+ | 16: 10 00 01 01 00 40 20 20 00 00 00 04 00 00 00 02 .....@ ........
+ | 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
+ | 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+ | 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 ................
+ | 96: 00 2e 30 38 0d 00 00 00 01 0f c0 00 0f c0 00 00 ..08............
+ | 4032: 3e 01 06 17 11 11 01 69 74 61 62 6c 65 74 31 74 >......itablet1t
+ | 4048: 31 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 1.CREATE TABLE t
+ | 4064: 31 28 78 2c 79 20 44 45 46 41 55 4c 54 20 78 27 1(x,y DEFAULT x'
+ | 4080: 66 66 27 2c 7a 20 44 45 46 41 55 4c 54 20 30 29 ff',z DEFAULT 0)
+ | page 2 offset 4096
+ | 0: 0d 08 14 00 04 00 10 00 0e 05 0a 0f 04 15 00 10 ................
+ | 16: 88 02 03 05 90 04 0e 08 00 00 00 00 00 00 00 00 ................
+ | 1040: 00 00 00 00 ff 87 7c 02 05 8f 78 0e 08 00 00 00 ......|...x.....
+ | 2064: 00 00 00 ff 0c 0a 01 fb 00 00 00 00 00 00 00 00 ................
+ | 2560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 83 ................
+ | 2576: 78 01 05 87 70 0e 08 00 00 00 00 00 00 00 00 00 x...p...........
+ | 3072: 00 00 00 00 00 00 00 00 00 ff 00 00 01 fb 00 00 ................
+ | 3584: 00 00 00 00 00 83 78 00 05 87 70 0e 08 00 00 00 ......x...p.....
+ | 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ................
+ | end x9.db
+ SELECT rowid FROM t1;
+ PRAGMA integrity_check;
+
+You can run this script to see that the database file is correctly decoded
+and loaded. Furthermore, you can make subtle corruptions to the input
+database simply by editing the hexadecimal description, then rerun the
+script to verify that SQLite correctly handles the corruption.
diff --git a/tool/enlargedb.c b/tool/enlargedb.c
new file mode 100644
index 0000000..dab5ef1
--- /dev/null
+++ b/tool/enlargedb.c
@@ -0,0 +1,68 @@
+/*
+** Try to enlarge an SQLite database by appending many unused pages.
+** The resulting database will fail PRAGMA integrity_check due to the
+** appended unused pages, but it should work otherwise.
+**
+** Usage:
+**
+** enlargedb DATABASE N
+**
+** Adds N blank pages onto the end of DATABASE. N can be decimal
+** or hex. The total number of pages after adding must be no greater
+** than 4294967297
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv){
+ char *zEnd;
+ long long int toAppend;
+ long long int currentSz;
+ long long int newSz;
+ FILE *f;
+ size_t got;
+ int pgsz;
+ char zero = 0;
+ unsigned char buf[100];
+
+ if( argc!=3 ) goto usage_error;
+ toAppend = strtoll(argv[2], &zEnd, 0);
+ if( zEnd==argv[2] || zEnd[0] ) goto usage_error;
+ if( toAppend<1 ){
+ fprintf(stderr, "N must be at least 1\n");
+ exit(1);
+ }
+ f = fopen(argv[1], "r+b");
+ if( f==0 ){
+ fprintf(stderr, "cannot open \"%s\" for reading and writing\n", argv[1]);
+ exit(1);
+ }
+ got = fread(buf, 1, sizeof(buf), f);
+ if( got!=sizeof(buf) ) goto not_valid_db;
+ if( strcmp((char*)buf,"SQLite format 3")!=0 ) goto not_valid_db;
+ pgsz = (buf[16]<<8) + buf[17];
+ if( pgsz==1 ) pgsz = 65536;
+ if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto not_valid_db;
+ currentSz = (buf[28]<<24) + (buf[29]<<16) + (buf[30]<<8) + buf[31];
+ newSz = currentSz + toAppend;
+ if( newSz > 0xffffffff ) newSz = 0xffffffff;
+ buf[28] = (newSz>>24) & 0xff;
+ buf[29] = (newSz>>16) & 0xff;
+ buf[30] = (newSz>>8) & 0xff;
+ buf[31] = newSz & 0xff;
+ fseek(f, 28, SEEK_SET);
+ fwrite(&buf[28],4,1,f);
+ fseek(f, (long)(newSz*pgsz - 1), SEEK_SET);
+ fwrite(&zero,1,1,f);
+ fclose(f);
+ return 0;
+
+not_valid_db:
+ fprintf(stderr,"not a valid database: %s\n", argv[1]);
+ exit(1);
+
+usage_error:
+ fprintf(stderr,"Usage: %s DATABASE N\n", argv[0]);
+ exit(1);
+}
diff --git a/tool/extract-sqlite3h.tcl b/tool/extract-sqlite3h.tcl
new file mode 100755
index 0000000..a0f7c4e
--- /dev/null
+++ b/tool/extract-sqlite3h.tcl
@@ -0,0 +1,21 @@
+#!/usr/bin/tclsh
+#
+# Given an sqlite3.c source file identified by the command-line
+# argument, extract the "sqlite3.h" header file that is embedded inside
+# the sqlite3.c source file and write it to standard output.
+#
+if {[llength $argv]!=1} {
+ puts stderr "Usage: $argv0 sqlite3.c >sqlite3.h"
+ exit 1
+}
+set in [open [lindex $argv 0] rb]
+while {![eof $in]} {
+ set line [gets $in]
+ if {[string match {* Begin file sqlite3.h *} $line]} break
+}
+while {![eof $in]} {
+ set line [gets $in]
+ if {[string match {* End of sqlite3.h *} $line]} break
+ puts $line
+}
+close $in
diff --git a/tool/extract.c b/tool/extract.c
new file mode 100644
index 0000000..5bf5caa
--- /dev/null
+++ b/tool/extract.c
@@ -0,0 +1,46 @@
+/*
+** Extract a range of bytes from a file.
+**
+** Usage:
+**
+** extract FILENAME OFFSET AMOUNT
+**
+** The bytes are written to standard output.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv){
+ FILE *f;
+ char *zBuf;
+ int ofst;
+ int n;
+ size_t got;
+
+ if( argc!=4 ){
+ fprintf(stderr, "Usage: %s FILENAME OFFSET AMOUNT\n", *argv);
+ return 1;
+ }
+ f = fopen(argv[1], "rb");
+ if( f==0 ){
+ fprintf(stderr, "cannot open \"%s\"\n", argv[1]);
+ return 1;
+ }
+ ofst = atoi(argv[2]);
+ n = atoi(argv[3]);
+ zBuf = malloc( n );
+ if( zBuf==0 ){
+ fprintf(stderr, "out of memory\n");
+ return 1;
+ }
+ fseek(f, ofst, SEEK_SET);
+ got = fread(zBuf, 1, n, f);
+ fclose(f);
+ if( got<n ){
+ fprintf(stderr, "got only %d of %d bytes\n", got, n);
+ return 1;
+ }else{
+ fwrite(zBuf, 1, n, stdout);
+ }
+ return 0;
+}
diff --git a/tool/fast_vacuum.c b/tool/fast_vacuum.c
new file mode 100644
index 0000000..5ca0271
--- /dev/null
+++ b/tool/fast_vacuum.c
@@ -0,0 +1,234 @@
+/*
+** 2013-10-01
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This program implements a high-speed version of the VACUUM command.
+** It repacks an SQLite database to remove as much unused space as
+** possible and to relocate content sequentially in the file.
+**
+** This program runs faster and uses less temporary disk space than the
+** built-in VACUUM command. On the other hand, this program has a number
+** of important restrictions relative to the built-in VACUUM command.
+**
+** (1) The caller must ensure that no other processes are accessing the
+** database file while the vacuum is taking place. The usual SQLite
+** file locking is insufficient for this. The caller must use
+** external means to make sure only this one routine is reading and
+** writing the database.
+**
+** (2) Database reconfiguration such as page size or auto_vacuum changes
+** are not supported by this utility.
+**
+** (3) The database file might be renamed if a power loss or crash
+** occurs at just the wrong moment. Recovery must be prepared to
+** to deal with the possibly changed filename.
+**
+** This program is intended as a *Demonstration Only*. The intent of this
+** program is to provide example code that application developers can use
+** when creating similar functionality in their applications.
+**
+** To compile this program:
+**
+** cc fast_vacuum.c sqlite3.c
+**
+** Add whatever linker options are required. (Example: "-ldl -lpthread").
+** Then to run the program:
+**
+** ./a.out file-to-vacuum
+**
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+** Finalize a prepared statement. If an error has occurred, print the
+** error message and exit.
+*/
+static void vacuumFinalize(sqlite3_stmt *pStmt){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ int rc = sqlite3_finalize(pStmt);
+ if( rc ){
+ fprintf(stderr, "finalize error: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+}
+
+/*
+** Execute zSql on database db. The SQL text is printed to standard
+** output. If an error occurs, print an error message and exit the
+** process.
+*/
+static void execSql(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ if( !zSql ){
+ fprintf(stderr, "out of memory!\n");
+ exit(1);
+ }
+ printf("%s;\n", zSql);
+ if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
+ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+ sqlite3_step(pStmt);
+ vacuumFinalize(pStmt);
+}
+
+/*
+** Execute zSql on database db. The zSql statement returns exactly
+** one column. Execute this return value as SQL on the same database.
+**
+** The zSql statement is printed on standard output prior to being
+** run. If any errors occur, an error is printed and the process
+** exits.
+*/
+static void execExecSql(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ int rc;
+
+ printf("%s;\n", zSql);
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ execSql(db, (char*)sqlite3_column_text(pStmt, 0));
+ }
+ vacuumFinalize(pStmt);
+}
+
+
+int main(int argc, char **argv){
+ sqlite3 *db; /* Connection to the database file */
+ int rc; /* Return code from SQLite interface calls */
+ sqlite3_uint64 r; /* A random number */
+ const char *zDbToVacuum; /* Database to be vacuumed */
+ char *zBackupDb; /* Backup copy of the original database */
+ char *zTempDb; /* Temporary database */
+ char *zSql; /* An SQL statement */
+
+ if( argc!=2 ){
+ fprintf(stderr, "Usage: %s DATABASE\n", argv[0]);
+ return 1;
+ }
+
+ /* Identify the database file to be vacuumed and open it.
+ */
+ zDbToVacuum = argv[1];
+ printf("-- open database file \"%s\"\n", zDbToVacuum);
+ rc = sqlite3_open(zDbToVacuum, &db);
+ if( rc ){
+ fprintf(stderr, "%s: %s\n", zDbToVacuum, sqlite3_errstr(rc));
+ return 1;
+ }
+
+ /* Create names for two other files. zTempDb will be a new database
+ ** into which we construct a vacuumed copy of zDbToVacuum. zBackupDb
+ ** will be a new name for zDbToVacuum after it is vacuumed.
+ */
+ sqlite3_randomness(sizeof(r), &r);
+ zTempDb = sqlite3_mprintf("%s-vacuum-%016llx", zDbToVacuum, r);
+ zBackupDb = sqlite3_mprintf("%s-backup-%016llx", zDbToVacuum, r);
+
+ /* Attach the zTempDb database to the database connection.
+ */
+ zSql = sqlite3_mprintf("ATTACH '%q' AS vacuum_db;", zTempDb);
+ execSql(db, zSql);
+ sqlite3_free(zSql);
+
+ /* TODO:
+ ** Set the page_size and auto_vacuum mode for zTempDb here, if desired.
+ */
+
+ /* The vacuum will occur inside of a transaction. Set writable_schema
+ ** to ON so that we can directly update the sqlite_schema table in the
+ ** zTempDb database.
+ */
+ execSql(db, "PRAGMA writable_schema=ON");
+ execSql(db, "BEGIN");
+
+
+ /* Query the schema of the main database. Create a mirror schema
+ ** in the temporary database.
+ */
+ execExecSql(db,
+ "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) "
+ " FROM sqlite_schema WHERE type='table' AND name!='sqlite_sequence'"
+ " AND rootpage>0"
+ );
+ execExecSql(db,
+ "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)"
+ " FROM sqlite_schema WHERE sql LIKE 'CREATE INDEX %'"
+ );
+ execExecSql(db,
+ "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) "
+ " FROM sqlite_schema WHERE sql LIKE 'CREATE UNIQUE INDEX %'"
+ );
+
+ /* Loop through the tables in the main database. For each, do
+ ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy
+ ** the contents to the temporary database.
+ */
+ execExecSql(db,
+ "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
+ "|| ' SELECT * FROM main.' || quote(name) "
+ "FROM main.sqlite_schema "
+ "WHERE type = 'table' AND name!='sqlite_sequence' "
+ " AND rootpage>0"
+ );
+
+ /* Copy over the sequence table
+ */
+ execExecSql(db,
+ "SELECT 'DELETE FROM vacuum_db.' || quote(name) "
+ "FROM vacuum_db.sqlite_schema WHERE name='sqlite_sequence'"
+ );
+ execExecSql(db,
+ "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
+ "|| ' SELECT * FROM main.' || quote(name) "
+ "FROM vacuum_db.sqlite_schema WHERE name=='sqlite_sequence'"
+ );
+
+ /* Copy the triggers, views, and virtual tables from the main database
+ ** over to the temporary database. None of these objects has any
+ ** associated storage, so all we have to do is copy their entries
+ ** from the SQLITE_MASTER table.
+ */
+ execSql(db,
+ "INSERT INTO vacuum_db.sqlite_schema "
+ " SELECT type, name, tbl_name, rootpage, sql"
+ " FROM main.sqlite_schema"
+ " WHERE type='view' OR type='trigger'"
+ " OR (type='table' AND rootpage=0)"
+ );
+
+ /* Commit the transaction and close the database
+ */
+ execSql(db, "COMMIT");
+ printf("-- close database\n");
+ sqlite3_close(db);
+
+
+ /* At this point, zDbToVacuum is unchanged. zTempDb contains a
+ ** vacuumed copy of zDbToVacuum. Rearrange filenames so that
+ ** zTempDb becomes thenew zDbToVacuum.
+ */
+ printf("-- rename \"%s\" to \"%s\"\n", zDbToVacuum, zBackupDb);
+ rename(zDbToVacuum, zBackupDb);
+ printf("-- rename \"%s\" to \"%s\"\n", zTempDb, zDbToVacuum);
+ rename(zTempDb, zDbToVacuum);
+
+ /* Release allocated memory */
+ sqlite3_free(zTempDb);
+ sqlite3_free(zBackupDb);
+ return 0;
+}
diff --git a/tool/fragck.tcl b/tool/fragck.tcl
new file mode 100644
index 0000000..35e76f4
--- /dev/null
+++ b/tool/fragck.tcl
@@ -0,0 +1,149 @@
+# Run this TCL script using "testfixture" to get a report that shows
+# the sequence of database pages used by a particular table or index.
+# This information is used for fragmentation analysis.
+#
+
+# Get the name of the database to analyze
+#
+
+if {[llength $argv]!=2} {
+ puts stderr "Usage: $argv0 database-name table-or-index-name"
+ exit 1
+}
+set file_to_analyze [lindex $argv 0]
+if {![file exists $file_to_analyze]} {
+ puts stderr "No such file: $file_to_analyze"
+ exit 1
+}
+if {![file readable $file_to_analyze]} {
+ puts stderr "File is not readable: $file_to_analyze"
+ exit 1
+}
+if {[file size $file_to_analyze]<512} {
+ puts stderr "Empty or malformed database: $file_to_analyze"
+ exit 1
+}
+set objname [lindex $argv 1]
+
+# Open the database
+#
+sqlite3 db [lindex $argv 0]
+set DB [btree_open [lindex $argv 0] 1000 0]
+
+# This proc is a wrapper around the btree_cursor_info command. The
+# second argument is an open btree cursor returned by [btree_cursor].
+# The first argument is the name of an array variable that exists in
+# the scope of the caller. If the third argument is non-zero, then
+# info is returned for the page that lies $up entries upwards in the
+# tree-structure. (i.e. $up==1 returns the parent page, $up==2 the
+# grandparent etc.)
+#
+# The following entries in that array are filled in with information retrieved
+# using [btree_cursor_info]:
+#
+# $arrayvar(page_no) = The page number
+# $arrayvar(entry_no) = The entry number
+# $arrayvar(page_entries) = Total number of entries on this page
+# $arrayvar(cell_size) = Cell size (local payload + header)
+# $arrayvar(page_freebytes) = Number of free bytes on this page
+# $arrayvar(page_freeblocks) = Number of free blocks on the page
+# $arrayvar(payload_bytes) = Total payload size (local + overflow)
+# $arrayvar(header_bytes) = Header size in bytes
+# $arrayvar(local_payload_bytes) = Local payload size
+# $arrayvar(parent) = Parent page number
+#
+proc cursor_info {arrayvar csr {up 0}} {
+ upvar $arrayvar a
+ foreach [list a(page_no) \
+ a(entry_no) \
+ a(page_entries) \
+ a(cell_size) \
+ a(page_freebytes) \
+ a(page_freeblocks) \
+ a(payload_bytes) \
+ a(header_bytes) \
+ a(local_payload_bytes) \
+ a(parent) \
+ a(first_ovfl) ] [btree_cursor_info $csr $up] break
+}
+
+# Determine the page-size of the database. This global variable is used
+# throughout the script.
+#
+set pageSize [db eval {PRAGMA page_size}]
+
+# Find the root page of table or index to be analyzed. Also find out
+# if the object is a table or an index.
+#
+if {$objname=="sqlite_master"} {
+ set rootpage 1
+ set type table
+} else {
+ db eval {
+ SELECT rootpage, type FROM sqlite_master
+ WHERE name=$objname
+ } break
+ if {![info exists rootpage]} {
+ puts stderr "no such table or index: $objname"
+ exit 1
+ }
+ if {$type!="table" && $type!="index"} {
+ puts stderr "$objname is something other than a table or index"
+ exit 1
+ }
+ if {![string is integer -strict $rootpage]} {
+ puts stderr "invalid root page for $objname: $rootpage"
+ exit 1
+ }
+}
+
+# The cursor $csr is pointing to an entry. Print out information
+# about the page that $up levels above that page that contains
+# the entry. If $up==0 use the page that contains the entry.
+#
+# If information about the page has been printed already, then
+# this is a no-op.
+#
+proc page_info {csr up} {
+ global seen
+ cursor_info ci $csr $up
+ set pg $ci(page_no)
+ if {[info exists seen($pg)]} return
+ set seen($pg) 1
+
+ # Do parent pages first
+ #
+ if {$ci(parent)} {
+ page_info $csr [expr {$up+1}]
+ }
+
+ # Find the depth of this page
+ #
+ set depth 1
+ set i $up
+ while {$ci(parent)} {
+ incr i
+ incr depth
+ cursor_info ci $csr $i
+ }
+
+ # print the results
+ #
+ puts [format {LEVEL %d: %6d} $depth $pg]
+}
+
+
+
+
+# Loop through the object and print out page numbers
+#
+set csr [btree_cursor $DB $rootpage 0]
+for {btree_first $csr} {![btree_eof $csr]} {btree_next $csr} {
+ page_info $csr 0
+ set i 1
+ foreach pg [btree_ovfl_info $DB $csr] {
+ puts [format {OVFL %3d: %6d} $i $pg]
+ incr i
+ }
+}
+exit 0
diff --git a/tool/fuzzershell.c b/tool/fuzzershell.c
new file mode 100644
index 0000000..7a7aef0
--- /dev/null
+++ b/tool/fuzzershell.c
@@ -0,0 +1,1267 @@
+/*
+** 2015-04-17
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This is a utility program designed to aid running the SQLite library
+** against an external fuzzer, such as American Fuzzy Lop (AFL)
+** (http://lcamtuf.coredump.cx/afl/). Basically, this program reads
+** SQL text from standard input and passes it through to SQLite for evaluation,
+** just like the "sqlite3" command-line shell. Differences from the
+** command-line shell:
+**
+** (1) The complex "dot-command" extensions are omitted. This
+** prevents the fuzzer from discovering that it can run things
+** like ".shell rm -rf ~"
+**
+** (2) The database is opened with the SQLITE_OPEN_MEMORY flag so that
+** no disk I/O from the database is permitted. The ATTACH command
+** with a filename still uses an in-memory database.
+**
+** (3) The main in-memory database can be initialized from a template
+** disk database so that the fuzzer starts with a database containing
+** content.
+**
+** (4) The eval() SQL function is added, allowing the fuzzer to do
+** interesting recursive operations.
+**
+** (5) An error is raised if there is a memory leak.
+**
+** The input text can be divided into separate test cases using comments
+** of the form:
+**
+** |****<...>****|
+**
+** where the "..." is arbitrary text. (Except the "|" should really be "/".
+** "|" is used here to avoid compiler errors about nested comments.)
+** A separate in-memory SQLite database is created to run each test case.
+** This feature allows the "queue" of AFL to be captured into a single big
+** file using a command like this:
+**
+** (for i in id:*; do echo '|****<'$i'>****|'; cat $i; done) >~/all-queue.txt
+**
+** (Once again, change the "|" to "/") Then all elements of the AFL queue
+** can be run in a single go (for regression testing, for example) by typing:
+**
+** fuzzershell -f ~/all-queue.txt
+**
+** After running each chunk of SQL, the database connection is closed. The
+** program aborts if the close fails or if there is any unfreed memory after
+** the close.
+**
+** New test cases can be appended to all-queue.txt at any time. If redundant
+** test cases are added, they can be eliminated by running:
+**
+** fuzzershell -f ~/all-queue.txt --unique-cases ~/unique-cases.txt
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include "sqlite3.h"
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+
+/*
+** All global variables are gathered into the "g" singleton.
+*/
+struct GlobalVars {
+ const char *zArgv0; /* Name of program */
+ sqlite3_mem_methods sOrigMem; /* Original memory methods */
+ sqlite3_mem_methods sOomMem; /* Memory methods with OOM simulator */
+ int iOomCntdown; /* Memory fails on 1 to 0 transition */
+ int nOomFault; /* Increments for each OOM fault */
+ int bOomOnce; /* Fail just once if true */
+ int bOomEnable; /* True to enable OOM simulation */
+ int nOomBrkpt; /* Number of calls to oomFault() */
+ char zTestName[100]; /* Name of current test */
+} g;
+
+/*
+** Maximum number of iterations for an OOM test
+*/
+#ifndef OOM_MAX
+# define OOM_MAX 625
+#endif
+
+/*
+** This routine is called when a simulated OOM occurs. It exists as a
+** convenient place to set a debugger breakpoint.
+*/
+static void oomFault(void){
+ g.nOomBrkpt++; /* Prevent oomFault() from being optimized out */
+}
+
+
+/* Versions of malloc() and realloc() that simulate OOM conditions */
+static void *oomMalloc(int nByte){
+ if( nByte>0 && g.bOomEnable && g.iOomCntdown>0 ){
+ g.iOomCntdown--;
+ if( g.iOomCntdown==0 ){
+ if( g.nOomFault==0 ) oomFault();
+ g.nOomFault++;
+ if( !g.bOomOnce ) g.iOomCntdown = 1;
+ return 0;
+ }
+ }
+ return g.sOrigMem.xMalloc(nByte);
+}
+static void *oomRealloc(void *pOld, int nByte){
+ if( nByte>0 && g.bOomEnable && g.iOomCntdown>0 ){
+ g.iOomCntdown--;
+ if( g.iOomCntdown==0 ){
+ if( g.nOomFault==0 ) oomFault();
+ g.nOomFault++;
+ if( !g.bOomOnce ) g.iOomCntdown = 1;
+ return 0;
+ }
+ }
+ return g.sOrigMem.xRealloc(pOld, nByte);
+}
+
+/*
+** Print an error message and abort in such a way to indicate to the
+** fuzzer that this counts as a crash.
+*/
+static void abendError(const char *zFormat, ...){
+ va_list ap;
+ if( g.zTestName[0] ){
+ fprintf(stderr, "%s (%s): ", g.zArgv0, g.zTestName);
+ }else{
+ fprintf(stderr, "%s: ", g.zArgv0);
+ }
+ va_start(ap, zFormat);
+ vfprintf(stderr, zFormat, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ abort();
+}
+/*
+** Print an error message and quit, but not in a way that would look
+** like a crash.
+*/
+static void fatalError(const char *zFormat, ...){
+ va_list ap;
+ if( g.zTestName[0] ){
+ fprintf(stderr, "%s (%s): ", g.zArgv0, g.zTestName);
+ }else{
+ fprintf(stderr, "%s: ", g.zArgv0);
+ }
+ va_start(ap, zFormat);
+ vfprintf(stderr, zFormat, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+/*
+** Evaluate some SQL. Abort if unable.
+*/
+static void sqlexec(sqlite3 *db, const char *zFormat, ...){
+ va_list ap;
+ char *zSql;
+ char *zErrMsg = 0;
+ int rc;
+ va_start(ap, zFormat);
+ zSql = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg);
+ if( rc ) abendError("failed sql [%s]: %s", zSql, zErrMsg);
+ sqlite3_free(zSql);
+}
+
+/*
+** This callback is invoked by sqlite3_log().
+*/
+static void shellLog(void *pNotUsed, int iErrCode, const char *zMsg){
+ printf("LOG: (%d) %s\n", iErrCode, zMsg);
+ fflush(stdout);
+}
+static void shellLogNoop(void *pNotUsed, int iErrCode, const char *zMsg){
+ return;
+}
+
+/*
+** This callback is invoked by sqlite3_exec() to return query results.
+*/
+static int execCallback(void *NotUsed, int argc, char **argv, char **colv){
+ int i;
+ static unsigned cnt = 0;
+ printf("ROW #%u:\n", ++cnt);
+ if( argv ){
+ for(i=0; i<argc; i++){
+ printf(" %s=", colv[i]);
+ if( argv[i] ){
+ printf("[%s]\n", argv[i]);
+ }else{
+ printf("NULL\n");
+ }
+ }
+ }
+ fflush(stdout);
+ return 0;
+}
+static int execNoop(void *NotUsed, int argc, char **argv, char **colv){
+ return 0;
+}
+
+#ifndef SQLITE_OMIT_TRACE
+/*
+** This callback is invoked by sqlite3_trace() as each SQL statement
+** starts.
+*/
+static void traceCallback(void *NotUsed, const char *zMsg){
+ printf("TRACE: %s\n", zMsg);
+ fflush(stdout);
+}
+static void traceNoop(void *NotUsed, const char *zMsg){
+ return;
+}
+#endif
+
+/***************************************************************************
+** String accumulator object
+*/
+typedef struct Str Str;
+struct Str {
+ char *z; /* The string. Memory from malloc() */
+ sqlite3_uint64 n; /* Bytes of input used */
+ sqlite3_uint64 nAlloc; /* Bytes allocated to z[] */
+ int oomErr; /* OOM error has been seen */
+};
+
+/* Initialize a Str object */
+static void StrInit(Str *p){
+ memset(p, 0, sizeof(*p));
+}
+
+/* Append text to the end of a Str object */
+static void StrAppend(Str *p, const char *z){
+ sqlite3_uint64 n = strlen(z);
+ if( p->n + n >= p->nAlloc ){
+ char *zNew;
+ sqlite3_uint64 nNew;
+ if( p->oomErr ) return;
+ nNew = p->nAlloc*2 + 100 + n;
+ zNew = sqlite3_realloc(p->z, (int)nNew);
+ if( zNew==0 ){
+ sqlite3_free(p->z);
+ memset(p, 0, sizeof(*p));
+ p->oomErr = 1;
+ return;
+ }
+ p->z = zNew;
+ p->nAlloc = nNew;
+ }
+ memcpy(p->z + p->n, z, (size_t)n);
+ p->n += n;
+ p->z[p->n] = 0;
+}
+
+/* Return the current string content */
+static char *StrStr(Str *p){
+ return p->z;
+}
+
+/* Free the string */
+static void StrFree(Str *p){
+ sqlite3_free(p->z);
+ StrInit(p);
+}
+
+/***************************************************************************
+** eval() implementation copied from ../ext/misc/eval.c
+*/
+/*
+** Structure used to accumulate the output
+*/
+struct EvalResult {
+ char *z; /* Accumulated output */
+ const char *zSep; /* Separator */
+ int szSep; /* Size of the separator string */
+ sqlite3_int64 nAlloc; /* Number of bytes allocated for z[] */
+ sqlite3_int64 nUsed; /* Number of bytes of z[] actually used */
+};
+
+/*
+** Callback from sqlite_exec() for the eval() function.
+*/
+static int callback(void *pCtx, int argc, char **argv, char **colnames){
+ struct EvalResult *p = (struct EvalResult*)pCtx;
+ int i;
+ for(i=0; i<argc; i++){
+ const char *z = argv[i] ? argv[i] : "";
+ size_t sz = strlen(z);
+ if( (sqlite3_int64)sz+p->nUsed+p->szSep+1 > p->nAlloc ){
+ char *zNew;
+ p->nAlloc = p->nAlloc*2 + sz + p->szSep + 1;
+ /* Using sqlite3_realloc64() would be better, but it is a recent
+ ** addition and will cause a segfault if loaded by an older version
+ ** of SQLite. */
+ zNew = p->nAlloc<=0x7fffffff ? sqlite3_realloc(p->z, (int)p->nAlloc) : 0;
+ if( zNew==0 ){
+ sqlite3_free(p->z);
+ memset(p, 0, sizeof(*p));
+ return 1;
+ }
+ p->z = zNew;
+ }
+ if( p->nUsed>0 ){
+ memcpy(&p->z[p->nUsed], p->zSep, p->szSep);
+ p->nUsed += p->szSep;
+ }
+ memcpy(&p->z[p->nUsed], z, sz);
+ p->nUsed += sz;
+ }
+ return 0;
+}
+
+/*
+** Implementation of the eval(X) and eval(X,Y) SQL functions.
+**
+** Evaluate the SQL text in X. Return the results, using string
+** Y as the separator. If Y is omitted, use a single space character.
+*/
+static void sqlEvalFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zSql;
+ sqlite3 *db;
+ char *zErr = 0;
+ int rc;
+ struct EvalResult x;
+
+ memset(&x, 0, sizeof(x));
+ x.zSep = " ";
+ zSql = (const char*)sqlite3_value_text(argv[0]);
+ if( zSql==0 ) return;
+ if( argc>1 ){
+ x.zSep = (const char*)sqlite3_value_text(argv[1]);
+ if( x.zSep==0 ) return;
+ }
+ x.szSep = (int)strlen(x.zSep);
+ db = sqlite3_context_db_handle(context);
+ rc = sqlite3_exec(db, zSql, callback, &x, &zErr);
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error(context, zErr, -1);
+ sqlite3_free(zErr);
+ }else if( x.zSep==0 ){
+ sqlite3_result_error_nomem(context);
+ sqlite3_free(x.z);
+ }else{
+ sqlite3_result_text(context, x.z, (int)x.nUsed, sqlite3_free);
+ }
+}
+/* End of the eval() implementation
+******************************************************************************/
+
+/******************************************************************************
+** The generate_series(START,END,STEP) eponymous table-valued function.
+**
+** This code is copy/pasted from ext/misc/series.c in the SQLite source tree.
+*/
+/* series_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct series_cursor series_cursor;
+struct series_cursor {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ int isDesc; /* True to count down rather than up */
+ sqlite3_int64 iRowid; /* The rowid */
+ sqlite3_int64 iValue; /* Current value ("value") */
+ sqlite3_int64 mnValue; /* Mimimum value ("start") */
+ sqlite3_int64 mxValue; /* Maximum value ("stop") */
+ sqlite3_int64 iStep; /* Increment ("step") */
+};
+
+/*
+** The seriesConnect() method is invoked to create a new
+** series_vtab that describes the generate_series virtual table.
+**
+** Think of this routine as the constructor for series_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the series_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+** result set of queries against generate_series will look like.
+*/
+static int seriesConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ sqlite3_vtab *pNew;
+ int rc;
+
+/* Column numbers */
+#define SERIES_COLUMN_VALUE 0
+#define SERIES_COLUMN_START 1
+#define SERIES_COLUMN_STOP 2
+#define SERIES_COLUMN_STEP 3
+
+ rc = sqlite3_declare_vtab(db,
+ "CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
+ if( rc==SQLITE_OK ){
+ pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for series_cursor objects.
+*/
+static int seriesDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new series_cursor object.
+*/
+static int seriesOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
+ series_cursor *pCur;
+ pCur = sqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a series_cursor.
+*/
+static int seriesClose(sqlite3_vtab_cursor *cur){
+ sqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a series_cursor to its next row of output.
+*/
+static int seriesNext(sqlite3_vtab_cursor *cur){
+ series_cursor *pCur = (series_cursor*)cur;
+ if( pCur->isDesc ){
+ pCur->iValue -= pCur->iStep;
+ }else{
+ pCur->iValue += pCur->iStep;
+ }
+ pCur->iRowid++;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the series_cursor
+** is currently pointing.
+*/
+static int seriesColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ series_cursor *pCur = (series_cursor*)cur;
+ sqlite3_int64 x = 0;
+ switch( i ){
+ case SERIES_COLUMN_START: x = pCur->mnValue; break;
+ case SERIES_COLUMN_STOP: x = pCur->mxValue; break;
+ case SERIES_COLUMN_STEP: x = pCur->iStep; break;
+ default: x = pCur->iValue; break;
+ }
+ sqlite3_result_int64(ctx, x);
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** rowid is the same as the output value.
+*/
+static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ series_cursor *pCur = (series_cursor*)cur;
+ *pRowid = pCur->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int seriesEof(sqlite3_vtab_cursor *cur){
+ series_cursor *pCur = (series_cursor*)cur;
+ if( pCur->isDesc ){
+ return pCur->iValue < pCur->mnValue;
+ }else{
+ return pCur->iValue > pCur->mxValue;
+ }
+}
+
+/* True to cause run-time checking of the start=, stop=, and/or step=
+** parameters. The only reason to do this is for testing the
+** constraint checking logic for virtual tables in the SQLite core.
+*/
+#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
+# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
+#endif
+
+/*
+** This method is called to "rewind" the series_cursor object back
+** to the first row of output. This method is always called at least
+** once prior to any call to seriesColumn() or seriesRowid() or
+** seriesEof().
+**
+** The query plan selected by seriesBestIndex is passed in the idxNum
+** parameter. (idxStr is not used in this implementation.) idxNum
+** is a bitmask showing which constraints are available:
+**
+** 1: start=VALUE
+** 2: stop=VALUE
+** 4: step=VALUE
+**
+** Also, if bit 8 is set, that means that the series should be output
+** in descending order rather than in ascending order.
+**
+** This routine should initialize the cursor and position it so that it
+** is pointing at the first row, or pointing off the end of the table
+** (so that seriesEof() will return true) if the table is empty.
+*/
+static int seriesFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ series_cursor *pCur = (series_cursor *)pVtabCursor;
+ int i = 0;
+ if( idxNum & 1 ){
+ pCur->mnValue = sqlite3_value_int64(argv[i++]);
+ }else{
+ pCur->mnValue = 0;
+ }
+ if( idxNum & 2 ){
+ pCur->mxValue = sqlite3_value_int64(argv[i++]);
+ }else{
+ pCur->mxValue = 0xffffffff;
+ }
+ if( idxNum & 4 ){
+ pCur->iStep = sqlite3_value_int64(argv[i++]);
+ if( pCur->iStep<1 ) pCur->iStep = 1;
+ }else{
+ pCur->iStep = 1;
+ }
+ if( idxNum & 8 ){
+ pCur->isDesc = 1;
+ pCur->iValue = pCur->mxValue;
+ if( pCur->iStep>0 ){
+ pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep;
+ }
+ }else{
+ pCur->isDesc = 0;
+ pCur->iValue = pCur->mnValue;
+ }
+ pCur->iRowid = 1;
+ return SQLITE_OK;
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the generate_series virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+**
+** In this implementation idxNum is used to represent the
+** query plan. idxStr is unused.
+**
+** The query plan is represented by bits in idxNum:
+**
+** (1) start = $value -- constraint exists
+** (2) stop = $value -- constraint exists
+** (4) step = $value -- constraint exists
+** (8) output in descending order
+*/
+static int seriesBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i; /* Loop over constraints */
+ int idxNum = 0; /* The query plan bitmask */
+ int startIdx = -1; /* Index of the start= constraint, or -1 if none */
+ int stopIdx = -1; /* Index of the stop= constraint, or -1 if none */
+ int stepIdx = -1; /* Index of the step= constraint, or -1 if none */
+ int nArg = 0; /* Number of arguments that seriesFilter() expects */
+
+ const struct sqlite3_index_constraint *pConstraint;
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ if( pConstraint->usable==0 ) continue;
+ if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ switch( pConstraint->iColumn ){
+ case SERIES_COLUMN_START:
+ startIdx = i;
+ idxNum |= 1;
+ break;
+ case SERIES_COLUMN_STOP:
+ stopIdx = i;
+ idxNum |= 2;
+ break;
+ case SERIES_COLUMN_STEP:
+ stepIdx = i;
+ idxNum |= 4;
+ break;
+ }
+ }
+ if( startIdx>=0 ){
+ pIdxInfo->aConstraintUsage[startIdx].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[startIdx].omit= !SQLITE_SERIES_CONSTRAINT_VERIFY;
+ }
+ if( stopIdx>=0 ){
+ pIdxInfo->aConstraintUsage[stopIdx].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[stopIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
+ }
+ if( stepIdx>=0 ){
+ pIdxInfo->aConstraintUsage[stepIdx].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[stepIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
+ }
+ if( (idxNum & 3)==3 ){
+ /* Both start= and stop= boundaries are available. This is the
+ ** the preferred case */
+ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
+ pIdxInfo->estimatedRows = 1000;
+ if( pIdxInfo->nOrderBy==1 ){
+ if( pIdxInfo->aOrderBy[0].desc ) idxNum |= 8;
+ pIdxInfo->orderByConsumed = 1;
+ }
+ }else{
+ /* If either boundary is missing, we have to generate a huge span
+ ** of numbers. Make this case very expensive so that the query
+ ** planner will work hard to avoid it. */
+ pIdxInfo->estimatedCost = (double)2147483647;
+ pIdxInfo->estimatedRows = 2147483647;
+ }
+ pIdxInfo->idxNum = idxNum;
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** generate_series virtual table.
+*/
+static sqlite3_module seriesModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ seriesConnect, /* xConnect */
+ seriesBestIndex, /* xBestIndex */
+ seriesDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ seriesOpen, /* xOpen - open a cursor */
+ seriesClose, /* xClose - close a cursor */
+ seriesFilter, /* xFilter - configure scan constraints */
+ seriesNext, /* xNext - advance a cursor */
+ seriesEof, /* xEof - check for end of scan */
+ seriesColumn, /* xColumn - read data */
+ seriesRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
+};
+/* END the generate_series(START,END,STEP) implementation
+*********************************************************************************/
+
+/*
+** Print sketchy documentation for this utility program
+*/
+static void showHelp(void){
+ printf("Usage: %s [options] ?FILE...?\n", g.zArgv0);
+ printf(
+"Read SQL text from FILE... (or from standard input if FILE... is omitted)\n"
+"and then evaluate each block of SQL contained therein.\n"
+"Options:\n"
+" --autovacuum Enable AUTOVACUUM mode\n"
+" --database FILE Use database FILE instead of an in-memory database\n"
+" --disable-lookaside Turn off lookaside memory\n"
+" --heap SZ MIN Memory allocator uses SZ bytes & min allocation MIN\n"
+" --help Show this help text\n"
+" --lookaside N SZ Configure lookaside for N slots of SZ bytes each\n"
+" --oom Run each test multiple times in a simulated OOM loop\n"
+" --pagesize N Set the page size to N\n"
+" --pcache N SZ Configure N pages of pagecache each of size SZ bytes\n"
+" -q Reduced output\n"
+" --quiet Reduced output\n"
+" --scratch N SZ Configure scratch memory for N slots of SZ bytes each\n"
+" --unique-cases FILE Write all unique test cases to FILE\n"
+" --utf16be Set text encoding to UTF-16BE\n"
+" --utf16le Set text encoding to UTF-16LE\n"
+" -v Increased output\n"
+" --verbose Increased output\n"
+ );
+}
+
+/*
+** Return the value of a hexadecimal digit. Return -1 if the input
+** is not a hex digit.
+*/
+static int hexDigitValue(char c){
+ if( c>='0' && c<='9' ) return c - '0';
+ if( c>='a' && c<='f' ) return c - 'a' + 10;
+ if( c>='A' && c<='F' ) return c - 'A' + 10;
+ return -1;
+}
+
+/*
+** Interpret zArg as an integer value, possibly with suffixes.
+*/
+static int integerValue(const char *zArg){
+ sqlite3_int64 v = 0;
+ static const struct { char *zSuffix; int iMult; } aMult[] = {
+ { "KiB", 1024 },
+ { "MiB", 1024*1024 },
+ { "GiB", 1024*1024*1024 },
+ { "KB", 1000 },
+ { "MB", 1000000 },
+ { "GB", 1000000000 },
+ { "K", 1000 },
+ { "M", 1000000 },
+ { "G", 1000000000 },
+ };
+ int i;
+ int isNeg = 0;
+ if( zArg[0]=='-' ){
+ isNeg = 1;
+ zArg++;
+ }else if( zArg[0]=='+' ){
+ zArg++;
+ }
+ if( zArg[0]=='0' && zArg[1]=='x' ){
+ int x;
+ zArg += 2;
+ while( (x = hexDigitValue(zArg[0]))>=0 ){
+ v = (v<<4) + x;
+ zArg++;
+ }
+ }else{
+ while( ISDIGIT(zArg[0]) ){
+ v = v*10 + zArg[0] - '0';
+ zArg++;
+ }
+ }
+ for(i=0; i<sizeof(aMult)/sizeof(aMult[0]); i++){
+ if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){
+ v *= aMult[i].iMult;
+ break;
+ }
+ }
+ if( v>0x7fffffff ) abendError("parameter too large - max 2147483648");
+ return (int)(isNeg? -v : v);
+}
+
+/* Return the current wall-clock time */
+static sqlite3_int64 timeOfDay(void){
+ static sqlite3_vfs *clockVfs = 0;
+ sqlite3_int64 t;
+ if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0);
+ if( clockVfs->iVersion>=1 && clockVfs->xCurrentTimeInt64!=0 ){
+ clockVfs->xCurrentTimeInt64(clockVfs, &t);
+ }else{
+ double r;
+ clockVfs->xCurrentTime(clockVfs, &r);
+ t = (sqlite3_int64)(r*86400000.0);
+ }
+ return t;
+}
+
+int main(int argc, char **argv){
+ char *zIn = 0; /* Input text */
+ int nAlloc = 0; /* Number of bytes allocated for zIn[] */
+ int nIn = 0; /* Number of bytes of zIn[] used */
+ size_t got; /* Bytes read from input */
+ int rc = SQLITE_OK; /* Result codes from API functions */
+ int i; /* Loop counter */
+ int iNext; /* Next block of SQL */
+ sqlite3 *db; /* Open database */
+ char *zErrMsg = 0; /* Error message returned from sqlite3_exec() */
+ const char *zEncoding = 0; /* --utf16be or --utf16le */
+ int nHeap = 0, mnHeap = 0; /* Heap size from --heap */
+ int nLook = 0, szLook = 0; /* --lookaside configuration */
+ int nPCache = 0, szPCache = 0;/* --pcache configuration */
+ int nScratch = 0, szScratch=0;/* --scratch configuration */
+ int pageSize = 0; /* Desired page size. 0 means default */
+ void *pHeap = 0; /* Allocated heap space */
+ void *pLook = 0; /* Allocated lookaside space */
+ void *pPCache = 0; /* Allocated storage for pcache */
+ void *pScratch = 0; /* Allocated storage for scratch */
+ int doAutovac = 0; /* True for --autovacuum */
+ char *zSql; /* SQL to run */
+ char *zToFree = 0; /* Call sqlite3_free() on this afte running zSql */
+ int verboseFlag = 0; /* --verbose or -v flag */
+ int quietFlag = 0; /* --quiet or -q flag */
+ int nTest = 0; /* Number of test cases run */
+ int multiTest = 0; /* True if there will be multiple test cases */
+ int lastPct = -1; /* Previous percentage done output */
+ sqlite3 *dataDb = 0; /* Database holding compacted input data */
+ sqlite3_stmt *pStmt = 0; /* Statement to insert testcase into dataDb */
+ const char *zDataOut = 0; /* Write compacted data to this output file */
+ int nHeader = 0; /* Bytes of header comment text on input file */
+ int oomFlag = 0; /* --oom */
+ int oomCnt = 0; /* Counter for the OOM loop */
+ char zErrBuf[200]; /* Space for the error message */
+ const char *zFailCode; /* Value of the TEST_FAILURE environment var */
+ const char *zPrompt; /* Initial prompt when large-file fuzzing */
+ int nInFile = 0; /* Number of input files to read */
+ char **azInFile = 0; /* Array of input file names */
+ int jj; /* Loop counter for azInFile[] */
+ sqlite3_int64 iBegin; /* Start time for the whole program */
+ sqlite3_int64 iStart, iEnd; /* Start and end-times for a test case */
+ const char *zDbName = 0; /* Name of an on-disk database file to open */
+
+ iBegin = timeOfDay();
+ sqlite3_shutdown();
+ zFailCode = getenv("TEST_FAILURE");
+ g.zArgv0 = argv[0];
+ zPrompt = "<stdin>";
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"autovacuum")==0 ){
+ doAutovac = 1;
+ }else
+ if( strcmp(z,"database")==0 ){
+ if( i>=argc-1 ) abendError("missing argument on %s\n", argv[i]);
+ zDbName = argv[i+1];
+ i += 1;
+ }else
+ if( strcmp(z,"disable-lookaside")==0 ){
+ nLook = 1;
+ szLook = 0;
+ }else
+ if( strcmp(z, "f")==0 && i+1<argc ){
+ i++;
+ goto addNewInFile;
+ }else
+ if( strcmp(z,"heap")==0 ){
+ if( i>=argc-2 ) abendError("missing arguments on %s\n", argv[i]);
+ nHeap = integerValue(argv[i+1]);
+ mnHeap = integerValue(argv[i+2]);
+ i += 2;
+ }else
+ if( strcmp(z,"help")==0 ){
+ showHelp();
+ return 0;
+ }else
+ if( strcmp(z,"lookaside")==0 ){
+ if( i>=argc-2 ) abendError("missing arguments on %s", argv[i]);
+ nLook = integerValue(argv[i+1]);
+ szLook = integerValue(argv[i+2]);
+ i += 2;
+ }else
+ if( strcmp(z,"oom")==0 ){
+ oomFlag = 1;
+ }else
+ if( strcmp(z,"pagesize")==0 ){
+ if( i>=argc-1 ) abendError("missing argument on %s", argv[i]);
+ pageSize = integerValue(argv[++i]);
+ }else
+ if( strcmp(z,"pcache")==0 ){
+ if( i>=argc-2 ) abendError("missing arguments on %s", argv[i]);
+ nPCache = integerValue(argv[i+1]);
+ szPCache = integerValue(argv[i+2]);
+ i += 2;
+ }else
+ if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
+ quietFlag = 1;
+ verboseFlag = 0;
+ }else
+ if( strcmp(z,"scratch")==0 ){
+ if( i>=argc-2 ) abendError("missing arguments on %s", argv[i]);
+ nScratch = integerValue(argv[i+1]);
+ szScratch = integerValue(argv[i+2]);
+ i += 2;
+ }else
+ if( strcmp(z, "unique-cases")==0 ){
+ if( i>=argc-1 ) abendError("missing arguments on %s", argv[i]);
+ if( zDataOut ) abendError("only one --minimize allowed");
+ zDataOut = argv[++i];
+ }else
+ if( strcmp(z,"utf16le")==0 ){
+ zEncoding = "utf16le";
+ }else
+ if( strcmp(z,"utf16be")==0 ){
+ zEncoding = "utf16be";
+ }else
+ if( strcmp(z,"verbose")==0 || strcmp(z,"v")==0 ){
+ quietFlag = 0;
+ verboseFlag = 1;
+ }else
+ {
+ abendError("unknown option: %s", argv[i]);
+ }
+ }else{
+ addNewInFile:
+ nInFile++;
+ azInFile = realloc(azInFile, sizeof(azInFile[0])*nInFile);
+ if( azInFile==0 ) abendError("out of memory");
+ azInFile[nInFile-1] = argv[i];
+ }
+ }
+
+ /* Do global SQLite initialization */
+ sqlite3_config(SQLITE_CONFIG_LOG, verboseFlag ? shellLog : shellLogNoop, 0);
+ if( nHeap>0 ){
+ pHeap = malloc( nHeap );
+ if( pHeap==0 ) fatalError("cannot allocate %d-byte heap\n", nHeap);
+ rc = sqlite3_config(SQLITE_CONFIG_HEAP, pHeap, nHeap, mnHeap);
+ if( rc ) abendError("heap configuration failed: %d\n", rc);
+ }
+ if( oomFlag ){
+ sqlite3_config(SQLITE_CONFIG_GETMALLOC, &g.sOrigMem);
+ g.sOomMem = g.sOrigMem;
+ g.sOomMem.xMalloc = oomMalloc;
+ g.sOomMem.xRealloc = oomRealloc;
+ sqlite3_config(SQLITE_CONFIG_MALLOC, &g.sOomMem);
+ }
+ if( nLook>0 ){
+ sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0);
+ if( szLook>0 ){
+ pLook = malloc( nLook*szLook );
+ if( pLook==0 ) fatalError("out of memory");
+ }
+ }
+ if( nScratch>0 && szScratch>0 ){
+ pScratch = malloc( nScratch*(sqlite3_int64)szScratch );
+ if( pScratch==0 ) fatalError("cannot allocate %lld-byte scratch",
+ nScratch*(sqlite3_int64)szScratch);
+ rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, pScratch, szScratch, nScratch);
+ if( rc ) abendError("scratch configuration failed: %d\n", rc);
+ }
+ if( nPCache>0 && szPCache>0 ){
+ pPCache = malloc( nPCache*(sqlite3_int64)szPCache );
+ if( pPCache==0 ) fatalError("cannot allocate %lld-byte pcache",
+ nPCache*(sqlite3_int64)szPCache);
+ rc = sqlite3_config(SQLITE_CONFIG_PAGECACHE, pPCache, szPCache, nPCache);
+ if( rc ) abendError("pcache configuration failed: %d", rc);
+ }
+
+ /* If the --unique-cases option was supplied, open the database that will
+ ** be used to gather unique test cases.
+ */
+ if( zDataOut ){
+ rc = sqlite3_open(":memory:", &dataDb);
+ if( rc ) abendError("cannot open :memory: database");
+ rc = sqlite3_exec(dataDb,
+ "CREATE TABLE testcase(sql BLOB PRIMARY KEY, tm) WITHOUT ROWID;",0,0,0);
+ if( rc ) abendError("%s", sqlite3_errmsg(dataDb));
+ rc = sqlite3_prepare_v2(dataDb,
+ "INSERT OR IGNORE INTO testcase(sql,tm)VALUES(?1,?2)",
+ -1, &pStmt, 0);
+ if( rc ) abendError("%s", sqlite3_errmsg(dataDb));
+ }
+
+ /* Initialize the input buffer used to hold SQL text */
+ if( nInFile==0 ) nInFile = 1;
+ nAlloc = 1000;
+ zIn = malloc(nAlloc);
+ if( zIn==0 ) fatalError("out of memory");
+
+ /* Loop over all input files */
+ for(jj=0; jj<nInFile; jj++){
+
+ /* Read the complete content of the next input file into zIn[] */
+ FILE *in;
+ if( azInFile ){
+ int j, k;
+ in = fopen(azInFile[jj],"rb");
+ if( in==0 ){
+ abendError("cannot open %s for reading", azInFile[jj]);
+ }
+ zPrompt = azInFile[jj];
+ for(j=k=0; zPrompt[j]; j++) if( zPrompt[j]=='/' ) k = j+1;
+ zPrompt += k;
+ }else{
+ in = stdin;
+ zPrompt = "<stdin>";
+ }
+ while( !feof(in) ){
+ got = fread(zIn+nIn, 1, nAlloc-nIn-1, in);
+ nIn += (int)got;
+ zIn[nIn] = 0;
+ if( got==0 ) break;
+ if( nAlloc - nIn - 1 < 100 ){
+ nAlloc += nAlloc+1000;
+ zIn = realloc(zIn, nAlloc);
+ if( zIn==0 ) fatalError("out of memory");
+ }
+ }
+ if( in!=stdin ) fclose(in);
+ lastPct = -1;
+
+ /* Skip initial lines of the input file that begin with "#" */
+ for(i=0; i<nIn; i=iNext+1){
+ if( zIn[i]!='#' ) break;
+ for(iNext=i+1; iNext<nIn && zIn[iNext]!='\n'; iNext++){}
+ }
+ nHeader = i;
+
+ /* Process all test cases contained within the input file.
+ */
+ for(; i<nIn; i=iNext, nTest++, g.zTestName[0]=0){
+ char cSaved;
+ if( strncmp(&zIn[i], "/****<",6)==0 ){
+ char *z = strstr(&zIn[i], ">****/");
+ if( z ){
+ z += 6;
+ sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "%.*s",
+ (int)(z-&zIn[i]) - 12, &zIn[i+6]);
+ if( verboseFlag ){
+ printf("%.*s\n", (int)(z-&zIn[i]), &zIn[i]);
+ fflush(stdout);
+ }
+ i += (int)(z-&zIn[i]);
+ multiTest = 1;
+ }
+ }
+ for(iNext=i; iNext<nIn && strncmp(&zIn[iNext],"/****<",6)!=0; iNext++){}
+ cSaved = zIn[iNext];
+ zIn[iNext] = 0;
+
+
+ /* Print out the SQL of the next test case is --verbose is enabled
+ */
+ zSql = &zIn[i];
+ if( verboseFlag ){
+ printf("INPUT (offset: %d, size: %d): [%s]\n",
+ i, (int)strlen(&zIn[i]), &zIn[i]);
+ }else if( multiTest && !quietFlag ){
+ if( oomFlag ){
+ printf("%s\n", g.zTestName);
+ }else{
+ int pct = (10*iNext)/nIn;
+ if( pct!=lastPct ){
+ if( lastPct<0 ) printf("%s:", zPrompt);
+ printf(" %d%%", pct*10);
+ lastPct = pct;
+ }
+ }
+ }else if( nInFile>1 ){
+ printf("%s\n", zPrompt);
+ }
+ fflush(stdout);
+
+ /* Run the next test case. Run it multiple times in --oom mode
+ */
+ if( oomFlag ){
+ oomCnt = g.iOomCntdown = 1;
+ g.nOomFault = 0;
+ g.bOomOnce = 1;
+ if( verboseFlag ){
+ printf("Once.%d\n", oomCnt);
+ fflush(stdout);
+ }
+ }else{
+ oomCnt = 0;
+ }
+ do{
+ Str sql;
+ StrInit(&sql);
+ if( zDbName ){
+ rc = sqlite3_open_v2(zDbName, &db, SQLITE_OPEN_READWRITE, 0);
+ if( rc!=SQLITE_OK ){
+ abendError("Cannot open database file %s", zDbName);
+ }
+ }else{
+ rc = sqlite3_open_v2(
+ "main.db", &db,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY,
+ 0);
+ if( rc!=SQLITE_OK ){
+ abendError("Unable to open the in-memory database");
+ }
+ }
+ if( pLook ){
+ rc = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE,pLook,szLook,nLook);
+ if( rc!=SQLITE_OK ) abendError("lookaside configuration filed: %d", rc);
+ }
+ #ifndef SQLITE_OMIT_TRACE
+ sqlite3_trace(db, verboseFlag ? traceCallback : traceNoop, 0);
+ #endif
+ sqlite3_create_function(db, "eval", 1, SQLITE_UTF8, 0, sqlEvalFunc, 0, 0);
+ sqlite3_create_function(db, "eval", 2, SQLITE_UTF8, 0, sqlEvalFunc, 0, 0);
+ sqlite3_create_module(db, "generate_series", &seriesModule, 0);
+ sqlite3_limit(db, SQLITE_LIMIT_LENGTH, 1000000);
+ if( zEncoding ) sqlexec(db, "PRAGMA encoding=%s", zEncoding);
+ if( pageSize ) sqlexec(db, "PRAGMA pagesize=%d", pageSize);
+ if( doAutovac ) sqlexec(db, "PRAGMA auto_vacuum=FULL");
+ iStart = timeOfDay();
+
+ /* If using an input database file and that database contains a table
+ ** named "autoexec" with a column "sql", then replace the input SQL
+ ** with the concatenated text of the autoexec table. In this way,
+ ** if the database file is the input being fuzzed, the SQL text is
+ ** fuzzed at the same time. */
+ if( sqlite3_table_column_metadata(db,0,"autoexec","sql",0,0,0,0,0)==0 ){
+ sqlite3_stmt *pStmt2;
+ rc = sqlite3_prepare_v2(db,"SELECT sql FROM autoexec",-1,&pStmt2,0);
+ if( rc==SQLITE_OK ){
+ while( sqlite3_step(pStmt2)==SQLITE_ROW ){
+ StrAppend(&sql, (const char*)sqlite3_column_text(pStmt2, 0));
+ StrAppend(&sql, "\n");
+ }
+ }
+ sqlite3_finalize(pStmt2);
+ zSql = StrStr(&sql);
+ }
+
+ g.bOomEnable = 1;
+ if( verboseFlag ){
+ zErrMsg = 0;
+ rc = sqlite3_exec(db, zSql, execCallback, 0, &zErrMsg);
+ if( zErrMsg ){
+ sqlite3_snprintf(sizeof(zErrBuf),zErrBuf,"%z", zErrMsg);
+ zErrMsg = 0;
+ }
+ }else {
+ rc = sqlite3_exec(db, zSql, execNoop, 0, 0);
+ }
+ g.bOomEnable = 0;
+ iEnd = timeOfDay();
+ StrFree(&sql);
+ rc = sqlite3_close(db);
+ if( rc ){
+ abendError("sqlite3_close() failed with rc=%d", rc);
+ }
+ if( !zDataOut && sqlite3_memory_used()>0 ){
+ abendError("memory in use after close: %lld bytes",sqlite3_memory_used());
+ }
+ if( oomFlag ){
+ /* Limit the number of iterations of the OOM loop to OOM_MAX. If the
+ ** first pass (single failure) exceeds 2/3rds of OOM_MAX this skip the
+ ** second pass (continuous failure after first) completely. */
+ if( g.nOomFault==0 || oomCnt>OOM_MAX ){
+ if( g.bOomOnce && oomCnt<=(OOM_MAX*2/3) ){
+ oomCnt = g.iOomCntdown = 1;
+ g.bOomOnce = 0;
+ }else{
+ oomCnt = 0;
+ }
+ }else{
+ g.iOomCntdown = ++oomCnt;
+ g.nOomFault = 0;
+ }
+ if( oomCnt ){
+ if( verboseFlag ){
+ printf("%s.%d\n", g.bOomOnce ? "Once" : "Multi", oomCnt);
+ fflush(stdout);
+ }
+ nTest++;
+ }
+ }
+ }while( oomCnt>0 );
+
+ /* Store unique test cases in the in the dataDb database if the
+ ** --unique-cases flag is present
+ */
+ if( zDataOut ){
+ sqlite3_bind_blob(pStmt, 1, &zIn[i], iNext-i, SQLITE_STATIC);
+ sqlite3_bind_int64(pStmt, 2, iEnd - iStart);
+ rc = sqlite3_step(pStmt);
+ if( rc!=SQLITE_DONE ) abendError("%s", sqlite3_errmsg(dataDb));
+ sqlite3_reset(pStmt);
+ }
+
+ /* Free the SQL from the current test case
+ */
+ if( zToFree ){
+ sqlite3_free(zToFree);
+ zToFree = 0;
+ }
+ zIn[iNext] = cSaved;
+
+ /* Show test-case results in --verbose mode
+ */
+ if( verboseFlag ){
+ printf("RESULT-CODE: %d\n", rc);
+ if( zErrMsg ){
+ printf("ERROR-MSG: [%s]\n", zErrBuf);
+ }
+ fflush(stdout);
+ }
+
+ /* Simulate an error if the TEST_FAILURE environment variable is "5".
+ ** This is used to verify that automated test script really do spot
+ ** errors that occur in this test program.
+ */
+ if( zFailCode ){
+ if( zFailCode[0]=='5' && zFailCode[1]==0 ){
+ abendError("simulated failure");
+ }else if( zFailCode[0]!=0 ){
+ /* If TEST_FAILURE is something other than 5, just exit the test
+ ** early */
+ printf("\nExit early due to TEST_FAILURE being set");
+ break;
+ }
+ }
+ }
+ if( !verboseFlag && multiTest && !quietFlag && !oomFlag ) printf("\n");
+ }
+
+ /* Report total number of tests run
+ */
+ if( nTest>1 && !quietFlag ){
+ sqlite3_int64 iElapse = timeOfDay() - iBegin;
+ printf("%s: 0 errors out of %d tests in %d.%03d seconds\nSQLite %s %s\n",
+ g.zArgv0, nTest, (int)(iElapse/1000), (int)(iElapse%1000),
+ sqlite3_libversion(), sqlite3_sourceid());
+ }
+
+ /* Write the unique test cases if the --unique-cases flag was used
+ */
+ if( zDataOut ){
+ int n = 0;
+ FILE *out = fopen(zDataOut, "wb");
+ if( out==0 ) abendError("cannot open %s for writing", zDataOut);
+ if( nHeader>0 ) fwrite(zIn, nHeader, 1, out);
+ sqlite3_finalize(pStmt);
+ rc = sqlite3_prepare_v2(dataDb, "SELECT sql, tm FROM testcase ORDER BY tm, sql",
+ -1, &pStmt, 0);
+ if( rc ) abendError("%s", sqlite3_errmsg(dataDb));
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ fprintf(out,"/****<%d:%dms>****/", ++n, sqlite3_column_int(pStmt,1));
+ fwrite(sqlite3_column_blob(pStmt,0),sqlite3_column_bytes(pStmt,0),1,out);
+ }
+ fclose(out);
+ sqlite3_finalize(pStmt);
+ sqlite3_close(dataDb);
+ }
+
+ /* Clean up and exit.
+ */
+ free(azInFile);
+ free(zIn);
+ free(pHeap);
+ free(pLook);
+ free(pScratch);
+ free(pPCache);
+ return 0;
+}
diff --git a/tool/genfkey.README b/tool/genfkey.README
new file mode 100644
index 0000000..57cdff8
--- /dev/null
+++ b/tool/genfkey.README
@@ -0,0 +1,137 @@
+
+OVERVIEW
+
+ The SQLite library is capable of parsing SQL foreign key constraints
+ supplied as part of CREATE TABLE statements, but it does not actually
+ implement them. However, most of the features of foreign keys may be
+ implemented using SQL triggers, which SQLite does support. This text
+ file describes a feature of the SQLite shell tool (sqlite3) that
+ extracts foreign key definitions from an existing SQLite database and
+ creates the set of CREATE TRIGGER statements required to implement
+ the foreign key constraints.
+
+CAPABILITIES
+
+ An SQL foreign key is a constraint that requires that each row in
+ the "child" table corresponds to a row in the "parent" table. For
+ example, the following schema:
+
+ CREATE TABLE parent(a, b, c, PRIMARY KEY(a, b));
+ CREATE TABLE child(d, e, f, FOREIGN KEY(d, e) REFERENCES parent(a, b));
+
+ implies that for each row in table "child", there must be a row in
+ "parent" for which the expression (child.d==parent.a AND child.e==parent.b)
+ is true. The columns in the parent table are required to be either the
+ primary key columns or subject to a UNIQUE constraint. There is no such
+ requirement for the columns of the child table.
+
+ At this time, all foreign keys are implemented as if they were
+ "MATCH NONE", even if the declaration specified "MATCH PARTIAL" or
+ "MATCH FULL". "MATCH NONE" means that if any of the key columns in
+ the child table are NULL, then there is no requirement for a corresponding
+ row in the parent table. So, taking this into account, the expression that
+ must be true for every row of the child table in the above example is
+ actually:
+
+ (child.d IS NULL) OR
+ (child.e IS NULL) OR
+ (child.d==parent.a AND child.e==parent.b)
+
+ Attempting to insert or update a row in the child table so that the
+ affected row violates this constraint results in an exception being
+ thrown.
+
+ The effect of attempting to delete or update a row in the parent table
+ so that the constraint becomes untrue for one or more rows in the child
+ table depends on the "ON DELETE" or "ON UPDATE" actions specified as
+ part of the foreign key definition, respectively. Three different actions
+ are supported: "RESTRICT" (the default), "CASCADE" and "SET NULL". SQLite
+ will also parse the "SET DEFAULT" action, but this is not implemented
+ and "RESTRICT" is used instead.
+
+ RESTRICT: Attempting to update or delete a row in the parent table so
+ that the constraint becomes untrue for one or more rows in
+ the child table is not allowed. An exception is thrown.
+
+ CASCADE: Instead of throwing an exception, all corresponding child table
+ rows are either deleted (if the parent row is being deleted)
+ or updated to match the new parent key values (if the parent
+ row is being updated).
+
+ SET NULL: Instead of throwing an exception, the foreign key fields of
+ all corresponding child table rows are set to NULL.
+
+LIMITATIONS
+
+ Apart from those limitiations described above:
+
+ * Implicit mapping to composite primary keys is not supported. If
+ a parent table has a composite primary key, then any child table
+ that refers to it must explicitly map each column. For example, given
+ the following definition of table "parent":
+
+ CREATE TABLE parent(a, b, c, PRIMARY KEY(a, b));
+
+ only the first of the following two definitions of table "child"
+ is supported:
+
+ CREATE TABLE child(d, e, f, FOREIGN KEY(d, e) REFERENCES parent(a, b));
+ CREATE TABLE child(d, e, f, FOREIGN KEY(d, e) REFERENCES parent);
+
+ An implicit reference to a composite primary key is detected as an
+ error when the program is run (see below).
+
+ * SQLite does not support recursive triggers, and therefore this program
+ does not support recursive CASCADE or SET NULL foreign key
+ relationships. If the parent and the child tables of a CASCADE or
+ SET NULL foreign key are the same table, the generated triggers will
+ malfunction. This is also true if the recursive foreign key constraint
+ is indirect (for example if table A references table B which references
+ table A with a CASCADE or SET NULL foreign key constraint).
+
+ Recursive CASCADE or SET NULL foreign key relationships are *not*
+ detected as errors when the program is run. Buyer beware.
+
+USAGE
+
+ The functionality is accessed through an sqlite3 shell tool "dot-command":
+
+ .genfkey ?--no-drop? ?--ignore-errors? ?--exec?
+
+ When this command is run, it first checks the schema of the open SQLite
+ database for foreign key related errors or inconsistencies. For example,
+ a foreign key that refers to a parent table that does not exist, or
+ a foreign key that refers to columns in a parent table that are not
+ guaranteed to be unique. If such errors are found and the --ignore-errors
+ option was not present, a message for each one is printed to stderr and
+ no further processing takes place.
+
+ If errors are found and the --ignore-errors option is passed, then
+ no error messages are printed. No "CREATE TRIGGER" statements are generated
+ for foriegn-key definitions that contained errors, they are silently
+ ignored by subsequent processing.
+
+ All triggers generated by this command have names that match the pattern
+ "genfkey*". Unless the --no-drop option is specified, then the program
+ also generates a "DROP TRIGGER" statement for each trigger that exists
+ in the database with a name that matches this pattern. This allows the
+ program to be used to upgrade a database schema for which foreign key
+ triggers have already been installed (i.e. after new tables are created
+ or existing tables dropped).
+
+ Finally, a series of SQL trigger definitions (CREATE TRIGGER statements)
+ that implement the foreign key constraints found in the database schema are
+ generated.
+
+ If the --exec option was passed, then all generated SQL is immediately
+ executed on the database. Otherwise, the generated SQL strings are output
+ in the same way as the results of SELECT queries are. Normally, this means
+ they will be printed to stdout, but this can be configured using other
+ dot-commands (i.e. ".output").
+
+ The simplest way to activate the foriegn key definitions in a database
+ is simply to open it using the shell tool and enter the command
+ ".genfkey --exec":
+
+ sqlite> .genfkey --exec
+
diff --git a/tool/genfkey.test b/tool/genfkey.test
new file mode 100644
index 0000000..16c67bb
--- /dev/null
+++ b/tool/genfkey.test
@@ -0,0 +1,353 @@
+
+package require sqlite3
+
+proc do_test {name cmd expected} {
+ puts -nonewline "$name ..."
+ set res [uplevel $cmd]
+ if {$res eq $expected} {
+ puts Ok
+ } else {
+ puts Error
+ puts " Got: $res"
+ puts " Expected: $expected"
+ exit
+ }
+}
+
+proc execsql {sql} {
+ uplevel [list db eval $sql]
+}
+
+proc catchsql {sql} {
+ set rc [catch {uplevel [list db eval $sql]} msg]
+ list $rc $msg
+}
+
+file delete -force test.db test.db.journal
+sqlite3 db test.db
+
+# The following tests - genfkey-1.* - test RESTRICT foreign keys.
+#
+do_test genfkey-1.1 {
+ execsql {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(b, c));
+ CREATE TABLE t2(e REFERENCES t1, f);
+ CREATE TABLE t3(g, h, i, FOREIGN KEY (h, i) REFERENCES t1(b, c));
+ }
+} {}
+do_test genfkey-1.2 {
+ execsql [exec ./sqlite3 test.db .genfkey]
+} {}
+do_test genfkey-1.3 {
+ catchsql { INSERT INTO t2 VALUES(1, 2) }
+} {1 {constraint failed}}
+do_test genfkey-1.4 {
+ execsql {
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t2 VALUES(1, 2);
+ }
+} {}
+do_test genfkey-1.5 {
+ execsql { INSERT INTO t2 VALUES(NULL, 3) }
+} {}
+do_test genfkey-1.6 {
+ catchsql { UPDATE t2 SET e = 5 WHERE e IS NULL }
+} {1 {constraint failed}}
+do_test genfkey-1.7 {
+ execsql { UPDATE t2 SET e = 1 WHERE e IS NULL }
+} {}
+do_test genfkey-1.8 {
+ execsql { UPDATE t2 SET e = NULL WHERE f = 3 }
+} {}
+do_test genfkey-1.9 {
+ catchsql { UPDATE t1 SET a = 10 }
+} {1 {constraint failed}}
+do_test genfkey-1.9a {
+ catchsql { UPDATE t1 SET a = NULL }
+} {1 {datatype mismatch}}
+do_test genfkey-1.10 {
+ catchsql { DELETE FROM t1 }
+} {1 {constraint failed}}
+do_test genfkey-1.11 {
+ execsql { UPDATE t2 SET e = NULL }
+} {}
+do_test genfkey-1.12 {
+ execsql {
+ UPDATE t1 SET a = 10 ;
+ DELETE FROM t1;
+ DELETE FROM t2;
+ }
+} {}
+
+do_test genfkey-1.13 {
+ execsql {
+ INSERT INTO t3 VALUES(1, NULL, NULL);
+ INSERT INTO t3 VALUES(1, 2, NULL);
+ INSERT INTO t3 VALUES(1, NULL, 3);
+ }
+} {}
+do_test genfkey-1.14 {
+ catchsql { INSERT INTO t3 VALUES(3, 1, 4) }
+} {1 {constraint failed}}
+do_test genfkey-1.15 {
+ execsql {
+ INSERT INTO t1 VALUES(1, 1, 4);
+ INSERT INTO t3 VALUES(3, 1, 4);
+ }
+} {}
+do_test genfkey-1.16 {
+ catchsql { DELETE FROM t1 }
+} {1 {constraint failed}}
+do_test genfkey-1.17 {
+ catchsql { UPDATE t1 SET b = 10}
+} {1 {constraint failed}}
+do_test genfkey-1.18 {
+ execsql { UPDATE t1 SET a = 10}
+} {}
+do_test genfkey-1.19 {
+ catchsql { UPDATE t3 SET h = 'hello' WHERE i = 3}
+} {1 {constraint failed}}
+
+do_test genfkey-1.X {
+ execsql {
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ }
+} {}
+
+# The following tests - genfkey-2.* - test CASCADE foreign keys.
+#
+do_test genfkey-2.1 {
+ execsql {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(b, c));
+ CREATE TABLE t2(e REFERENCES t1 ON UPDATE CASCADE ON DELETE CASCADE, f);
+ CREATE TABLE t3(g, h, i,
+ FOREIGN KEY (h, i)
+ REFERENCES t1(b, c) ON UPDATE CASCADE ON DELETE CASCADE
+ );
+ }
+} {}
+do_test genfkey-2.2 {
+ execsql [exec ./sqlite3 test.db .genfkey]
+} {}
+do_test genfkey-2.3 {
+ execsql {
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ INSERT INTO t2 VALUES(1, 'one');
+ INSERT INTO t2 VALUES(4, 'four');
+ }
+} {}
+do_test genfkey-2.4 {
+ execsql {
+ UPDATE t1 SET a = 2 WHERE a = 1;
+ SELECT * FROM t2;
+ }
+} {2 one 4 four}
+do_test genfkey-2.5 {
+ execsql {
+ DELETE FROM t1 WHERE a = 4;
+ SELECT * FROM t2;
+ }
+} {2 one}
+do_test genfkey-2.6 {
+ execsql {
+ INSERT INTO t3 VALUES('hello', 2, 3);
+ UPDATE t1 SET c = 2;
+ SELECT * FROM t3;
+ }
+} {hello 2 2}
+do_test genfkey-2.7 {
+ execsql {
+ DELETE FROM t1;
+ SELECT * FROM t3;
+ }
+} {}
+do_test genfkey-2.X {
+ execsql {
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ }
+} {}
+
+
+# The following tests - genfkey-3.* - test SET NULL foreign keys.
+#
+do_test genfkey-3.1 {
+ execsql {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(c, b));
+ CREATE TABLE t2(e REFERENCES t1 ON UPDATE SET NULL ON DELETE SET NULL, f);
+ CREATE TABLE t3(g, h, i,
+ FOREIGN KEY (h, i)
+ REFERENCES t1(b, c) ON UPDATE SET NULL ON DELETE SET NULL
+ );
+ }
+} {}
+do_test genfkey-3.2 {
+ execsql [exec ./sqlite3 test.db .genfkey]
+} {}
+do_test genfkey-3.3 {
+ execsql {
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ INSERT INTO t2 VALUES(1, 'one');
+ INSERT INTO t2 VALUES(4, 'four');
+ }
+} {}
+do_test genfkey-3.4 {
+ execsql {
+ UPDATE t1 SET a = 2 WHERE a = 1;
+ SELECT * FROM t2;
+ }
+} {{} one 4 four}
+do_test genfkey-3.5 {
+ execsql {
+ DELETE FROM t1 WHERE a = 4;
+ SELECT * FROM t2;
+ }
+} {{} one {} four}
+do_test genfkey-3.6 {
+ execsql {
+ INSERT INTO t3 VALUES('hello', 2, 3);
+ UPDATE t1 SET c = 2;
+ SELECT * FROM t3;
+ }
+} {hello {} {}}
+do_test genfkey-2.7 {
+ execsql {
+ UPDATE t3 SET h = 2, i = 2;
+ DELETE FROM t1;
+ SELECT * FROM t3;
+ }
+} {hello {} {}}
+do_test genfkey-3.X {
+ execsql {
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ }
+} {}
+
+# The following tests - genfkey-4.* - test that errors in the schema
+# are detected correctly.
+#
+do_test genfkey-4.1 {
+ execsql {
+ CREATE TABLE t1(a REFERENCES nosuchtable, b);
+ CREATE TABLE t2(a REFERENCES t1, b);
+
+ CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
+ CREATE TABLE t4(a, b, c, FOREIGN KEY(c, b) REFERENCES t3);
+
+ CREATE TABLE t5(a REFERENCES t4(d), b, c);
+ CREATE TABLE t6(a REFERENCES t4(a), b, c);
+ CREATE TABLE t7(a REFERENCES t3(a), b, c);
+ CREATE TABLE t8(a REFERENCES nosuchtable(a), b, c);
+ }
+} {}
+
+do_test genfkey-4.X {
+ set rc [catch {exec ./sqlite3 test.db .genfkey} msg]
+ list $rc $msg
+} "1 {[string trim {
+Error in table t5: foreign key columns do not exist
+Error in table t8: foreign key columns do not exist
+Error in table t4: implicit mapping to composite primary key
+Error in table t1: implicit mapping to non-existant primary key
+Error in table t2: implicit mapping to non-existant primary key
+Error in table t6: foreign key is not unique
+Error in table t7: foreign key is not unique
+}]}"
+
+# Test that ticket #3800 has been resolved.
+#
+do_test genfkey-5.1 {
+ execsql {
+ DROP TABLE t1; DROP TABLE t2; DROP TABLE t3;
+ DROP TABLE t4; DROP TABLE t5; DROP TABLE t6;
+ DROP TABLE t7; DROP TABLE t8;
+ }
+} {}
+do_test genfkey-5.2 {
+ execsql {
+ CREATE TABLE "t.3" (c1 PRIMARY KEY);
+ CREATE TABLE t13 (c1, foreign key(c1) references "t.3"(c1));
+ }
+} {}
+do_test genfkey-5.3 {
+ set rc [catch {exec ./sqlite3 test.db .genfkey} msg]
+} {0}
+do_test genfkey-5.4 {
+ db eval $msg
+} {}
+do_test genfkey-5.5 {
+ catchsql { INSERT INTO t13 VALUES(1) }
+} {1 {constraint failed}}
+do_test genfkey-5.5 {
+ catchsql {
+ INSERT INTO "t.3" VALUES(1);
+ INSERT INTO t13 VALUES(1);
+ }
+} {0 {}}
+
+# Test also column names that require quoting.
+do_test genfkey-6.1 {
+ execsql {
+ DROP TABLE "t.3";
+ DROP TABLE t13;
+ CREATE TABLE p(
+ "a.1 first", "b.2 second",
+ UNIQUE("a.1 first", "b.2 second")
+ );
+ CREATE TABLE c(
+ "c.1 I", "d.2 II",
+ FOREIGN KEY("c.1 I", "d.2 II")
+ REFERENCES p("a.1 first", "b.2 second")
+ ON UPDATE CASCADE ON DELETE CASCADE
+ );
+ }
+} {}
+do_test genfkey-6.2 {
+ set rc [catch {exec ./sqlite3 test.db .genfkey} msg]
+} {0}
+do_test genfkey-6.3 {
+ execsql $msg
+ execsql {
+ INSERT INTO p VALUES('A', 'B');
+ INSERT INTO p VALUES('C', 'D');
+ INSERT INTO c VALUES('A', 'B');
+ INSERT INTO c VALUES('C', 'D');
+ UPDATE p SET "a.1 first" = 'X' WHERE rowid = 1;
+ DELETE FROM p WHERE rowid = 2;
+ }
+ execsql { SELECT * FROM c }
+} {X B}
+
+do_test genfkey-6.4 {
+ execsql {
+ DROP TABLE p;
+ DROP TABLE c;
+ CREATE TABLE parent("a.1", PRIMARY KEY("a.1"));
+ CREATE TABLE child("b.2", FOREIGN KEY("b.2") REFERENCES parent("a.1"));
+ }
+ set rc [catch {exec ./sqlite3 test.db .genfkey} msg]
+} {0}
+do_test genfkey-6.5 {
+ execsql $msg
+ execsql {
+ INSERT INTO parent VALUES(1);
+ INSERT INTO child VALUES(1);
+ }
+ catchsql { UPDATE parent SET "a.1"=0 }
+} {1 {constraint failed}}
+do_test genfkey-6.6 {
+ catchsql { UPDATE child SET "b.2"=7 }
+} {1 {constraint failed}}
+do_test genfkey-6.7 {
+ execsql {
+ SELECT * FROM parent;
+ SELECT * FROM child;
+ }
+} {1 1}
diff --git a/tool/getlock.c b/tool/getlock.c
new file mode 100644
index 0000000..7eff04d
--- /dev/null
+++ b/tool/getlock.c
@@ -0,0 +1,134 @@
+/*
+** This utility program looks at an SQLite database and determines whether
+** or not it is locked, the kind of lock, and who is holding this lock.
+**
+** This only works on unix when the posix advisory locking method is used
+** (which is the default on unix) and when the PENDING_BYTE is in its
+** usual place.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+static void usage(const char *argv0){
+ fprintf(stderr, "Usage: %s database\n", argv0);
+ exit(1);
+}
+
+/* Check for a conflicting lock. If one is found, print an this
+** on standard output using the format string given and return 1.
+** If there are no conflicting locks, return 0.
+*/
+static int isLocked(
+ int h, /* File descriptor to check */
+ int type, /* F_RDLCK or F_WRLCK */
+ unsigned int iOfst, /* First byte of the lock */
+ unsigned int iCnt, /* Number of bytes in the lock range */
+ const char *zType /* Type of lock */
+){
+ struct flock lk;
+
+ memset(&lk, 0, sizeof(lk));
+ lk.l_type = type;
+ lk.l_whence = SEEK_SET;
+ lk.l_start = iOfst;
+ lk.l_len = iCnt;
+ if( fcntl(h, F_GETLK, &lk)==(-1) ){
+ fprintf(stderr, "fcntl(%d) failed: errno=%d\n", h, errno);
+ exit(1);
+ }
+ if( lk.l_type==F_UNLCK ) return 0;
+ printf("%s lock held by %d\n", zType, (int)lk.l_pid);
+ return 1;
+}
+
+/*
+** Location of locking bytes in the database file
+*/
+#define PENDING_BYTE (0x40000000)
+#define RESERVED_BYTE (PENDING_BYTE+1)
+#define SHARED_FIRST (PENDING_BYTE+2)
+#define SHARED_SIZE 510
+
+/*
+** Lock locations for shared-memory locks used by WAL mode.
+*/
+#define SHM_BASE 120
+#define SHM_WRITE SHM_BASE
+#define SHM_CHECKPOINT (SHM_BASE+1)
+#define SHM_RECOVER (SHM_BASE+2)
+#define SHM_READ_FIRST (SHM_BASE+3)
+#define SHM_READ_SIZE 5
+
+
+int main(int argc, char **argv){
+ int hDb; /* File descriptor for the open database file */
+ int hShm; /* File descriptor for WAL shared-memory file */
+ char *zShm; /* Name of the shared-memory file for WAL mode */
+ ssize_t got; /* Bytes read from header */
+ int isWal; /* True if in WAL mode */
+ int nName; /* Length of filename */
+ unsigned char aHdr[100]; /* Database header */
+ int nLock = 0; /* Number of locks held */
+ int i; /* Loop counter */
+
+ if( argc!=2 ) usage(argv[0]);
+ hDb = open(argv[1], O_RDONLY, 0);
+ if( hDb<0 ){
+ fprintf(stderr, "cannot open %s\n", argv[1]);
+ return 1;
+ }
+
+ /* Make sure we are dealing with an database file */
+ got = read(hDb, aHdr, 100);
+ if( got!=100 || memcmp(aHdr, "SQLite format 3",16)!=0 ){
+ fprintf(stderr, "not an SQLite database: %s\n", argv[1]);
+ exit(1);
+ }
+
+ /* First check for an exclusive lock */
+ if( isLocked(hDb, F_RDLCK, SHARED_FIRST, SHARED_SIZE, "EXCLUSIVE") ){
+ return 0;
+ }
+ isWal = aHdr[18]==2;
+ if( isWal==0 ){
+ /* Rollback mode */
+ if( isLocked(hDb, F_RDLCK, PENDING_BYTE, 1, "PENDING") ) return 0;
+ if( isLocked(hDb, F_RDLCK, RESERVED_BYTE, 1, "RESERVED") ) return 0;
+ if( isLocked(hDb, F_WRLCK, SHARED_FIRST, SHARED_SIZE, "SHARED") ){
+ return 0;
+ }
+ }else{
+ /* WAL mode */
+ nName = (int)strlen(argv[1]);
+ zShm = malloc( nName + 100 );
+ if( zShm==0 ){
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ memcpy(zShm, argv[1], nName);
+ memcpy(&zShm[nName], "-shm", 5);
+ hShm = open(zShm, O_RDONLY, 0);
+ if( hShm<0 ){
+ fprintf(stderr, "cannot open %s\n", zShm);
+ return 1;
+ }
+ if( isLocked(hShm, F_RDLCK, SHM_RECOVER, 1, "WAL-RECOVERY") ){
+ return 0;
+ }
+ nLock += isLocked(hShm, F_RDLCK, SHM_CHECKPOINT, 1, "WAL-CHECKPOINT");
+ nLock += isLocked(hShm, F_RDLCK, SHM_WRITE, 1, "WAL-WRITE");
+ for(i=0; i<SHM_READ_SIZE; i++){
+ nLock += isLocked(hShm, F_WRLCK, SHM_READ_FIRST+i, 1, "WAL-READ");
+ }
+ }
+ if( nLock==0 ){
+ printf("file is not locked\n");
+ }
+ return 0;
+}
diff --git a/tool/index_usage.c b/tool/index_usage.c
new file mode 100644
index 0000000..9bd3c9f
--- /dev/null
+++ b/tool/index_usage.c
@@ -0,0 +1,233 @@
+/*
+** 2018-12-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements a utility program used to help determine which
+** indexes in a database schema are used and unused, and how often specific
+** indexes are used.
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+static void usage(const char *argv0){
+ printf("Usage: %s [OPTIONS] DATABASE LOG\n\n", argv0);
+ printf(
+ "DATABASE is an SQLite database against which various statements\n"
+ "have been run. The SQL text is stored in LOG. LOG is an SQLite\n"
+ "database with this schema:\n"
+ "\n"
+ " CREATE TABLE sqllog(sql TEXT);\n"
+ "\n"
+ "This utility program analyzes statements contained in LOG and prints\n"
+ "a report showing how many times each index in DATABASE is used by the\n"
+ "statements in LOG.\n"
+ "\n"
+ "DATABASE only needs to contain the schema used by the statements in\n"
+ "LOG. The content can be removed from DATABASE.\n"
+ );
+ printf(
+ "\nOPTIONS:\n\n"
+ " --progress N Show a progress message after every N input rows\n"
+ " -q Omit error message when parsing log entries\n"
+ " --using NAME Print SQL statements that use index NAME\n"
+ );
+ printf("\nAnalysis will be done by SQLite version %s dated %.20s\n"
+ "checkin number %.40s. Different versions\n"
+ "of SQLite might use different indexes.\n",
+ sqlite3_libversion(), sqlite3_sourceid(), sqlite3_sourceid()+21);
+ exit(1);
+}
+
+int main(int argc, char **argv){
+ sqlite3 *db = 0; /* The main database */
+ sqlite3_stmt *pStmt = 0; /* a query */
+ char *zSql;
+ int nErr = 0;
+ int rc;
+ int bQuiet = 0;
+ int i, j;
+ const char *zUsing = 0;
+ sqlite3_stmt *pIncrCnt = 0;
+ int nRow = 0;
+ int iProgress = 0;
+
+ for(i=j=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"progress")==0 ){
+ if( i+1<argc ){
+ iProgress = strtol(argv[++i],0,0);
+ continue;
+ }
+ printf("The --progress option requires an argument\n");
+ exit(0);
+ }
+ if( strcmp(z,"q")==0 ){
+ bQuiet = 1;
+ continue;
+ }
+ if( strcmp(z,"using")==0 ){
+ if( i+1<argc ){
+ zUsing = argv[++i];
+ continue;
+ }
+ printf("The --using option requires an argument\n");
+ exit(0);
+ }
+ if( strcmp(z, "help")==0 || strcmp(z, "?")==0 ){
+ usage(argv[0]);
+ }
+ printf("Unknown command-line option: \"%s\"\n", argv[i]);
+ exit(0);
+ }else{
+ if( j<i ) argv[j++] = argv[i];
+ }
+ }
+ argc = j;
+
+ if( argc!=3 ) usage(argv[0]);
+ rc = sqlite3_open_v2(argv[1], &db, SQLITE_OPEN_READONLY, 0);
+ if( rc ){
+ printf("Cannot open \"%s\" for reading: %s\n", argv[1], sqlite3_errmsg(db));
+ goto errorOut;
+ }
+ rc = sqlite3_prepare_v2(db, "SELECT * FROM sqlite_schema", -1, &pStmt, 0);
+ if( rc ){
+ printf("Cannot read the schema from \"%s\" - %s\n", argv[1],
+ sqlite3_errmsg(db));
+ goto errorOut;
+ }
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ rc = sqlite3_exec(db,
+ "CREATE TABLE temp.idxu(\n"
+ " tbl TEXT COLLATE nocase,\n"
+ " idx TEXT COLLATE nocase,\n"
+ " cnt INT,\n"
+ " PRIMARY KEY(idx)\n"
+ ") WITHOUT ROWID;", 0, 0, 0);
+ if( rc ){
+ printf("Cannot create the result table - %s\n",
+ sqlite3_errmsg(db));
+ goto errorOut;
+ }
+ rc = sqlite3_exec(db,
+ "INSERT INTO temp.idxu(tbl,idx,cnt)"
+ " SELECT tbl_name, name, 0 FROM sqlite_schema"
+ " WHERE type='index' AND sql IS NOT NULL", 0, 0, 0);
+
+ /* Open the LOG database */
+ zSql = sqlite3_mprintf("ATTACH %Q AS log", argv[2]);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ printf("Cannot open the LOG database \"%s\" - %s\n",
+ argv[2], sqlite3_errmsg(db));
+ goto errorOut;
+ }
+ rc = sqlite3_prepare_v2(db,
+ "SELECT sql, rowid FROM log.sqllog"
+ " WHERE upper(substr(sql,1,5)) NOT IN ('BEGIN','COMMI','ROLLB','PRAGM')",
+ -1, &pStmt, 0);
+ if( rc ){
+ printf("Cannot read the SQLLOG table in the LOG database \"%s\" - %s\n",
+ argv[2], sqlite3_errmsg(db));
+ goto errorOut;
+ }
+
+ rc = sqlite3_prepare_v2(db,
+ "UPDATE temp.idxu SET cnt=cnt+1 WHERE idx=?1",
+ -1, &pIncrCnt, 0);
+ if( rc ){
+ printf("Cannot prepare a statement to increment a counter for "
+ "indexes used\n");
+ goto errorOut;
+ }
+
+ /* Update the counts based on LOG */
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zLog = (const char*)sqlite3_column_text(pStmt, 0);
+ sqlite3_stmt *pS2;
+ if( zLog==0 ) continue;
+ zSql = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zLog);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pS2, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ if( !bQuiet ){
+ printf("Cannot compile LOG entry %d (%s): %s\n",
+ sqlite3_column_int(pStmt, 1), zLog, sqlite3_errmsg(db));
+ fflush(stdout);
+ }
+ nErr++;
+ }else{
+ nRow++;
+ if( iProgress>0 && (nRow%iProgress)==0 ){
+ printf("%d...\n", nRow);
+ fflush(stdout);
+ }
+ while( sqlite3_step(pS2)==SQLITE_ROW ){
+ const char *zExplain = (const char*)sqlite3_column_text(pS2,3);
+ const char *z1, *z2;
+ int n;
+ /* printf("EXPLAIN: %s\n", zExplain); */
+ z1 = strstr(zExplain, " USING INDEX ");
+ if( z1==0 ) continue;
+ z1 += 13;
+ for(z2=z1+1; z2[0] && z2[1]!='('; z2++){}
+ n = z2 - z1;
+ if( zUsing && sqlite3_strnicmp(zUsing, z1, n)==0 ){
+ printf("Using %s:\n%s\n", zUsing, zLog);
+ fflush(stdout);
+ }
+ sqlite3_bind_text(pIncrCnt,1,z1,n,SQLITE_STATIC);
+ sqlite3_step(pIncrCnt);
+ sqlite3_reset(pIncrCnt);
+ }
+ }
+ sqlite3_finalize(pS2);
+ }
+ sqlite3_finalize(pStmt);
+
+ /* Generate the report */
+ rc = sqlite3_prepare_v2(db,
+ "SELECT tbl, idx, cnt, "
+ " (SELECT group_concat(name,',') FROM pragma_index_info(idx))"
+ " FROM temp.idxu, main.sqlite_schema"
+ " WHERE temp.idxu.tbl=main.sqlite_schema.tbl_name"
+ " AND temp.idxu.idx=main.sqlite_schema.name"
+ " ORDER BY cnt DESC, tbl, idx",
+ -1, &pStmt, 0);
+ if( rc ){
+ printf("Cannot query the result table - %s\n",
+ sqlite3_errmsg(db));
+ goto errorOut;
+ }
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ printf("%10d %s on %s(%s)\n",
+ sqlite3_column_int(pStmt, 2),
+ sqlite3_column_text(pStmt, 1),
+ sqlite3_column_text(pStmt, 0),
+ sqlite3_column_text(pStmt, 3));
+ }
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+
+errorOut:
+ sqlite3_finalize(pIncrCnt);
+ sqlite3_finalize(pStmt);
+ sqlite3_close(db);
+ return nErr;
+}
diff --git a/tool/kvtest-speed.sh b/tool/kvtest-speed.sh
new file mode 100644
index 0000000..5f2c834
--- /dev/null
+++ b/tool/kvtest-speed.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# A script for running speed tests using kvtest.
+#
+# The test database must be set up first. Recommended
+# command-line:
+#
+# ./kvtest init kvtest.db --count 100K --size 12K --variance 5K
+
+if test "$1" = ""
+then
+ echo "Usage: $0 OUTPUTFILE [OPTIONS]"
+ exit
+fi
+NAME=$1
+shift
+OPTS="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DIRECT_OVERFLOW_READ -DUSE_PREAD"
+KVARGS="--count 100K --stats"
+gcc -g -Os -I. $OPTS $* kvtest.c sqlite3.c -o kvtest
+
+# First run using SQL
+rm cachegrind.out.[1-9][0-9]*
+valgrind --tool=cachegrind ./kvtest run kvtest.db $KVARGS 2>&1 | tee summary-kvtest-$NAME.txt
+mv cachegrind.out.[1-9][0-9]* cachegrind.out.sql-$NAME
+cg_anno.tcl cachegrind.out.sql-$NAME >cout-kvtest-sql-$NAME.txt
+
+# Second run using the sqlite3_blob object
+valgrind --tool=cachegrind ./kvtest run kvtest.db $KVARGS --blob-api 2>&1 | tee -a summary-kvtest-$NAME.txt
+mv cachegrind.out.[1-9][0-9]* cachegrind.out.$NAME
+cg_anno.tcl cachegrind.out.$NAME >cout-kvtest-$NAME.txt
+
+# Diff the sqlite3_blob API analysis for non-trunk runs.
+if test "$NAME" != "trunk"; then
+ fossil test-diff --tk cout-kvtest-trunk.txt cout-kvtest-$NAME.txt &
+fi
diff --git a/tool/lemon.c b/tool/lemon.c
new file mode 100644
index 0000000..7804837
--- /dev/null
+++ b/tool/lemon.c
@@ -0,0 +1,5916 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator. The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define ISSPACE(X) isspace((unsigned char)(X))
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+#define ISALNUM(X) isalnum((unsigned char)(X))
+#define ISALPHA(X) isalpha((unsigned char)(X))
+#define ISUPPER(X) isupper((unsigned char)(X))
+#define ISLOWER(X) islower((unsigned char)(X))
+
+
+#ifndef __WIN32__
+# if defined(_WIN32) || defined(WIN32)
+# define __WIN32__
+# endif
+#endif
+
+#ifdef __WIN32__
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int access(const char *path, int mode);
+#ifdef __cplusplus
+}
+#endif
+#else
+#include <unistd.h>
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5 /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+extern void memory_error();
+static int showPrecedenceConflict = 0;
+static char *msort(char*,char**,int(*)(const char*,const char*));
+
+/*
+** Compilers are getting increasingly pedantic about type conversions
+** as C evolves ever closer to Ada.... To work around the latest problems
+** we have to define the following variant of strlen().
+*/
+#define lemonStrlen(X) ((int)strlen(X))
+
+/*
+** Compilers are starting to complain about the use of sprintf() and strcpy(),
+** saying they are unsafe. So we define our own versions of those routines too.
+**
+** There are three routines here: lemon_sprintf(), lemon_vsprintf(), and
+** lemon_addtext(). The first two are replacements for sprintf() and vsprintf().
+** The third is a helper routine for vsnprintf() that adds texts to the end of a
+** buffer, making sure the buffer is always zero-terminated.
+**
+** The string formatter is a minimal subset of stdlib sprintf() supporting only
+** a few simply conversions:
+**
+** %d
+** %s
+** %.*s
+**
+*/
+static void lemon_addtext(
+ char *zBuf, /* The buffer to which text is added */
+ int *pnUsed, /* Slots of the buffer used so far */
+ const char *zIn, /* Text to add */
+ int nIn, /* Bytes of text to add. -1 to use strlen() */
+ int iWidth /* Field width. Negative to left justify */
+){
+ if( nIn<0 ) for(nIn=0; zIn[nIn]; nIn++){}
+ while( iWidth>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth--; }
+ if( nIn==0 ) return;
+ memcpy(&zBuf[*pnUsed], zIn, nIn);
+ *pnUsed += nIn;
+ while( (-iWidth)>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth++; }
+ zBuf[*pnUsed] = 0;
+}
+static int lemon_vsprintf(char *str, const char *zFormat, va_list ap){
+ int i, j, k, c;
+ int nUsed = 0;
+ const char *z;
+ char zTemp[50];
+ str[0] = 0;
+ for(i=j=0; (c = zFormat[i])!=0; i++){
+ if( c=='%' ){
+ int iWidth = 0;
+ lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0);
+ c = zFormat[++i];
+ if( ISDIGIT(c) || (c=='-' && ISDIGIT(zFormat[i+1])) ){
+ if( c=='-' ) i++;
+ while( ISDIGIT(zFormat[i]) ) iWidth = iWidth*10 + zFormat[i++] - '0';
+ if( c=='-' ) iWidth = -iWidth;
+ c = zFormat[i];
+ }
+ if( c=='d' ){
+ int v = va_arg(ap, int);
+ if( v<0 ){
+ lemon_addtext(str, &nUsed, "-", 1, iWidth);
+ v = -v;
+ }else if( v==0 ){
+ lemon_addtext(str, &nUsed, "0", 1, iWidth);
+ }
+ k = 0;
+ while( v>0 ){
+ k++;
+ zTemp[sizeof(zTemp)-k] = (v%10) + '0';
+ v /= 10;
+ }
+ lemon_addtext(str, &nUsed, &zTemp[sizeof(zTemp)-k], k, iWidth);
+ }else if( c=='s' ){
+ z = va_arg(ap, const char*);
+ lemon_addtext(str, &nUsed, z, -1, iWidth);
+ }else if( c=='.' && memcmp(&zFormat[i], ".*s", 3)==0 ){
+ i += 2;
+ k = va_arg(ap, int);
+ z = va_arg(ap, const char*);
+ lemon_addtext(str, &nUsed, z, k, iWidth);
+ }else if( c=='%' ){
+ lemon_addtext(str, &nUsed, "%", 1, 0);
+ }else{
+ fprintf(stderr, "illegal format\n");
+ exit(1);
+ }
+ j = i+1;
+ }
+ }
+ lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0);
+ return nUsed;
+}
+static int lemon_sprintf(char *str, const char *format, ...){
+ va_list ap;
+ int rc;
+ va_start(ap, format);
+ rc = lemon_vsprintf(str, format, ap);
+ va_end(ap);
+ return rc;
+}
+static void lemon_strcpy(char *dest, const char *src){
+ while( (*(dest++) = *(src++))!=0 ){}
+}
+static void lemon_strcat(char *dest, const char *src){
+ while( *dest ) dest++;
+ lemon_strcpy(dest, src);
+}
+
+
+/* a few forward declarations... */
+struct rule;
+struct lemon;
+struct action;
+
+static struct action *Action_new(void);
+static struct action *Action_sort(struct action *);
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences(struct lemon*);
+void FindFirstSets(struct lemon*);
+void FindStates(struct lemon*);
+void FindLinks(struct lemon*);
+void FindFollowSets(struct lemon*);
+void FindActions(struct lemon*);
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(void);
+struct config *Configlist_add(struct rule *, int);
+struct config *Configlist_addbasis(struct rule *, int);
+void Configlist_closure(struct lemon *);
+void Configlist_sort(void);
+void Configlist_sortbasis(void);
+struct config *Configlist_return(void);
+struct config *Configlist_basis(void);
+void Configlist_eat(struct config *);
+void Configlist_reset(void);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+enum option_type { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR,
+ OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR};
+struct s_options {
+ enum option_type type;
+ const char *label;
+ char *arg;
+ const char *message;
+};
+int OptInit(char**,struct s_options*,FILE*);
+int OptNArgs(void);
+char *OptArg(int);
+void OptErr(int);
+void OptPrint(void);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(struct lemon *lemp);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(void);
+void Plink_add(struct plink **, struct config *);
+void Plink_copy(struct plink **, struct plink *);
+void Plink_delete(struct plink *);
+
+/********** From the file "report.h" *************************************/
+void Reprint(struct lemon *);
+void ReportOutput(struct lemon *);
+void ReportTable(struct lemon *, int, int);
+void ReportHeader(struct lemon *);
+void CompressTables(struct lemon *);
+void ResortStates(struct lemon *);
+
+/********** From the file "set.h" ****************************************/
+void SetSize(int); /* All sets will be of size N */
+char *SetNew(void); /* A new set for element 0..N */
+void SetFree(char*); /* Deallocate a set */
+int SetAdd(char*,int); /* Add element to a set */
+int SetUnion(char *,char *); /* A <- A U B, thru element N */
+#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+enum symbol_type {
+ TERMINAL,
+ NONTERMINAL,
+ MULTITERMINAL
+};
+enum e_assoc {
+ LEFT,
+ RIGHT,
+ NONE,
+ UNK
+};
+struct symbol {
+ const char *name; /* Name of the symbol */
+ int index; /* Index number for this symbol */
+ enum symbol_type type; /* Symbols are all either TERMINALS or NTs */
+ struct rule *rule; /* Linked list of rules of this (if an NT) */
+ struct symbol *fallback; /* fallback token in case this token doesn't parse */
+ int prec; /* Precedence if defined (-1 otherwise) */
+ enum e_assoc assoc; /* Associativity if precedence is defined */
+ char *firstset; /* First-set for all rules of this symbol */
+ Boolean lambda; /* True if NT and can generate an empty string */
+ int useCnt; /* Number of times used */
+ char *destructor; /* Code which executes whenever this symbol is
+ ** popped from the stack during error processing */
+ int destLineno; /* Line number for start of destructor. Set to
+ ** -1 for duplicate destructors. */
+ char *datatype; /* The data type of information held by this
+ ** object. Only used if type==NONTERMINAL */
+ int dtnum; /* The data type number. In the parser, the value
+ ** stack is a union. The .yy%d element of this
+ ** union is the correct data type for this object */
+ int bContent; /* True if this symbol ever carries content - if
+ ** it is ever more than just syntax */
+ /* The following fields are used by MULTITERMINALs only */
+ int nsubsym; /* Number of constituent symbols in the MULTI */
+ struct symbol **subsym; /* Array of constituent symbols */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure. */
+struct rule {
+ struct symbol *lhs; /* Left-hand side of the rule */
+ const char *lhsalias; /* Alias for the LHS (NULL if none) */
+ int lhsStart; /* True if left-hand side is the start symbol */
+ int ruleline; /* Line number for the rule */
+ int nrhs; /* Number of RHS symbols */
+ struct symbol **rhs; /* The RHS symbols */
+ const char **rhsalias; /* An alias for each RHS symbol (NULL if none) */
+ int line; /* Line number at which code begins */
+ const char *code; /* The code executed when this rule is reduced */
+ const char *codePrefix; /* Setup code before code[] above */
+ const char *codeSuffix; /* Breakdown code after code[] above */
+ struct symbol *precsym; /* Precedence symbol for this rule */
+ int index; /* An index number for this rule */
+ int iRule; /* Rule number as used in the generated tables */
+ Boolean noCode; /* True if this rule has no associated C code */
+ Boolean codeEmitted; /* True if the code has been emitted already */
+ Boolean canReduce; /* True if this rule is ever reduced */
+ Boolean doesReduce; /* Reduce actions occur after optimization */
+ Boolean neverReduce; /* Reduce is theoretically possible, but prevented
+ ** by actions or other outside implementation */
+ struct rule *nextlhs; /* Next rule with the same LHS */
+ struct rule *next; /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+enum cfgstatus {
+ COMPLETE,
+ INCOMPLETE
+};
+struct config {
+ struct rule *rp; /* The rule upon which the configuration is based */
+ int dot; /* The parse point */
+ char *fws; /* Follow-set for this configuration only */
+ struct plink *fplp; /* Follow-set forward propagation links */
+ struct plink *bplp; /* Follow-set backwards propagation links */
+ struct state *stp; /* Pointer to state which contains this */
+ enum cfgstatus status; /* used during followset and shift computations */
+ struct config *next; /* Next configuration in the state */
+ struct config *bp; /* The next basis configuration */
+};
+
+enum e_action {
+ SHIFT,
+ ACCEPT,
+ REDUCE,
+ ERROR,
+ SSCONFLICT, /* A shift/shift conflict */
+ SRCONFLICT, /* Was a reduce, but part of a conflict */
+ RRCONFLICT, /* Was a reduce, but part of a conflict */
+ SH_RESOLVED, /* Was a shift. Precedence resolved conflict */
+ RD_RESOLVED, /* Was reduce. Precedence resolved conflict */
+ NOT_USED, /* Deleted by compression */
+ SHIFTREDUCE /* Shift first, then reduce */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+ struct symbol *sp; /* The look-ahead symbol */
+ enum e_action type;
+ union {
+ struct state *stp; /* The new state, if a shift */
+ struct rule *rp; /* The rule, if a reduce */
+ } x;
+ struct symbol *spOpt; /* SHIFTREDUCE optimization to this symbol */
+ struct action *next; /* Next action for this state */
+ struct action *collide; /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+ struct config *bp; /* The basis configurations for this state */
+ struct config *cfp; /* All configurations in this set */
+ int statenum; /* Sequential number for this state */
+ struct action *ap; /* List of actions for this state */
+ int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */
+ int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */
+ int iDfltReduce; /* Default action is to REDUCE by this rule */
+ struct rule *pDfltReduce;/* The default REDUCE rule. */
+ int autoReduce; /* True if this is an auto-reduce state */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+ struct config *cfp; /* The configuration to which linked */
+ struct plink *next; /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows. (LEMON uses no global variables and makes little use of
+** static variables. Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+ struct state **sorted; /* Table of states sorted by state number */
+ struct rule *rule; /* List of all rules */
+ struct rule *startRule; /* First rule */
+ int nstate; /* Number of states */
+ int nxstate; /* nstate with tail degenerate states removed */
+ int nrule; /* Number of rules */
+ int nruleWithAction; /* Number of rules with actions */
+ int nsymbol; /* Number of terminal and nonterminal symbols */
+ int nterminal; /* Number of terminal symbols */
+ int minShiftReduce; /* Minimum shift-reduce action value */
+ int errAction; /* Error action value */
+ int accAction; /* Accept action value */
+ int noAction; /* No-op action value */
+ int minReduce; /* Minimum reduce action */
+ int maxAction; /* Maximum action value of any kind */
+ struct symbol **symbols; /* Sorted array of pointers to symbols */
+ int errorcnt; /* Number of errors */
+ struct symbol *errsym; /* The error symbol */
+ struct symbol *wildcard; /* Token that matches anything */
+ char *name; /* Name of the generated parser */
+ char *arg; /* Declaration of the 3rd argument to parser */
+ char *ctx; /* Declaration of 2nd argument to constructor */
+ char *tokentype; /* Type of terminal symbols in the parser stack */
+ char *vartype; /* The default type of non-terminal symbols */
+ char *start; /* Name of the start symbol for the grammar */
+ char *stacksize; /* Size of the parser stack */
+ char *include; /* Code to put at the start of the C file */
+ char *error; /* Code to execute when an error is seen */
+ char *overflow; /* Code to execute on a stack overflow */
+ char *failure; /* Code to execute on parser failure */
+ char *accept; /* Code to execute when the parser excepts */
+ char *extracode; /* Code appended to the generated file */
+ char *tokendest; /* Code to execute to destroy token data */
+ char *vardest; /* Code for the default non-terminal destructor */
+ char *filename; /* Name of the input file */
+ char *outname; /* Name of the current output file */
+ char *tokenprefix; /* A prefix added to token names in the .h file */
+ int nconflict; /* Number of parsing conflicts */
+ int nactiontab; /* Number of entries in the yy_action[] table */
+ int nlookaheadtab; /* Number of entries in yy_lookahead[] */
+ int tablesize; /* Total table size of all tables in bytes */
+ int basisflag; /* Print only basis configurations */
+ int printPreprocessed; /* Show preprocessor output on stdout */
+ int has_fallback; /* True if any %fallback is seen in the grammar */
+ int nolinenosflag; /* True if #line statements should not be printed */
+ int argc; /* Number of command-line arguments */
+ char **argv; /* Command-line arguments */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+ extern void memory_error(); \
+ memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+/* Routines for handling a strings */
+
+const char *Strsafe(const char *);
+
+void Strsafe_init(void);
+int Strsafe_insert(const char *);
+const char *Strsafe_find(const char *);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new(const char *);
+int Symbolcmpp(const void *, const void *);
+void Symbol_init(void);
+int Symbol_insert(struct symbol *, const char *);
+struct symbol *Symbol_find(const char *);
+struct symbol *Symbol_Nth(int);
+int Symbol_count(void);
+struct symbol **Symbol_arrayof(void);
+
+/* Routines to manage the state table */
+
+int Configcmp(const char *, const char *);
+struct state *State_new(void);
+void State_init(void);
+int State_insert(struct state *, struct config *);
+struct state *State_find(struct config *);
+struct state **State_arrayof(void);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(void);
+int Configtable_insert(struct config *);
+struct config *Configtable_find(struct config *);
+void Configtable_clear(int(*)(struct config *));
+
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+static struct action *Action_new(void){
+ static struct action *actionfreelist = 0;
+ struct action *newaction;
+
+ if( actionfreelist==0 ){
+ int i;
+ int amt = 100;
+ actionfreelist = (struct action *)calloc(amt, sizeof(struct action));
+ if( actionfreelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new parser action.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) actionfreelist[i].next = &actionfreelist[i+1];
+ actionfreelist[amt-1].next = 0;
+ }
+ newaction = actionfreelist;
+ actionfreelist = actionfreelist->next;
+ return newaction;
+}
+
+/* Compare two actions for sorting purposes. Return negative, zero, or
+** positive if the first action is less than, equal to, or greater than
+** the first
+*/
+static int actioncmp(
+ struct action *ap1,
+ struct action *ap2
+){
+ int rc;
+ rc = ap1->sp->index - ap2->sp->index;
+ if( rc==0 ){
+ rc = (int)ap1->type - (int)ap2->type;
+ }
+ if( rc==0 && (ap1->type==REDUCE || ap1->type==SHIFTREDUCE) ){
+ rc = ap1->x.rp->index - ap2->x.rp->index;
+ }
+ if( rc==0 ){
+ rc = (int) (ap2 - ap1);
+ }
+ return rc;
+}
+
+/* Sort parser actions */
+static struct action *Action_sort(
+ struct action *ap
+){
+ ap = (struct action *)msort((char *)ap,(char **)&ap->next,
+ (int(*)(const char*,const char*))actioncmp);
+ return ap;
+}
+
+void Action_add(
+ struct action **app,
+ enum e_action type,
+ struct symbol *sp,
+ char *arg
+){
+ struct action *newaction;
+ newaction = Action_new();
+ newaction->next = *app;
+ *app = newaction;
+ newaction->type = type;
+ newaction->sp = sp;
+ newaction->spOpt = 0;
+ if( type==SHIFT ){
+ newaction->x.stp = (struct state *)arg;
+ }else{
+ newaction->x.rp = (struct rule *)arg;
+ }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure.
+**
+** The yy_action table maps the pair (state_number, lookahead) into an
+** action_number. The table is an array of integers pairs. The state_number
+** determines an initial offset into the yy_action array. The lookahead
+** value is then added to this initial offset to get an index X into the
+** yy_action array. If the aAction[X].lookahead equals the value of the
+** of the lookahead input, then the value of the action_number output is
+** aAction[X].action. If the lookaheads do not match then the
+** default action for the state_number is returned.
+**
+** All actions associated with a single state_number are first entered
+** into aLookahead[] using multiple calls to acttab_action(). Then the
+** actions for that single state_number are placed into the aAction[]
+** array with a single call to acttab_insert(). The acttab_insert() call
+** also resets the aLookahead[] array in preparation for the next
+** state number.
+*/
+struct lookahead_action {
+ int lookahead; /* Value of the lookahead token */
+ int action; /* Action to take on the given lookahead */
+};
+typedef struct acttab acttab;
+struct acttab {
+ int nAction; /* Number of used slots in aAction[] */
+ int nActionAlloc; /* Slots allocated for aAction[] */
+ struct lookahead_action
+ *aAction, /* The yy_action[] table under construction */
+ *aLookahead; /* A single new transaction set */
+ int mnLookahead; /* Minimum aLookahead[].lookahead */
+ int mnAction; /* Action associated with mnLookahead */
+ int mxLookahead; /* Maximum aLookahead[].lookahead */
+ int nLookahead; /* Used slots in aLookahead[] */
+ int nLookaheadAlloc; /* Slots allocated in aLookahead[] */
+ int nterminal; /* Number of terminal symbols */
+ int nsymbol; /* total number of symbols */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_lookahead_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N) ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+ free( p->aAction );
+ free( p->aLookahead );
+ free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(int nsymbol, int nterminal){
+ acttab *p = (acttab *) calloc( 1, sizeof(*p) );
+ if( p==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new acttab.");
+ exit(1);
+ }
+ memset(p, 0, sizeof(*p));
+ p->nsymbol = nsymbol;
+ p->nterminal = nterminal;
+ return p;
+}
+
+/* Add a new action to the current transaction set.
+**
+** This routine is called once for each lookahead for a particular
+** state.
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+ if( p->nLookahead>=p->nLookaheadAlloc ){
+ p->nLookaheadAlloc += 25;
+ p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead,
+ sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+ if( p->aLookahead==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ }
+ if( p->nLookahead==0 ){
+ p->mxLookahead = lookahead;
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }else{
+ if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+ if( p->mnLookahead>lookahead ){
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }
+ }
+ p->aLookahead[p->nLookahead].lookahead = lookahead;
+ p->aLookahead[p->nLookahead].action = action;
+ p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table. Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+**
+** If the makeItSafe parameter is true, then the offset is chosen so that
+** it is impossible to overread the yy_lookaside[] table regardless of
+** the lookaside token. This is done for the terminal symbols, as they
+** come from external inputs and can contain syntax errors. When makeItSafe
+** is false, there is more flexibility in selecting offsets, resulting in
+** a smaller table. For non-terminal symbols, which are never syntax errors,
+** makeItSafe can be false.
+*/
+int acttab_insert(acttab *p, int makeItSafe){
+ int i, j, k, n, end;
+ assert( p->nLookahead>0 );
+
+ /* Make sure we have enough space to hold the expanded action table
+ ** in the worst case. The worst case occurs if the transaction set
+ ** must be appended to the current action table
+ */
+ n = p->nsymbol + 1;
+ if( p->nAction + n >= p->nActionAlloc ){
+ int oldAlloc = p->nActionAlloc;
+ p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+ p->aAction = (struct lookahead_action *) realloc( p->aAction,
+ sizeof(p->aAction[0])*p->nActionAlloc);
+ if( p->aAction==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=oldAlloc; i<p->nActionAlloc; i++){
+ p->aAction[i].lookahead = -1;
+ p->aAction[i].action = -1;
+ }
+ }
+
+ /* Scan the existing action table looking for an offset that is a
+ ** duplicate of the current transaction set. Fall out of the loop
+ ** if and when the duplicate is found.
+ **
+ ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+ */
+ end = makeItSafe ? p->mnLookahead : 0;
+ for(i=p->nAction-1; i>=end; i--){
+ if( p->aAction[i].lookahead==p->mnLookahead ){
+ /* All lookaheads and actions in the aLookahead[] transaction
+ ** must match against the candidate aAction[i] entry. */
+ if( p->aAction[i].action!=p->mnAction ) continue;
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 || k>=p->nAction ) break;
+ if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+ if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+ }
+ if( j<p->nLookahead ) continue;
+
+ /* No possible lookahead value that is not in the aLookahead[]
+ ** transaction is allowed to match aAction[i] */
+ n = 0;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead<0 ) continue;
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+ }
+ if( n==p->nLookahead ){
+ break; /* An exact match is found at offset i */
+ }
+ }
+ }
+
+ /* If no existing offsets exactly match the current transaction, find an
+ ** an empty offset in the aAction[] table in which we can add the
+ ** aLookahead[] transaction.
+ */
+ if( i<end ){
+ /* Look for holes in the aAction[] table that fit the current
+ ** aLookahead[] transaction. Leave i set to the offset of the hole.
+ ** If no holes are found, i is left at p->nAction, which means the
+ ** transaction will be appended. */
+ i = makeItSafe ? p->mnLookahead : 0;
+ for(; i<p->nActionAlloc - p->mxLookahead; i++){
+ if( p->aAction[i].lookahead<0 ){
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 ) break;
+ if( p->aAction[k].lookahead>=0 ) break;
+ }
+ if( j<p->nLookahead ) continue;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+ }
+ if( j==p->nAction ){
+ break; /* Fits in empty slots */
+ }
+ }
+ }
+ }
+ /* Insert transaction set at index i. */
+#if 0
+ printf("Acttab:");
+ for(j=0; j<p->nLookahead; j++){
+ printf(" %d", p->aLookahead[j].lookahead);
+ }
+ printf(" inserted at %d\n", i);
+#endif
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ p->aAction[k] = p->aLookahead[j];
+ if( k>=p->nAction ) p->nAction = k+1;
+ }
+ if( makeItSafe && i+p->nterminal>=p->nAction ) p->nAction = i+p->nterminal+1;
+ p->nLookahead = 0;
+
+ /* Return the offset that is added to the lookahead in order to get the
+ ** index into yy_action of the action */
+ return i - p->mnLookahead;
+}
+
+/*
+** Return the size of the action table without the trailing syntax error
+** entries.
+*/
+int acttab_action_size(acttab *p){
+ int n = p->nAction;
+ while( n>0 && p->aAction[n-1].lookahead<0 ){ n--; }
+ return n;
+}
+
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+**
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled. Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence. If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(struct lemon *xp)
+{
+ struct rule *rp;
+ for(rp=xp->rule; rp; rp=rp->next){
+ if( rp->precsym==0 ){
+ int i, j;
+ for(i=0; i<rp->nrhs && rp->precsym==0; i++){
+ struct symbol *sp = rp->rhs[i];
+ if( sp->type==MULTITERMINAL ){
+ for(j=0; j<sp->nsubsym; j++){
+ if( sp->subsym[j]->prec>=0 ){
+ rp->precsym = sp->subsym[j];
+ break;
+ }
+ }
+ }else if( sp->prec>=0 ){
+ rp->precsym = rp->rhs[i];
+ }
+ }
+ }
+ }
+ return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(struct lemon *lemp)
+{
+ int i, j;
+ struct rule *rp;
+ int progress;
+
+ for(i=0; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->lambda = LEMON_FALSE;
+ }
+ for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->firstset = SetNew();
+ }
+
+ /* First compute all lambdas */
+ do{
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->lhs->lambda ) continue;
+ for(i=0; i<rp->nrhs; i++){
+ struct symbol *sp = rp->rhs[i];
+ assert( sp->type==NONTERMINAL || sp->lambda==LEMON_FALSE );
+ if( sp->lambda==LEMON_FALSE ) break;
+ }
+ if( i==rp->nrhs ){
+ rp->lhs->lambda = LEMON_TRUE;
+ progress = 1;
+ }
+ }
+ }while( progress );
+
+ /* Now compute all first sets */
+ do{
+ struct symbol *s1, *s2;
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ s1 = rp->lhs;
+ for(i=0; i<rp->nrhs; i++){
+ s2 = rp->rhs[i];
+ if( s2->type==TERMINAL ){
+ progress += SetAdd(s1->firstset,s2->index);
+ break;
+ }else if( s2->type==MULTITERMINAL ){
+ for(j=0; j<s2->nsubsym; j++){
+ progress += SetAdd(s1->firstset,s2->subsym[j]->index);
+ }
+ break;
+ }else if( s1==s2 ){
+ if( s1->lambda==LEMON_FALSE ) break;
+ }else{
+ progress += SetUnion(s1->firstset,s2->firstset);
+ if( s2->lambda==LEMON_FALSE ) break;
+ }
+ }
+ }
+ }while( progress );
+ return;
+}
+
+/* Compute all LR(0) states for the grammar. Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(struct lemon *); /* forward reference */
+void FindStates(struct lemon *lemp)
+{
+ struct symbol *sp;
+ struct rule *rp;
+
+ Configlist_init();
+
+ /* Find the start symbol */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ){
+ ErrorMsg(lemp->filename,0,
+ "The specified start symbol \"%s\" is not "
+ "in a nonterminal of the grammar. \"%s\" will be used as the start "
+ "symbol instead.",lemp->start,lemp->startRule->lhs->name);
+ lemp->errorcnt++;
+ sp = lemp->startRule->lhs;
+ }
+ }else if( lemp->startRule ){
+ sp = lemp->startRule->lhs;
+ }else{
+ ErrorMsg(lemp->filename,0,"Internal error - no start rule\n");
+ exit(1);
+ }
+
+ /* Make sure the start symbol doesn't occur on the right-hand side of
+ ** any rule. Report an error if it does. (YACC would generate a new
+ ** start symbol in this case.) */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]==sp ){ /* FIX ME: Deal with multiterminals */
+ ErrorMsg(lemp->filename,0,
+ "The start symbol \"%s\" occurs on the "
+ "right-hand side of a rule. This will result in a parser which "
+ "does not work properly.",sp->name);
+ lemp->errorcnt++;
+ }
+ }
+ }
+
+ /* The basis configuration set for the first state
+ ** is all rules which have the start symbol as their
+ ** left-hand side */
+ for(rp=sp->rule; rp; rp=rp->nextlhs){
+ struct config *newcfp;
+ rp->lhsStart = 1;
+ newcfp = Configlist_addbasis(rp,0);
+ SetAdd(newcfp->fws,0);
+ }
+
+ /* Compute the first state. All other states will be
+ ** computed automatically during the computation of the first one.
+ ** The returned pointer to the first state is not used. */
+ (void)getstate(lemp);
+ return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(struct lemon *, struct state *); /* Forwd ref */
+PRIVATE struct state *getstate(struct lemon *lemp)
+{
+ struct config *cfp, *bp;
+ struct state *stp;
+
+ /* Extract the sorted basis of the new state. The basis was constructed
+ ** by prior calls to "Configlist_addbasis()". */
+ Configlist_sortbasis();
+ bp = Configlist_basis();
+
+ /* Get a state with the same basis */
+ stp = State_find(bp);
+ if( stp ){
+ /* A state with the same basis already exists! Copy all the follow-set
+ ** propagation links from the state under construction into the
+ ** preexisting state, then return a pointer to the preexisting state */
+ struct config *x, *y;
+ for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+ Plink_copy(&y->bplp,x->bplp);
+ Plink_delete(x->fplp);
+ x->fplp = x->bplp = 0;
+ }
+ cfp = Configlist_return();
+ Configlist_eat(cfp);
+ }else{
+ /* This really is a new state. Construct all the details */
+ Configlist_closure(lemp); /* Compute the configuration closure */
+ Configlist_sort(); /* Sort the configuration closure */
+ cfp = Configlist_return(); /* Get a pointer to the config list */
+ stp = State_new(); /* A new state structure */
+ MemoryCheck(stp);
+ stp->bp = bp; /* Remember the configuration basis */
+ stp->cfp = cfp; /* Remember the configuration closure */
+ stp->statenum = lemp->nstate++; /* Every state gets a sequence number */
+ stp->ap = 0; /* No actions, yet. */
+ State_insert(stp,stp->bp); /* Add to the state table */
+ buildshifts(lemp,stp); /* Recursively compute successor states */
+ }
+ return stp;
+}
+
+/*
+** Return true if two symbols are the same.
+*/
+int same_symbol(struct symbol *a, struct symbol *b)
+{
+ int i;
+ if( a==b ) return 1;
+ if( a->type!=MULTITERMINAL ) return 0;
+ if( b->type!=MULTITERMINAL ) return 0;
+ if( a->nsubsym!=b->nsubsym ) return 0;
+ for(i=0; i<a->nsubsym; i++){
+ if( a->subsym[i]!=b->subsym[i] ) return 0;
+ }
+ return 1;
+}
+
+/* Construct all successor states to the given state. A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(struct lemon *lemp, struct state *stp)
+{
+ struct config *cfp; /* For looping thru the config closure of "stp" */
+ struct config *bcfp; /* For the inner loop on config closure of "stp" */
+ struct config *newcfg; /* */
+ struct symbol *sp; /* Symbol following the dot in configuration "cfp" */
+ struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */
+ struct state *newstp; /* A pointer to a successor state */
+
+ /* Each configuration becomes complete after it contributes to a successor
+ ** state. Initially, all configurations are incomplete */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+ /* Loop through all configurations of the state "stp" */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */
+ if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */
+ Configlist_reset(); /* Reset the new config set */
+ sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */
+
+ /* For every configuration in the state "stp" which has the symbol "sp"
+ ** following its dot, add the same configuration to the basis set under
+ ** construction but with the dot shifted one symbol to the right. */
+ for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+ if( bcfp->status==COMPLETE ) continue; /* Already used */
+ if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+ bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */
+ if( !same_symbol(bsp,sp) ) continue; /* Must be same as for "cfp" */
+ bcfp->status = COMPLETE; /* Mark this config as used */
+ newcfg = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+ Plink_add(&newcfg->bplp,bcfp);
+ }
+
+ /* Get a pointer to the state described by the basis configuration set
+ ** constructed in the preceding loop */
+ newstp = getstate(lemp);
+
+ /* The state "newstp" is reached from the state "stp" by a shift action
+ ** on the symbol "sp" */
+ if( sp->type==MULTITERMINAL ){
+ int i;
+ for(i=0; i<sp->nsubsym; i++){
+ Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp);
+ }
+ }else{
+ Action_add(&stp->ap,SHIFT,sp,(char *)newstp);
+ }
+ }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(struct lemon *lemp)
+{
+ int i;
+ struct config *cfp, *other;
+ struct state *stp;
+ struct plink *plp;
+
+ /* Housekeeping detail:
+ ** Add to every propagate link a pointer back to the state to
+ ** which the link is attached. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){
+ cfp->stp = stp;
+ }
+ }
+
+ /* Convert all backlinks into forward links. Only the forward
+ ** links are used in the follow-set computation. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){
+ for(plp=cfp->bplp; plp; plp=plp->next){
+ other = plp->cfp;
+ Plink_add(&other->fplp,cfp);
+ }
+ }
+ }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(struct lemon *lemp)
+{
+ int i;
+ struct config *cfp;
+ struct plink *plp;
+ int progress;
+ int change;
+
+ for(i=0; i<lemp->nstate; i++){
+ assert( lemp->sorted[i]!=0 );
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ cfp->status = INCOMPLETE;
+ }
+ }
+
+ do{
+ progress = 0;
+ for(i=0; i<lemp->nstate; i++){
+ assert( lemp->sorted[i]!=0 );
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue;
+ for(plp=cfp->fplp; plp; plp=plp->next){
+ change = SetUnion(plp->cfp->fws,cfp->fws);
+ if( change ){
+ plp->cfp->status = INCOMPLETE;
+ progress = 1;
+ }
+ }
+ cfp->status = COMPLETE;
+ }
+ }
+ }while( progress );
+}
+
+static int resolve_conflict(struct action *,struct action *);
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(struct lemon *lemp)
+{
+ int i,j;
+ struct config *cfp;
+ struct state *stp;
+ struct symbol *sp;
+ struct rule *rp;
+
+ /* Add all of the reduce actions
+ ** A reduce action is added for each element of the followset of
+ ** a configuration which has its dot at the extreme right.
+ */
+ for(i=0; i<lemp->nstate; i++){ /* Loop over all states */
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */
+ if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */
+ for(j=0; j<lemp->nterminal; j++){
+ if( SetFind(cfp->fws,j) ){
+ /* Add a reduce action to the state "stp" which will reduce by the
+ ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+ Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp);
+ }
+ }
+ }
+ }
+ }
+
+ /* Add the accepting token */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ){
+ if( lemp->startRule==0 ){
+ fprintf(stderr, "internal error on source line %d: no start rule\n",
+ __LINE__);
+ exit(1);
+ }
+ sp = lemp->startRule->lhs;
+ }
+ }else{
+ sp = lemp->startRule->lhs;
+ }
+ /* Add to the first state (which is always the starting state of the
+ ** finite state machine) an action to ACCEPT if the lookahead is the
+ ** start nonterminal. */
+ Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+ /* Resolve conflicts */
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap, *nap;
+ stp = lemp->sorted[i];
+ /* assert( stp->ap ); */
+ stp->ap = Action_sort(stp->ap);
+ for(ap=stp->ap; ap && ap->next; ap=ap->next){
+ for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+ /* The two actions "ap" and "nap" have the same lookahead.
+ ** Figure out which one should be used */
+ lemp->nconflict += resolve_conflict(ap,nap);
+ }
+ }
+ }
+
+ /* Report an error for each rule that can never be reduced. */
+ for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE;
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap;
+ for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE;
+ }
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->canReduce ) continue;
+ ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+ lemp->errorcnt++;
+ }
+}
+
+/* Resolve a conflict between the two given actions. If the
+** conflict can't be resolved, return non-zero.
+**
+** NO LONGER TRUE:
+** To resolve a conflict, first look to see if either action
+** is on an error rule. In that case, take the action which
+** is not associated with the error rule. If neither or both
+** actions are associated with an error rule, then try to
+** use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx. This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(
+ struct action *apx,
+ struct action *apy
+){
+ struct symbol *spx, *spy;
+ int errcnt = 0;
+ assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */
+ if( apx->type==SHIFT && apy->type==SHIFT ){
+ apy->type = SSCONFLICT;
+ errcnt++;
+ }
+ if( apx->type==SHIFT && apy->type==REDUCE ){
+ spx = apx->sp;
+ spy = apy->x.rp->precsym;
+ if( spy==0 || spx->prec<0 || spy->prec<0 ){
+ /* Not enough precedence information. */
+ apy->type = SRCONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){ /* higher precedence wins */
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = SH_RESOLVED;
+ }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+ apy->type = RD_RESOLVED; /* associativity */
+ }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */
+ apx->type = SH_RESOLVED;
+ }else{
+ assert( spx->prec==spy->prec && spx->assoc==NONE );
+ apx->type = ERROR;
+ }
+ }else if( apx->type==REDUCE && apy->type==REDUCE ){
+ spx = apx->x.rp->precsym;
+ spy = apy->x.rp->precsym;
+ if( spx==0 || spy==0 || spx->prec<0 ||
+ spy->prec<0 || spx->prec==spy->prec ){
+ apy->type = RRCONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = RD_RESOLVED;
+ }
+ }else{
+ assert(
+ apx->type==SH_RESOLVED ||
+ apx->type==RD_RESOLVED ||
+ apx->type==SSCONFLICT ||
+ apx->type==SRCONFLICT ||
+ apx->type==RRCONFLICT ||
+ apy->type==SH_RESOLVED ||
+ apy->type==RD_RESOLVED ||
+ apy->type==SSCONFLICT ||
+ apy->type==SRCONFLICT ||
+ apy->type==RRCONFLICT
+ );
+ /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+ ** REDUCEs on the list. If we reach this point it must be because
+ ** the parser conflict had already been resolved. */
+ }
+ return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0; /* List of free configurations */
+static struct config *current = 0; /* Top of list of configurations */
+static struct config **currentend = 0; /* Last on list of configs */
+static struct config *basis = 0; /* Top of list of basis configs */
+static struct config **basisend = 0; /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(void){
+ return (struct config*)calloc(1, sizeof(struct config));
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(struct config *old)
+{
+ old->next = freelist;
+ freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(void){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_init();
+ return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(void){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_clear(0);
+ return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(
+ struct rule *rp, /* The rule */
+ int dot /* Index into the RHS of the rule where the dot goes */
+){
+ struct config *cfp, model;
+
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(struct rule *rp, int dot)
+{
+ struct config *cfp, model;
+
+ assert( basisend!=0 );
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ *basisend = cfp;
+ basisend = &cfp->bp;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(struct lemon *lemp)
+{
+ struct config *cfp, *newcfp;
+ struct rule *rp, *newrp;
+ struct symbol *sp, *xsp;
+ int i, dot;
+
+ assert( currentend!=0 );
+ for(cfp=current; cfp; cfp=cfp->next){
+ rp = cfp->rp;
+ dot = cfp->dot;
+ if( dot>=rp->nrhs ) continue;
+ sp = rp->rhs[dot];
+ if( sp->type==NONTERMINAL ){
+ if( sp->rule==0 && sp!=lemp->errsym ){
+ ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+ sp->name);
+ lemp->errorcnt++;
+ }
+ for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+ newcfp = Configlist_add(newrp,0);
+ for(i=dot+1; i<rp->nrhs; i++){
+ xsp = rp->rhs[i];
+ if( xsp->type==TERMINAL ){
+ SetAdd(newcfp->fws,xsp->index);
+ break;
+ }else if( xsp->type==MULTITERMINAL ){
+ int k;
+ for(k=0; k<xsp->nsubsym; k++){
+ SetAdd(newcfp->fws, xsp->subsym[k]->index);
+ }
+ break;
+ }else{
+ SetUnion(newcfp->fws,xsp->firstset);
+ if( xsp->lambda==LEMON_FALSE ) break;
+ }
+ }
+ if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+ }
+ }
+ }
+ return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(void){
+ current = (struct config*)msort((char*)current,(char**)&(current->next),
+ Configcmp);
+ currentend = 0;
+ return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(void){
+ basis = (struct config*)msort((char*)current,(char**)&(current->bp),
+ Configcmp);
+ basisend = 0;
+ return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(void){
+ struct config *old;
+ old = current;
+ current = 0;
+ currentend = 0;
+ return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(void){
+ struct config *old;
+ old = basis;
+ basis = 0;
+ basisend = 0;
+ return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(struct config *cfp)
+{
+ struct config *nextcfp;
+ for(; cfp; cfp=nextcfp){
+ nextcfp = cfp->next;
+ assert( cfp->fplp==0 );
+ assert( cfp->bplp==0 );
+ if( cfp->fws ) SetFree(cfp->fws);
+ deleteconfig(cfp);
+ }
+ return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+ va_list ap;
+ fprintf(stderr, "%s:%d: ", filename, lineno);
+ va_start(ap, format);
+ vfprintf(stderr,format,ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort. This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(void){
+ fprintf(stderr,"Out of memory. Aborting...\n");
+ exit(1);
+}
+
+static int nDefine = 0; /* Number of -D options on the command line */
+static int nDefineUsed = 0; /* Number of -D options actually used */
+static char **azDefine = 0; /* Name of the -D macros */
+static char *bDefineUsed = 0; /* True for every -D macro actually used */
+
+/* This routine is called with the argument to each -D command-line option.
+** Add the macro defined to the azDefine array.
+*/
+static void handle_D_option(char *z){
+ char **paz;
+ nDefine++;
+ azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine);
+ if( azDefine==0 ){
+ fprintf(stderr,"out of memory\n");
+ exit(1);
+ }
+ bDefineUsed = (char*)realloc(bDefineUsed, nDefine);
+ if( bDefineUsed==0 ){
+ fprintf(stderr,"out of memory\n");
+ exit(1);
+ }
+ bDefineUsed[nDefine-1] = 0;
+ paz = &azDefine[nDefine-1];
+ *paz = (char *) malloc( lemonStrlen(z)+1 );
+ if( *paz==0 ){
+ fprintf(stderr,"out of memory\n");
+ exit(1);
+ }
+ lemon_strcpy(*paz, z);
+ for(z=*paz; *z && *z!='='; z++){}
+ *z = 0;
+}
+
+/* Rember the name of the output directory
+*/
+static char *outputDir = NULL;
+static void handle_d_option(char *z){
+ outputDir = (char *) malloc( lemonStrlen(z)+1 );
+ if( outputDir==0 ){
+ fprintf(stderr,"out of memory\n");
+ exit(1);
+ }
+ lemon_strcpy(outputDir, z);
+}
+
+static char *user_templatename = NULL;
+static void handle_T_option(char *z){
+ user_templatename = (char *) malloc( lemonStrlen(z)+1 );
+ if( user_templatename==0 ){
+ memory_error();
+ }
+ lemon_strcpy(user_templatename, z);
+}
+
+/* Merge together to lists of rules ordered by rule.iRule */
+static struct rule *Rule_merge(struct rule *pA, struct rule *pB){
+ struct rule *pFirst = 0;
+ struct rule **ppPrev = &pFirst;
+ while( pA && pB ){
+ if( pA->iRule<pB->iRule ){
+ *ppPrev = pA;
+ ppPrev = &pA->next;
+ pA = pA->next;
+ }else{
+ *ppPrev = pB;
+ ppPrev = &pB->next;
+ pB = pB->next;
+ }
+ }
+ if( pA ){
+ *ppPrev = pA;
+ }else{
+ *ppPrev = pB;
+ }
+ return pFirst;
+}
+
+/*
+** Sort a list of rules in order of increasing iRule value
+*/
+static struct rule *Rule_sort(struct rule *rp){
+ unsigned int i;
+ struct rule *pNext;
+ struct rule *x[32];
+ memset(x, 0, sizeof(x));
+ while( rp ){
+ pNext = rp->next;
+ rp->next = 0;
+ for(i=0; i<sizeof(x)/sizeof(x[0])-1 && x[i]; i++){
+ rp = Rule_merge(x[i], rp);
+ x[i] = 0;
+ }
+ x[i] = rp;
+ rp = pNext;
+ }
+ rp = 0;
+ for(i=0; i<sizeof(x)/sizeof(x[0]); i++){
+ rp = Rule_merge(x[i], rp);
+ }
+ return rp;
+}
+
+/* forward reference */
+static const char *minimum_size_type(int lwr, int upr, int *pnByte);
+
+/* Print a single line of the "Parser Stats" output
+*/
+static void stats_line(const char *zLabel, int iValue){
+ int nLabel = lemonStrlen(zLabel);
+ printf(" %s%.*s %5d\n", zLabel,
+ 35-nLabel, "................................",
+ iValue);
+}
+
+/* The main program. Parse the command line and do it... */
+int main(int argc, char **argv){
+ static int version = 0;
+ static int rpflag = 0;
+ static int basisflag = 0;
+ static int compress = 0;
+ static int quiet = 0;
+ static int statistics = 0;
+ static int mhflag = 0;
+ static int nolinenosflag = 0;
+ static int noResort = 0;
+ static int sqlFlag = 0;
+ static int printPP = 0;
+
+ static struct s_options options[] = {
+ {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+ {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+ {OPT_FSTR, "d", (char*)&handle_d_option, "Output directory. Default '.'"},
+ {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."},
+ {OPT_FLAG, "E", (char*)&printPP, "Print input file after preprocessing."},
+ {OPT_FSTR, "f", 0, "Ignored. (Placeholder for -f compiler options.)"},
+ {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+ {OPT_FSTR, "I", 0, "Ignored. (Placeholder for '-I' compiler options.)"},
+ {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."},
+ {OPT_FLAG, "l", (char*)&nolinenosflag, "Do not print #line statements."},
+ {OPT_FSTR, "O", 0, "Ignored. (Placeholder for '-O' compiler options.)"},
+ {OPT_FLAG, "p", (char*)&showPrecedenceConflict,
+ "Show conflicts resolved by precedence rules"},
+ {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+ {OPT_FLAG, "r", (char*)&noResort, "Do not sort or renumber states"},
+ {OPT_FLAG, "s", (char*)&statistics,
+ "Print parser stats to standard output."},
+ {OPT_FLAG, "S", (char*)&sqlFlag,
+ "Generate the *.sql file describing the parser tables."},
+ {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+ {OPT_FSTR, "T", (char*)handle_T_option, "Specify a template file."},
+ {OPT_FSTR, "W", 0, "Ignored. (Placeholder for '-W' compiler options.)"},
+ {OPT_FLAG,0,0,0}
+ };
+ int i;
+ int exitcode;
+ struct lemon lem;
+ struct rule *rp;
+
+ OptInit(argv,options,stderr);
+ if( version ){
+ printf("Lemon version 1.0\n");
+ exit(0);
+ }
+ if( OptNArgs()!=1 ){
+ fprintf(stderr,"Exactly one filename argument is required.\n");
+ exit(1);
+ }
+ memset(&lem, 0, sizeof(lem));
+ lem.errorcnt = 0;
+
+ /* Initialize the machine */
+ Strsafe_init();
+ Symbol_init();
+ State_init();
+ lem.argv = argv;
+ lem.argc = argc;
+ lem.filename = OptArg(0);
+ lem.basisflag = basisflag;
+ lem.nolinenosflag = nolinenosflag;
+ lem.printPreprocessed = printPP;
+ Symbol_new("$");
+
+ /* Parse the input file */
+ Parse(&lem);
+ if( lem.printPreprocessed || lem.errorcnt ) exit(lem.errorcnt);
+ if( lem.nrule==0 ){
+ fprintf(stderr,"Empty grammar.\n");
+ exit(1);
+ }
+ lem.errsym = Symbol_find("error");
+
+ /* Count and index the symbols of the grammar */
+ Symbol_new("{default}");
+ lem.nsymbol = Symbol_count();
+ lem.symbols = Symbol_arrayof();
+ for(i=0; i<lem.nsymbol; i++) lem.symbols[i]->index = i;
+ qsort(lem.symbols,lem.nsymbol,sizeof(struct symbol*), Symbolcmpp);
+ for(i=0; i<lem.nsymbol; i++) lem.symbols[i]->index = i;
+ while( lem.symbols[i-1]->type==MULTITERMINAL ){ i--; }
+ assert( strcmp(lem.symbols[i-1]->name,"{default}")==0 );
+ lem.nsymbol = i - 1;
+ for(i=1; ISUPPER(lem.symbols[i]->name[0]); i++);
+ lem.nterminal = i;
+
+ /* Assign sequential rule numbers. Start with 0. Put rules that have no
+ ** reduce action C-code associated with them last, so that the switch()
+ ** statement that selects reduction actions will have a smaller jump table.
+ */
+ for(i=0, rp=lem.rule; rp; rp=rp->next){
+ rp->iRule = rp->code ? i++ : -1;
+ }
+ lem.nruleWithAction = i;
+ for(rp=lem.rule; rp; rp=rp->next){
+ if( rp->iRule<0 ) rp->iRule = i++;
+ }
+ lem.startRule = lem.rule;
+ lem.rule = Rule_sort(lem.rule);
+
+ /* Generate a reprint of the grammar, if requested on the command line */
+ if( rpflag ){
+ Reprint(&lem);
+ }else{
+ /* Initialize the size for all follow and first sets */
+ SetSize(lem.nterminal+1);
+
+ /* Find the precedence for every production rule (that has one) */
+ FindRulePrecedences(&lem);
+
+ /* Compute the lambda-nonterminals and the first-sets for every
+ ** nonterminal */
+ FindFirstSets(&lem);
+
+ /* Compute all LR(0) states. Also record follow-set propagation
+ ** links so that the follow-set can be computed later */
+ lem.nstate = 0;
+ FindStates(&lem);
+ lem.sorted = State_arrayof();
+
+ /* Tie up loose ends on the propagation links */
+ FindLinks(&lem);
+
+ /* Compute the follow set of every reducible configuration */
+ FindFollowSets(&lem);
+
+ /* Compute the action tables */
+ FindActions(&lem);
+
+ /* Compress the action tables */
+ if( compress==0 ) CompressTables(&lem);
+
+ /* Reorder and renumber the states so that states with fewer choices
+ ** occur at the end. This is an optimization that helps make the
+ ** generated parser tables smaller. */
+ if( noResort==0 ) ResortStates(&lem);
+
+ /* Generate a report of the parser generated. (the "y.output" file) */
+ if( !quiet ) ReportOutput(&lem);
+
+ /* Generate the source code for the parser */
+ ReportTable(&lem, mhflag, sqlFlag);
+
+ /* Produce a header file for use by the scanner. (This step is
+ ** omitted if the "-m" option is used because makeheaders will
+ ** generate the file for us.) */
+ if( !mhflag ) ReportHeader(&lem);
+ }
+ if( statistics ){
+ printf("Parser statistics:\n");
+ stats_line("terminal symbols", lem.nterminal);
+ stats_line("non-terminal symbols", lem.nsymbol - lem.nterminal);
+ stats_line("total symbols", lem.nsymbol);
+ stats_line("rules", lem.nrule);
+ stats_line("states", lem.nxstate);
+ stats_line("conflicts", lem.nconflict);
+ stats_line("action table entries", lem.nactiontab);
+ stats_line("lookahead table entries", lem.nlookaheadtab);
+ stats_line("total table size (bytes)", lem.tablesize);
+ }
+ if( lem.nconflict > 0 ){
+ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+ }
+
+ /* return 0 on success, 1 on failure. */
+ exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0;
+ exit(exitcode);
+ return (exitcode);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list. Then to sort the list call:
+**
+** ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp. The second argument is a pointer to the pointer to the
+** second element of the linked list. This address is used to compute
+** the offset to the "next" field within the structure. The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((char*)A)+offset))
+
+/*
+** Inputs:
+** a: A sorted, null-terminated linked list. (May be null).
+** b: A sorted, null-terminated linked list. (May be null).
+** cmp: A pointer to the comparison function.
+** offset: Offset in the structure to the "next" field.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** of both a and b.
+**
+** Side effects:
+** The "next" pointers for elements in the lists a and b are
+** changed.
+*/
+static char *merge(
+ char *a,
+ char *b,
+ int (*cmp)(const char*,const char*),
+ int offset
+){
+ char *ptr, *head;
+
+ if( a==0 ){
+ head = b;
+ }else if( b==0 ){
+ head = a;
+ }else{
+ if( (*cmp)(a,b)<=0 ){
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ ptr = b;
+ b = NEXT(b);
+ }
+ head = ptr;
+ while( a && b ){
+ if( (*cmp)(a,b)<=0 ){
+ NEXT(ptr) = a;
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ NEXT(ptr) = b;
+ ptr = b;
+ b = NEXT(b);
+ }
+ }
+ if( a ) NEXT(ptr) = a;
+ else NEXT(ptr) = b;
+ }
+ return head;
+}
+
+/*
+** Inputs:
+** list: Pointer to a singly-linked list of structures.
+** next: Pointer to pointer to the second element of the list.
+** cmp: A comparison function.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** originally in list.
+**
+** Side effects:
+** The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+static char *msort(
+ char *list,
+ char **next,
+ int (*cmp)(const char*,const char*)
+){
+ unsigned long offset;
+ char *ep;
+ char *set[LISTSIZE];
+ int i;
+ offset = (unsigned long)((char*)next - (char*)list);
+ for(i=0; i<LISTSIZE; i++) set[i] = 0;
+ while( list ){
+ ep = list;
+ list = NEXT(list);
+ NEXT(ep) = 0;
+ for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+ ep = merge(ep,set[i],cmp,offset);
+ set[i] = 0;
+ }
+ set[i] = ep;
+ }
+ ep = 0;
+ for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(set[i],ep,cmp,offset);
+ return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **g_argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(int n, int k, FILE *err)
+{
+ int spcnt, i;
+ if( g_argv[0] ){
+ fprintf(err,"%s",g_argv[0]);
+ spcnt = lemonStrlen(g_argv[0]) + 1;
+ }else{
+ spcnt = 0;
+ }
+ for(i=1; i<n && g_argv[i]; i++){
+ fprintf(err," %s",g_argv[i]);
+ spcnt += lemonStrlen(g_argv[i])+1;
+ }
+ spcnt += k;
+ for(; g_argv[i]; i++) fprintf(err," %s",g_argv[i]);
+ if( spcnt<20 ){
+ fprintf(err,"\n%*s^-- here\n",spcnt,"");
+ }else{
+ fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+ }
+}
+
+/*
+** Return the index of the N-th non-switch argument. Return -1
+** if N is out of range.
+*/
+static int argindex(int n)
+{
+ int i;
+ int dashdash = 0;
+ if( g_argv!=0 && *g_argv!=0 ){
+ for(i=1; g_argv[i]; i++){
+ if( dashdash || !ISOPT(g_argv[i]) ){
+ if( n==0 ) return i;
+ n--;
+ }
+ if( strcmp(g_argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(int i, FILE *err)
+{
+ int v;
+ int errcnt = 0;
+ int j;
+ for(j=0; op[j].label; j++){
+ if( strncmp(&g_argv[i][1],op[j].label,lemonStrlen(op[j].label))==0 ) break;
+ }
+ v = g_argv[i][0]=='-' ? 1 : 0;
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }else if( op[j].arg==0 ){
+ /* Ignore this option */
+ }else if( op[j].type==OPT_FLAG ){
+ *((int*)op[j].arg) = v;
+ }else if( op[j].type==OPT_FFLAG ){
+ (*(void(*)(int))(op[j].arg))(v);
+ }else if( op[j].type==OPT_FSTR ){
+ (*(void(*)(char *))(op[j].arg))(&g_argv[i][2]);
+ }else{
+ if( err ){
+ fprintf(err,"%smissing argument on switch.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }
+ return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(int i, FILE *err)
+{
+ int lv = 0;
+ double dv = 0.0;
+ char *sv = 0, *end;
+ char *cp;
+ int j;
+ int errcnt = 0;
+ cp = strchr(g_argv[i],'=');
+ assert( cp!=0 );
+ *cp = 0;
+ for(j=0; op[j].label; j++){
+ if( strcmp(g_argv[i],op[j].label)==0 ) break;
+ }
+ *cp = '=';
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ }else{
+ cp++;
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ if( err ){
+ fprintf(err,"%soption requires an argument.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ dv = strtod(cp,&end);
+ if( *end ){
+ if( err ){
+ fprintf(err,
+ "%sillegal character in floating-point argument.\n",emsg);
+ errline(i,(int)((char*)end-(char*)g_argv[i]),err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ lv = strtol(cp,&end,0);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in integer argument.\n",emsg);
+ errline(i,(int)((char*)end-(char*)g_argv[i]),err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ sv = cp;
+ break;
+ }
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_DBL:
+ *(double*)(op[j].arg) = dv;
+ break;
+ case OPT_FDBL:
+ (*(void(*)(double))(op[j].arg))(dv);
+ break;
+ case OPT_INT:
+ *(int*)(op[j].arg) = lv;
+ break;
+ case OPT_FINT:
+ (*(void(*)(int))(op[j].arg))((int)lv);
+ break;
+ case OPT_STR:
+ *(char**)(op[j].arg) = sv;
+ break;
+ case OPT_FSTR:
+ (*(void(*)(char *))(op[j].arg))(sv);
+ break;
+ }
+ }
+ return errcnt;
+}
+
+int OptInit(char **a, struct s_options *o, FILE *err)
+{
+ int errcnt = 0;
+ g_argv = a;
+ op = o;
+ errstream = err;
+ if( g_argv && *g_argv && op ){
+ int i;
+ for(i=1; g_argv[i]; i++){
+ if( g_argv[i][0]=='+' || g_argv[i][0]=='-' ){
+ errcnt += handleflags(i,err);
+ }else if( strchr(g_argv[i],'=') ){
+ errcnt += handleswitch(i,err);
+ }
+ }
+ }
+ if( errcnt>0 ){
+ fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+ OptPrint();
+ exit(1);
+ }
+ return 0;
+}
+
+int OptNArgs(void){
+ int cnt = 0;
+ int dashdash = 0;
+ int i;
+ if( g_argv!=0 && g_argv[0]!=0 ){
+ for(i=1; g_argv[i]; i++){
+ if( dashdash || !ISOPT(g_argv[i]) ) cnt++;
+ if( strcmp(g_argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return cnt;
+}
+
+char *OptArg(int n)
+{
+ int i;
+ i = argindex(n);
+ return i>=0 ? g_argv[i] : 0;
+}
+
+void OptErr(int n)
+{
+ int i;
+ i = argindex(n);
+ if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(void){
+ int i;
+ int max, len;
+ max = 0;
+ for(i=0; op[i].label; i++){
+ len = lemonStrlen(op[i].label) + 1;
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ len += 9; /* length of "<integer>" */
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ len += 6; /* length of "<real>" */
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ len += 8; /* length of "<string>" */
+ break;
+ }
+ if( len>max ) max = len;
+ }
+ for(i=0; op[i].label; i++){
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ fprintf(errstream," -%-*s %s\n",max,op[i].label,op[i].message);
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ fprintf(errstream," -%s<integer>%*s %s\n",op[i].label,
+ (int)(max-lemonStrlen(op[i].label)-9),"",op[i].message);
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ fprintf(errstream," -%s<real>%*s %s\n",op[i].label,
+ (int)(max-lemonStrlen(op[i].label)-6),"",op[i].message);
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ fprintf(errstream," -%s<string>%*s %s\n",op[i].label,
+ (int)(max-lemonStrlen(op[i].label)-8),"",op[i].message);
+ break;
+ }
+ }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+enum e_state {
+ INITIALIZE,
+ WAITING_FOR_DECL_OR_RULE,
+ WAITING_FOR_DECL_KEYWORD,
+ WAITING_FOR_DECL_ARG,
+ WAITING_FOR_PRECEDENCE_SYMBOL,
+ WAITING_FOR_ARROW,
+ IN_RHS,
+ LHS_ALIAS_1,
+ LHS_ALIAS_2,
+ LHS_ALIAS_3,
+ RHS_ALIAS_1,
+ RHS_ALIAS_2,
+ PRECEDENCE_MARK_1,
+ PRECEDENCE_MARK_2,
+ RESYNC_AFTER_RULE_ERROR,
+ RESYNC_AFTER_DECL_ERROR,
+ WAITING_FOR_DESTRUCTOR_SYMBOL,
+ WAITING_FOR_DATATYPE_SYMBOL,
+ WAITING_FOR_FALLBACK_ID,
+ WAITING_FOR_WILDCARD_ID,
+ WAITING_FOR_CLASS_ID,
+ WAITING_FOR_CLASS_TOKEN,
+ WAITING_FOR_TOKEN_NAME
+};
+struct pstate {
+ char *filename; /* Name of the input file */
+ int tokenlineno; /* Linenumber at which current token starts */
+ int errorcnt; /* Number of errors so far */
+ char *tokenstart; /* Text of current token */
+ struct lemon *gp; /* Global state vector */
+ enum e_state state; /* The state of the parser */
+ struct symbol *fallback; /* The fallback token */
+ struct symbol *tkclass; /* Token class symbol */
+ struct symbol *lhs; /* Left-hand side of current rule */
+ const char *lhsalias; /* Alias for the LHS */
+ int nrhs; /* Number of right-hand side symbols seen */
+ struct symbol *rhs[MAXRHS]; /* RHS symbols */
+ const char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */
+ struct rule *prevrule; /* Previous rule parsed */
+ const char *declkeyword; /* Keyword of a declaration */
+ char **declargslot; /* Where the declaration argument should be put */
+ int insertLineMacro; /* Add #line before declaration insert */
+ int *decllinenoslot; /* Where to write declaration line number */
+ enum e_assoc declassoc; /* Assign this association to decl arguments */
+ int preccounter; /* Assign this precedence to decl arguments */
+ struct rule *firstrule; /* Pointer to first rule in the grammar */
+ struct rule *lastrule; /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(struct pstate *psp)
+{
+ const char *x;
+ x = Strsafe(psp->tokenstart); /* Save the token permanently */
+#if 0
+ printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+ x,psp->state);
+#endif
+ switch( psp->state ){
+ case INITIALIZE:
+ psp->prevrule = 0;
+ psp->preccounter = 0;
+ psp->firstrule = psp->lastrule = 0;
+ psp->gp->nrule = 0;
+ /* fall through */
+ case WAITING_FOR_DECL_OR_RULE:
+ if( x[0]=='%' ){
+ psp->state = WAITING_FOR_DECL_KEYWORD;
+ }else if( ISLOWER(x[0]) ){
+ psp->lhs = Symbol_new(x);
+ psp->nrhs = 0;
+ psp->lhsalias = 0;
+ psp->state = WAITING_FOR_ARROW;
+ }else if( x[0]=='{' ){
+ if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "There is no prior rule upon which to attach the code "
+ "fragment which begins on this line.");
+ psp->errorcnt++;
+ }else if( psp->prevrule->code!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Code fragment beginning on this line is not the first "
+ "to follow the previous rule.");
+ psp->errorcnt++;
+ }else if( strcmp(x, "{NEVER-REDUCE")==0 ){
+ psp->prevrule->neverReduce = 1;
+ }else{
+ psp->prevrule->line = psp->tokenlineno;
+ psp->prevrule->code = &x[1];
+ psp->prevrule->noCode = 0;
+ }
+ }else if( x[0]=='[' ){
+ psp->state = PRECEDENCE_MARK_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+ x);
+ psp->errorcnt++;
+ }
+ break;
+ case PRECEDENCE_MARK_1:
+ if( !ISUPPER(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The precedence symbol must be a terminal.");
+ psp->errorcnt++;
+ }else if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "There is no prior rule to assign precedence \"[%s]\".",x);
+ psp->errorcnt++;
+ }else if( psp->prevrule->precsym!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Precedence mark on this line is not the first "
+ "to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->precsym = Symbol_new(x);
+ }
+ psp->state = PRECEDENCE_MARK_2;
+ break;
+ case PRECEDENCE_MARK_2:
+ if( x[0]!=']' ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"]\" on precedence mark.");
+ psp->errorcnt++;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ break;
+ case WAITING_FOR_ARROW:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else if( x[0]=='(' ){
+ psp->state = LHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Expected to see a \":\" following the LHS symbol \"%s\".",
+ psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_1:
+ if( ISALPHA(x[0]) ){
+ psp->lhsalias = x;
+ psp->state = LHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+ x,psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = LHS_ALIAS_3;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_3:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"->\" following: \"%s(%s)\".",
+ psp->lhs->name,psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case IN_RHS:
+ if( x[0]=='.' ){
+ struct rule *rp;
+ rp = (struct rule *)calloc( sizeof(struct rule) +
+ sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1);
+ if( rp==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't allocate enough memory for this rule.");
+ psp->errorcnt++;
+ psp->prevrule = 0;
+ }else{
+ int i;
+ rp->ruleline = psp->tokenlineno;
+ rp->rhs = (struct symbol**)&rp[1];
+ rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]);
+ for(i=0; i<psp->nrhs; i++){
+ rp->rhs[i] = psp->rhs[i];
+ rp->rhsalias[i] = psp->alias[i];
+ if( rp->rhsalias[i]!=0 ){ rp->rhs[i]->bContent = 1; }
+ }
+ rp->lhs = psp->lhs;
+ rp->lhsalias = psp->lhsalias;
+ rp->nrhs = psp->nrhs;
+ rp->code = 0;
+ rp->noCode = 1;
+ rp->precsym = 0;
+ rp->index = psp->gp->nrule++;
+ rp->nextlhs = rp->lhs->rule;
+ rp->lhs->rule = rp;
+ rp->next = 0;
+ if( psp->firstrule==0 ){
+ psp->firstrule = psp->lastrule = rp;
+ }else{
+ psp->lastrule->next = rp;
+ psp->lastrule = rp;
+ }
+ psp->prevrule = rp;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( ISALPHA(x[0]) ){
+ if( psp->nrhs>=MAXRHS ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Too many symbols on RHS of rule beginning at \"%s\".",
+ x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }else{
+ psp->rhs[psp->nrhs] = Symbol_new(x);
+ psp->alias[psp->nrhs] = 0;
+ psp->nrhs++;
+ }
+ }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 && ISUPPER(x[1]) ){
+ struct symbol *msp = psp->rhs[psp->nrhs-1];
+ if( msp->type!=MULTITERMINAL ){
+ struct symbol *origsp = msp;
+ msp = (struct symbol *) calloc(1,sizeof(*msp));
+ memset(msp, 0, sizeof(*msp));
+ msp->type = MULTITERMINAL;
+ msp->nsubsym = 1;
+ msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*));
+ msp->subsym[0] = origsp;
+ msp->name = origsp->name;
+ psp->rhs[psp->nrhs-1] = msp;
+ }
+ msp->nsubsym++;
+ msp->subsym = (struct symbol **) realloc(msp->subsym,
+ sizeof(struct symbol*)*msp->nsubsym);
+ msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]);
+ if( ISLOWER(x[1]) || ISLOWER(msp->subsym[0]->name[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Cannot form a compound containing a non-terminal");
+ psp->errorcnt++;
+ }
+ }else if( x[0]=='(' && psp->nrhs>0 ){
+ psp->state = RHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal character on RHS of rule: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_1:
+ if( ISALPHA(x[0]) ){
+ psp->alias[psp->nrhs-1] = x;
+ psp->state = RHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+ x,psp->rhs[psp->nrhs-1]->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case WAITING_FOR_DECL_KEYWORD:
+ if( ISALPHA(x[0]) ){
+ psp->declkeyword = x;
+ psp->declargslot = 0;
+ psp->decllinenoslot = 0;
+ psp->insertLineMacro = 1;
+ psp->state = WAITING_FOR_DECL_ARG;
+ if( strcmp(x,"name")==0 ){
+ psp->declargslot = &(psp->gp->name);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"include")==0 ){
+ psp->declargslot = &(psp->gp->include);
+ }else if( strcmp(x,"code")==0 ){
+ psp->declargslot = &(psp->gp->extracode);
+ }else if( strcmp(x,"token_destructor")==0 ){
+ psp->declargslot = &psp->gp->tokendest;
+ }else if( strcmp(x,"default_destructor")==0 ){
+ psp->declargslot = &psp->gp->vardest;
+ }else if( strcmp(x,"token_prefix")==0 ){
+ psp->declargslot = &psp->gp->tokenprefix;
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"syntax_error")==0 ){
+ psp->declargslot = &(psp->gp->error);
+ }else if( strcmp(x,"parse_accept")==0 ){
+ psp->declargslot = &(psp->gp->accept);
+ }else if( strcmp(x,"parse_failure")==0 ){
+ psp->declargslot = &(psp->gp->failure);
+ }else if( strcmp(x,"stack_overflow")==0 ){
+ psp->declargslot = &(psp->gp->overflow);
+ }else if( strcmp(x,"extra_argument")==0 ){
+ psp->declargslot = &(psp->gp->arg);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"extra_context")==0 ){
+ psp->declargslot = &(psp->gp->ctx);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"token_type")==0 ){
+ psp->declargslot = &(psp->gp->tokentype);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"default_type")==0 ){
+ psp->declargslot = &(psp->gp->vartype);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"stack_size")==0 ){
+ psp->declargslot = &(psp->gp->stacksize);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"start_symbol")==0 ){
+ psp->declargslot = &(psp->gp->start);
+ psp->insertLineMacro = 0;
+ }else if( strcmp(x,"left")==0 ){
+ psp->preccounter++;
+ psp->declassoc = LEFT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"right")==0 ){
+ psp->preccounter++;
+ psp->declassoc = RIGHT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"nonassoc")==0 ){
+ psp->preccounter++;
+ psp->declassoc = NONE;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"destructor")==0 ){
+ psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+ }else if( strcmp(x,"type")==0 ){
+ psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+ }else if( strcmp(x,"fallback")==0 ){
+ psp->fallback = 0;
+ psp->state = WAITING_FOR_FALLBACK_ID;
+ }else if( strcmp(x,"token")==0 ){
+ psp->state = WAITING_FOR_TOKEN_NAME;
+ }else if( strcmp(x,"wildcard")==0 ){
+ psp->state = WAITING_FOR_WILDCARD_ID;
+ }else if( strcmp(x,"token_class")==0 ){
+ psp->state = WAITING_FOR_CLASS_ID;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Unknown declaration keyword: \"%%%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal declaration keyword: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_DESTRUCTOR_SYMBOL:
+ if( !ISALPHA(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %%destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->destructor;
+ psp->decllinenoslot = &sp->destLineno;
+ psp->insertLineMacro = 1;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_DATATYPE_SYMBOL:
+ if( !ISALPHA(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %%type keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_find(x);
+ if((sp) && (sp->datatype)){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol %%type \"%s\" already defined", x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ if (!sp){
+ sp = Symbol_new(x);
+ }
+ psp->declargslot = &sp->datatype;
+ psp->insertLineMacro = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ }
+ break;
+ case WAITING_FOR_PRECEDENCE_SYMBOL:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( ISUPPER(x[0]) ){
+ struct symbol *sp;
+ sp = Symbol_new(x);
+ if( sp->prec>=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol \"%s\" has already be given a precedence.",x);
+ psp->errorcnt++;
+ }else{
+ sp->prec = psp->preccounter;
+ sp->assoc = psp->declassoc;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't assign a precedence to \"%s\".",x);
+ psp->errorcnt++;
+ }
+ break;
+ case WAITING_FOR_DECL_ARG:
+ if( x[0]=='{' || x[0]=='\"' || ISALNUM(x[0]) ){
+ const char *zOld, *zNew;
+ char *zBuf, *z;
+ int nOld, n, nLine = 0, nNew, nBack;
+ int addLineMacro;
+ char zLine[50];
+ zNew = x;
+ if( zNew[0]=='"' || zNew[0]=='{' ) zNew++;
+ nNew = lemonStrlen(zNew);
+ if( *psp->declargslot ){
+ zOld = *psp->declargslot;
+ }else{
+ zOld = "";
+ }
+ nOld = lemonStrlen(zOld);
+ n = nOld + nNew + 20;
+ addLineMacro = !psp->gp->nolinenosflag
+ && psp->insertLineMacro
+ && psp->tokenlineno>1
+ && (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0);
+ if( addLineMacro ){
+ for(z=psp->filename, nBack=0; *z; z++){
+ if( *z=='\\' ) nBack++;
+ }
+ lemon_sprintf(zLine, "#line %d ", psp->tokenlineno);
+ nLine = lemonStrlen(zLine);
+ n += nLine + lemonStrlen(psp->filename) + nBack;
+ }
+ *psp->declargslot = (char *) realloc(*psp->declargslot, n);
+ zBuf = *psp->declargslot + nOld;
+ if( addLineMacro ){
+ if( nOld && zBuf[-1]!='\n' ){
+ *(zBuf++) = '\n';
+ }
+ memcpy(zBuf, zLine, nLine);
+ zBuf += nLine;
+ *(zBuf++) = '"';
+ for(z=psp->filename; *z; z++){
+ if( *z=='\\' ){
+ *(zBuf++) = '\\';
+ }
+ *(zBuf++) = *z;
+ }
+ *(zBuf++) = '"';
+ *(zBuf++) = '\n';
+ }
+ if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){
+ psp->decllinenoslot[0] = psp->tokenlineno;
+ }
+ memcpy(zBuf, zNew, nNew);
+ zBuf += nNew;
+ *zBuf = 0;
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal argument to %%%s: %s",psp->declkeyword,x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_FALLBACK_ID:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !ISUPPER(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%fallback argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ if( psp->fallback==0 ){
+ psp->fallback = sp;
+ }else if( sp->fallback ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "More than one fallback assigned to token %s", x);
+ psp->errorcnt++;
+ }else{
+ sp->fallback = psp->fallback;
+ psp->gp->has_fallback = 1;
+ }
+ }
+ break;
+ case WAITING_FOR_TOKEN_NAME:
+ /* Tokens do not have to be declared before use. But they can be
+ ** in order to control their assigned integer number. The number for
+ ** each token is assigned when it is first seen. So by including
+ **
+ ** %token ONE TWO THREE.
+ **
+ ** early in the grammar file, that assigns small consecutive values
+ ** to each of the tokens ONE TWO and THREE.
+ */
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !ISUPPER(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%token argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ (void)Symbol_new(x);
+ }
+ break;
+ case WAITING_FOR_WILDCARD_ID:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !ISUPPER(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%wildcard argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ if( psp->gp->wildcard==0 ){
+ psp->gp->wildcard = sp;
+ }else{
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "Extra wildcard to token: %s", x);
+ psp->errorcnt++;
+ }
+ }
+ break;
+ case WAITING_FOR_CLASS_ID:
+ if( !ISLOWER(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%token_class must be followed by an identifier: %s", x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else if( Symbol_find(x) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "Symbol \"%s\" already used", x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ psp->tkclass = Symbol_new(x);
+ psp->tkclass->type = MULTITERMINAL;
+ psp->state = WAITING_FOR_CLASS_TOKEN;
+ }
+ break;
+ case WAITING_FOR_CLASS_TOKEN:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( ISUPPER(x[0]) || ((x[0]=='|' || x[0]=='/') && ISUPPER(x[1])) ){
+ struct symbol *msp = psp->tkclass;
+ msp->nsubsym++;
+ msp->subsym = (struct symbol **) realloc(msp->subsym,
+ sizeof(struct symbol*)*msp->nsubsym);
+ if( !ISUPPER(x[0]) ) x++;
+ msp->subsym[msp->nsubsym-1] = Symbol_new(x);
+ }else{
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%token_class argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case RESYNC_AFTER_RULE_ERROR:
+/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+** break; */
+ case RESYNC_AFTER_DECL_ERROR:
+ if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+ if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+ break;
+ }
+}
+
+/* The text in the input is part of the argument to an %ifdef or %ifndef.
+** Evaluate the text as a boolean expression. Return true or false.
+*/
+static int eval_preprocessor_boolean(char *z, int lineno){
+ int neg = 0;
+ int res = 0;
+ int okTerm = 1;
+ int i;
+ for(i=0; z[i]!=0; i++){
+ if( ISSPACE(z[i]) ) continue;
+ if( z[i]=='!' ){
+ if( !okTerm ) goto pp_syntax_error;
+ neg = !neg;
+ continue;
+ }
+ if( z[i]=='|' && z[i+1]=='|' ){
+ if( okTerm ) goto pp_syntax_error;
+ if( res ) return 1;
+ i++;
+ okTerm = 1;
+ continue;
+ }
+ if( z[i]=='&' && z[i+1]=='&' ){
+ if( okTerm ) goto pp_syntax_error;
+ if( !res ) return 0;
+ i++;
+ okTerm = 1;
+ continue;
+ }
+ if( z[i]=='(' ){
+ int k;
+ int n = 1;
+ if( !okTerm ) goto pp_syntax_error;
+ for(k=i+1; z[k]; k++){
+ if( z[k]==')' ){
+ n--;
+ if( n==0 ){
+ z[k] = 0;
+ res = eval_preprocessor_boolean(&z[i+1], -1);
+ z[k] = ')';
+ if( res<0 ){
+ i = i-res;
+ goto pp_syntax_error;
+ }
+ i = k;
+ break;
+ }
+ }else if( z[k]=='(' ){
+ n++;
+ }else if( z[k]==0 ){
+ i = k;
+ goto pp_syntax_error;
+ }
+ }
+ if( neg ){
+ res = !res;
+ neg = 0;
+ }
+ okTerm = 0;
+ continue;
+ }
+ if( ISALPHA(z[i]) ){
+ int j, k, n;
+ if( !okTerm ) goto pp_syntax_error;
+ for(k=i+1; ISALNUM(z[k]) || z[k]=='_'; k++){}
+ n = k - i;
+ res = 0;
+ for(j=0; j<nDefine; j++){
+ if( strncmp(azDefine[j],&z[i],n)==0 && azDefine[j][n]==0 ){
+ if( !bDefineUsed[j] ){
+ bDefineUsed[j] = 1;
+ nDefineUsed++;
+ }
+ res = 1;
+ break;
+ }
+ }
+ i = k-1;
+ if( neg ){
+ res = !res;
+ neg = 0;
+ }
+ okTerm = 0;
+ continue;
+ }
+ goto pp_syntax_error;
+ }
+ return res;
+
+pp_syntax_error:
+ if( lineno>0 ){
+ fprintf(stderr, "%%if syntax error on line %d.\n", lineno);
+ fprintf(stderr, " %.*s <-- syntax error here\n", i+1, z);
+ exit(1);
+ }else{
+ return -(i+1);
+ }
+}
+
+/* Run the preprocessor over the input file text. The global variables
+** azDefine[0] through azDefine[nDefine-1] contains the names of all defined
+** macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and
+** comments them out. Text in between is also commented out as appropriate.
+*/
+static void preprocess_input(char *z){
+ int i, j, k;
+ int exclude = 0;
+ int start = 0;
+ int lineno = 1;
+ int start_lineno = 1;
+ for(i=0; z[i]; i++){
+ if( z[i]=='\n' ) lineno++;
+ if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue;
+ if( strncmp(&z[i],"%endif",6)==0 && ISSPACE(z[i+6]) ){
+ if( exclude ){
+ exclude--;
+ if( exclude==0 ){
+ for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+ }
+ }
+ for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+ }else if( strncmp(&z[i],"%else",5)==0 && ISSPACE(z[i+5]) ){
+ if( exclude==1){
+ exclude = 0;
+ for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+ }else if( exclude==0 ){
+ exclude = 1;
+ start = i;
+ start_lineno = lineno;
+ }
+ for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+ }else if( strncmp(&z[i],"%ifdef ",7)==0
+ || strncmp(&z[i],"%if ",4)==0
+ || strncmp(&z[i],"%ifndef ",8)==0 ){
+ if( exclude ){
+ exclude++;
+ }else{
+ int isNot;
+ int iBool;
+ for(j=i; z[j] && !ISSPACE(z[j]); j++){}
+ iBool = j;
+ isNot = (j==i+7);
+ while( z[j] && z[j]!='\n' ){ j++; }
+ k = z[j];
+ z[j] = 0;
+ exclude = eval_preprocessor_boolean(&z[iBool], lineno);
+ z[j] = k;
+ if( !isNot ) exclude = !exclude;
+ if( exclude ){
+ start = i;
+ start_lineno = lineno;
+ }
+ }
+ for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+ }
+ }
+ if( exclude ){
+ fprintf(stderr,"unterminated %%ifdef starting on line %d\n", start_lineno);
+ exit(1);
+ }
+}
+
+/* In spite of its name, this function is really a scanner. It read
+** in the entire input file (all at once) then tokenizes it. Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(struct lemon *gp)
+{
+ struct pstate ps;
+ FILE *fp;
+ char *filebuf;
+ unsigned int filesize;
+ int lineno;
+ int c;
+ char *cp, *nextcp;
+ int startline = 0;
+
+ memset(&ps, '\0', sizeof(ps));
+ ps.gp = gp;
+ ps.filename = gp->filename;
+ ps.errorcnt = 0;
+ ps.state = INITIALIZE;
+
+ /* Begin by reading the input file */
+ fp = fopen(ps.filename,"rb");
+ if( fp==0 ){
+ ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+ gp->errorcnt++;
+ return;
+ }
+ fseek(fp,0,2);
+ filesize = ftell(fp);
+ rewind(fp);
+ filebuf = (char *)malloc( filesize+1 );
+ if( filesize>100000000 || filebuf==0 ){
+ ErrorMsg(ps.filename,0,"Input file too large.");
+ free(filebuf);
+ gp->errorcnt++;
+ fclose(fp);
+ return;
+ }
+ if( fread(filebuf,1,filesize,fp)!=filesize ){
+ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+ filesize);
+ free(filebuf);
+ gp->errorcnt++;
+ fclose(fp);
+ return;
+ }
+ fclose(fp);
+ filebuf[filesize] = 0;
+
+ /* Make an initial pass through the file to handle %ifdef and %ifndef */
+ preprocess_input(filebuf);
+ if( gp->printPreprocessed ){
+ printf("%s\n", filebuf);
+ return;
+ }
+
+ /* Now scan the text of the input file */
+ lineno = 1;
+ for(cp=filebuf; (c= *cp)!=0; ){
+ if( c=='\n' ) lineno++; /* Keep track of the line number */
+ if( ISSPACE(c) ){ cp++; continue; } /* Skip all white space */
+ if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ continue;
+ }
+ if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */
+ cp+=2;
+ if( (*cp)=='/' ) cp++;
+ while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c ) cp++;
+ continue;
+ }
+ ps.tokenstart = cp; /* Mark the beginning of the token */
+ ps.tokenlineno = lineno; /* Linenumber on which token begins */
+ if( c=='\"' ){ /* String literals */
+ cp++;
+ while( (c= *cp)!=0 && c!='\"' ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,startline,
+ "String starting on this line is not terminated before "
+ "the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( c=='{' ){ /* A block of C code */
+ int level;
+ cp++;
+ for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+ if( c=='\n' ) lineno++;
+ else if( c=='{' ) level++;
+ else if( c=='}' ) level--;
+ else if( c=='/' && cp[1]=='*' ){ /* Skip comments */
+ int prevc;
+ cp = &cp[2];
+ prevc = 0;
+ while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+ if( c=='\n' ) lineno++;
+ prevc = c;
+ cp++;
+ }
+ }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */
+ cp = &cp[2];
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ if( c ) lineno++;
+ }else if( c=='\'' || c=='\"' ){ /* String a character literals */
+ int startchar, prevc;
+ startchar = c;
+ prevc = 0;
+ for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+ if( c=='\n' ) lineno++;
+ if( prevc=='\\' ) prevc = 0;
+ else prevc = c;
+ }
+ }
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,ps.tokenlineno,
+ "C code starting on this line is not terminated before "
+ "the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( ISALNUM(c) ){ /* Identifiers */
+ while( (c= *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++;
+ nextcp = cp;
+ }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+ cp += 3;
+ nextcp = cp;
+ }else if( (c=='/' || c=='|') && ISALPHA(cp[1]) ){
+ cp += 2;
+ while( (c = *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++;
+ nextcp = cp;
+ }else{ /* All other (one character) operators */
+ cp++;
+ nextcp = cp;
+ }
+ c = *cp;
+ *cp = 0; /* Null terminate the token */
+ parseonetoken(&ps); /* Parse the token */
+ *cp = (char)c; /* Restore the buffer */
+ cp = nextcp;
+ }
+ free(filebuf); /* Release the buffer after parsing */
+ gp->rule = ps.firstrule;
+ gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(void){
+ struct plink *newlink;
+
+ if( plink_freelist==0 ){
+ int i;
+ int amt = 100;
+ plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) );
+ if( plink_freelist==0 ){
+ fprintf(stderr,
+ "Unable to allocate memory for a new follow-set propagation link.\n");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+ plink_freelist[amt-1].next = 0;
+ }
+ newlink = plink_freelist;
+ plink_freelist = plink_freelist->next;
+ return newlink;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(struct plink **plpp, struct config *cfp)
+{
+ struct plink *newlink;
+ newlink = Plink_new();
+ newlink->next = *plpp;
+ *plpp = newlink;
+ newlink->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(struct plink **to, struct plink *from)
+{
+ struct plink *nextpl;
+ while( from ){
+ nextpl = from->next;
+ from->next = *to;
+ *to = from;
+ from = nextpl;
+ }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(struct plink *plp)
+{
+ struct plink *nextpl;
+
+ while( plp ){
+ nextpl = plp->next;
+ plp->next = plink_freelist;
+ plink_freelist = plp;
+ plp = nextpl;
+ }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix. Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(struct lemon *lemp, const char *suffix)
+{
+ char *name;
+ char *cp;
+ char *filename = lemp->filename;
+ int sz;
+
+ if( outputDir ){
+ cp = strrchr(filename, '/');
+ if( cp ) filename = cp + 1;
+ }
+ sz = lemonStrlen(filename);
+ sz += lemonStrlen(suffix);
+ if( outputDir ) sz += lemonStrlen(outputDir) + 1;
+ sz += 5;
+ name = (char*)malloc( sz );
+ if( name==0 ){
+ fprintf(stderr,"Can't allocate space for a filename.\n");
+ exit(1);
+ }
+ name[0] = 0;
+ if( outputDir ){
+ lemon_strcpy(name, outputDir);
+ lemon_strcat(name, "/");
+ }
+ lemon_strcat(name,filename);
+ cp = strrchr(name,'.');
+ if( cp ) *cp = 0;
+ lemon_strcat(name,suffix);
+ return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(
+ struct lemon *lemp,
+ const char *suffix,
+ const char *mode
+){
+ FILE *fp;
+
+ if( lemp->outname ) free(lemp->outname);
+ lemp->outname = file_makename(lemp, suffix);
+ fp = fopen(lemp->outname,mode);
+ if( fp==0 && *mode=='w' ){
+ fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return fp;
+}
+
+/* Print the text of a rule
+*/
+void rule_print(FILE *out, struct rule *rp){
+ int i, j;
+ fprintf(out, "%s",rp->lhs->name);
+ /* if( rp->lhsalias ) fprintf(out,"(%s)",rp->lhsalias); */
+ fprintf(out," ::=");
+ for(i=0; i<rp->nrhs; i++){
+ struct symbol *sp = rp->rhs[i];
+ if( sp->type==MULTITERMINAL ){
+ fprintf(out," %s", sp->subsym[0]->name);
+ for(j=1; j<sp->nsubsym; j++){
+ fprintf(out,"|%s", sp->subsym[j]->name);
+ }
+ }else{
+ fprintf(out," %s", sp->name);
+ }
+ /* if( rp->rhsalias[i] ) fprintf(out,"(%s)",rp->rhsalias[i]); */
+ }
+}
+
+/* Duplicate the input file without comments and without actions
+** on rules */
+void Reprint(struct lemon *lemp)
+{
+ struct rule *rp;
+ struct symbol *sp;
+ int i, j, maxlen, len, ncolumns, skip;
+ printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+ maxlen = 10;
+ for(i=0; i<lemp->nsymbol; i++){
+ sp = lemp->symbols[i];
+ len = lemonStrlen(sp->name);
+ if( len>maxlen ) maxlen = len;
+ }
+ ncolumns = 76/(maxlen+5);
+ if( ncolumns<1 ) ncolumns = 1;
+ skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+ for(i=0; i<skip; i++){
+ printf("//");
+ for(j=i; j<lemp->nsymbol; j+=skip){
+ sp = lemp->symbols[j];
+ assert( sp->index==j );
+ printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+ }
+ printf("\n");
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ rule_print(stdout, rp);
+ printf(".");
+ if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+ /* if( rp->code ) printf("\n %s",rp->code); */
+ printf("\n");
+ }
+}
+
+/* Print a single rule.
+*/
+void RulePrint(FILE *fp, struct rule *rp, int iCursor){
+ struct symbol *sp;
+ int i, j;
+ fprintf(fp,"%s ::=",rp->lhs->name);
+ for(i=0; i<=rp->nrhs; i++){
+ if( i==iCursor ) fprintf(fp," *");
+ if( i==rp->nrhs ) break;
+ sp = rp->rhs[i];
+ if( sp->type==MULTITERMINAL ){
+ fprintf(fp," %s", sp->subsym[0]->name);
+ for(j=1; j<sp->nsubsym; j++){
+ fprintf(fp,"|%s",sp->subsym[j]->name);
+ }
+ }else{
+ fprintf(fp," %s", sp->name);
+ }
+ }
+}
+
+/* Print the rule for a configuration.
+*/
+void ConfigPrint(FILE *fp, struct config *cfp){
+ RulePrint(fp, cfp->rp, cfp->dot);
+}
+
+/* #define TEST */
+#if 0
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+ int i;
+ char *spacer;
+ spacer = "";
+ fprintf(out,"%12s[","");
+ for(i=0; i<lemp->nterminal; i++){
+ if( SetFind(set,i) ){
+ fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+ spacer = " ";
+ }
+ }
+ fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+ while( plp ){
+ fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum);
+ ConfigPrint(out,plp->cfp);
+ fprintf(out,"\n");
+ plp = plp->next;
+ }
+}
+#endif
+
+/* Print an action to the given file descriptor. Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(
+ struct action *ap, /* The action to print */
+ FILE *fp, /* Print the action here */
+ int indent /* Indent by this amount */
+){
+ int result = 1;
+ switch( ap->type ){
+ case SHIFT: {
+ struct state *stp = ap->x.stp;
+ fprintf(fp,"%*s shift %-7d",indent,ap->sp->name,stp->statenum);
+ break;
+ }
+ case REDUCE: {
+ struct rule *rp = ap->x.rp;
+ fprintf(fp,"%*s reduce %-7d",indent,ap->sp->name,rp->iRule);
+ RulePrint(fp, rp, -1);
+ break;
+ }
+ case SHIFTREDUCE: {
+ struct rule *rp = ap->x.rp;
+ fprintf(fp,"%*s shift-reduce %-7d",indent,ap->sp->name,rp->iRule);
+ RulePrint(fp, rp, -1);
+ break;
+ }
+ case ACCEPT:
+ fprintf(fp,"%*s accept",indent,ap->sp->name);
+ break;
+ case ERROR:
+ fprintf(fp,"%*s error",indent,ap->sp->name);
+ break;
+ case SRCONFLICT:
+ case RRCONFLICT:
+ fprintf(fp,"%*s reduce %-7d ** Parsing conflict **",
+ indent,ap->sp->name,ap->x.rp->iRule);
+ break;
+ case SSCONFLICT:
+ fprintf(fp,"%*s shift %-7d ** Parsing conflict **",
+ indent,ap->sp->name,ap->x.stp->statenum);
+ break;
+ case SH_RESOLVED:
+ if( showPrecedenceConflict ){
+ fprintf(fp,"%*s shift %-7d -- dropped by precedence",
+ indent,ap->sp->name,ap->x.stp->statenum);
+ }else{
+ result = 0;
+ }
+ break;
+ case RD_RESOLVED:
+ if( showPrecedenceConflict ){
+ fprintf(fp,"%*s reduce %-7d -- dropped by precedence",
+ indent,ap->sp->name,ap->x.rp->iRule);
+ }else{
+ result = 0;
+ }
+ break;
+ case NOT_USED:
+ result = 0;
+ break;
+ }
+ if( result && ap->spOpt ){
+ fprintf(fp," /* because %s==%s */", ap->sp->name, ap->spOpt->name);
+ }
+ return result;
+}
+
+/* Generate the "*.out" log file */
+void ReportOutput(struct lemon *lemp)
+{
+ int i, n;
+ struct state *stp;
+ struct config *cfp;
+ struct action *ap;
+ struct rule *rp;
+ FILE *fp;
+
+ fp = file_open(lemp,".out","wb");
+ if( fp==0 ) return;
+ for(i=0; i<lemp->nxstate; i++){
+ stp = lemp->sorted[i];
+ fprintf(fp,"State %d:\n",stp->statenum);
+ if( lemp->basisflag ) cfp=stp->bp;
+ else cfp=stp->cfp;
+ while( cfp ){
+ char buf[20];
+ if( cfp->dot==cfp->rp->nrhs ){
+ lemon_sprintf(buf,"(%d)",cfp->rp->iRule);
+ fprintf(fp," %5s ",buf);
+ }else{
+ fprintf(fp," ");
+ }
+ ConfigPrint(fp,cfp);
+ fprintf(fp,"\n");
+#if 0
+ SetPrint(fp,cfp->fws,lemp);
+ PlinkPrint(fp,cfp->fplp,"To ");
+ PlinkPrint(fp,cfp->bplp,"From");
+#endif
+ if( lemp->basisflag ) cfp=cfp->bp;
+ else cfp=cfp->next;
+ }
+ fprintf(fp,"\n");
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+ }
+ fprintf(fp,"\n");
+ }
+ fprintf(fp, "----------------------------------------------------\n");
+ fprintf(fp, "Symbols:\n");
+ fprintf(fp, "The first-set of non-terminals is shown after the name.\n\n");
+ for(i=0; i<lemp->nsymbol; i++){
+ int j;
+ struct symbol *sp;
+
+ sp = lemp->symbols[i];
+ fprintf(fp, " %3d: %s", i, sp->name);
+ if( sp->type==NONTERMINAL ){
+ fprintf(fp, ":");
+ if( sp->lambda ){
+ fprintf(fp, " <lambda>");
+ }
+ for(j=0; j<lemp->nterminal; j++){
+ if( sp->firstset && SetFind(sp->firstset, j) ){
+ fprintf(fp, " %s", lemp->symbols[j]->name);
+ }
+ }
+ }
+ if( sp->prec>=0 ) fprintf(fp," (precedence=%d)", sp->prec);
+ fprintf(fp, "\n");
+ }
+ fprintf(fp, "----------------------------------------------------\n");
+ fprintf(fp, "Syntax-only Symbols:\n");
+ fprintf(fp, "The following symbols never carry semantic content.\n\n");
+ for(i=n=0; i<lemp->nsymbol; i++){
+ int w;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->bContent ) continue;
+ w = (int)strlen(sp->name);
+ if( n>0 && n+w>75 ){
+ fprintf(fp,"\n");
+ n = 0;
+ }
+ if( n>0 ){
+ fprintf(fp, " ");
+ n++;
+ }
+ fprintf(fp, "%s", sp->name);
+ n += w;
+ }
+ if( n>0 ) fprintf(fp, "\n");
+ fprintf(fp, "----------------------------------------------------\n");
+ fprintf(fp, "Rules:\n");
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(fp, "%4d: ", rp->iRule);
+ rule_print(fp, rp);
+ fprintf(fp,".");
+ if( rp->precsym ){
+ fprintf(fp," [%s precedence=%d]",
+ rp->precsym->name, rp->precsym->prec);
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the executable */
+PRIVATE char *pathsearch(char *argv0, char *name, int modemask)
+{
+ const char *pathlist;
+ char *pathbufptr = 0;
+ char *pathbuf = 0;
+ char *path,*cp;
+ char c;
+
+#ifdef __WIN32__
+ cp = strrchr(argv0,'\\');
+#else
+ cp = strrchr(argv0,'/');
+#endif
+ if( cp ){
+ c = *cp;
+ *cp = 0;
+ path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 );
+ if( path ) lemon_sprintf(path,"%s/%s",argv0,name);
+ *cp = c;
+ }else{
+ pathlist = getenv("PATH");
+ if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+ pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 );
+ path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 );
+ if( (pathbuf != 0) && (path!=0) ){
+ pathbufptr = pathbuf;
+ lemon_strcpy(pathbuf, pathlist);
+ while( *pathbuf ){
+ cp = strchr(pathbuf,':');
+ if( cp==0 ) cp = &pathbuf[lemonStrlen(pathbuf)];
+ c = *cp;
+ *cp = 0;
+ lemon_sprintf(path,"%s/%s",pathbuf,name);
+ *cp = c;
+ if( c==0 ) pathbuf[0] = 0;
+ else pathbuf = &cp[1];
+ if( access(path,modemask)==0 ) break;
+ }
+ }
+ free(pathbufptr);
+ }
+ return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(struct lemon *lemp, struct action *ap)
+{
+ int act;
+ switch( ap->type ){
+ case SHIFT: act = ap->x.stp->statenum; break;
+ case SHIFTREDUCE: {
+ /* Since a SHIFT is inherient after a prior REDUCE, convert any
+ ** SHIFTREDUCE action with a nonterminal on the LHS into a simple
+ ** REDUCE action: */
+ if( ap->sp->index>=lemp->nterminal
+ && (lemp->errsym==0 || ap->sp->index!=lemp->errsym->index)
+ ){
+ act = lemp->minReduce + ap->x.rp->iRule;
+ }else{
+ act = lemp->minShiftReduce + ap->x.rp->iRule;
+ }
+ break;
+ }
+ case REDUCE: act = lemp->minReduce + ap->x.rp->iRule; break;
+ case ERROR: act = lemp->errAction; break;
+ case ACCEPT: act = lemp->accAction; break;
+ default: act = -1; break;
+ }
+ return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%". The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno)
+{
+ int i, iStart;
+ char line[LINESIZE];
+ while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+ (*lineno)++;
+ iStart = 0;
+ if( name ){
+ for(i=0; line[i]; i++){
+ if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+ && (i==0 || !ISALPHA(line[i-1]))
+ ){
+ if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+ fprintf(out,"%s",name);
+ i += 4;
+ iStart = i+1;
+ }
+ }
+ }
+ fprintf(out,"%s",&line[iStart]);
+ }
+}
+
+/* Skip forward past the header of the template file to the first "%%"
+*/
+PRIVATE void tplt_skip_header(FILE *in, int *lineno)
+{
+ char line[LINESIZE];
+ while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+ (*lineno)++;
+ }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(struct lemon *lemp)
+{
+ static char templatename[] = "lempar.c";
+ char buf[1000];
+ FILE *in;
+ char *tpltname;
+ char *toFree = 0;
+ char *cp;
+
+ /* first, see if user specified a template filename on the command line. */
+ if (user_templatename != 0) {
+ if( access(user_templatename,004)==-1 ){
+ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+ user_templatename);
+ lemp->errorcnt++;
+ return 0;
+ }
+ in = fopen(user_templatename,"rb");
+ if( in==0 ){
+ fprintf(stderr,"Can't open the template file \"%s\".\n",
+ user_templatename);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return in;
+ }
+
+ cp = strrchr(lemp->filename,'.');
+ if( cp ){
+ lemon_sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+ }else{
+ lemon_sprintf(buf,"%s.lt",lemp->filename);
+ }
+ if( access(buf,004)==0 ){
+ tpltname = buf;
+ }else if( access(templatename,004)==0 ){
+ tpltname = templatename;
+ }else{
+ toFree = tpltname = pathsearch(lemp->argv[0],templatename,0);
+ }
+ if( tpltname==0 ){
+ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+ templatename);
+ lemp->errorcnt++;
+ return 0;
+ }
+ in = fopen(tpltname,"rb");
+ if( in==0 ){
+ fprintf(stderr,"Can't open the template file \"%s\".\n",tpltname);
+ lemp->errorcnt++;
+ }
+ free(toFree);
+ return in;
+}
+
+/* Print a #line directive line to the output file. */
+PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename)
+{
+ fprintf(out,"#line %d \"",lineno);
+ while( *filename ){
+ if( *filename == '\\' ) putc('\\',out);
+ putc(*filename,out);
+ filename++;
+ }
+ fprintf(out,"\"\n");
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno)
+{
+ if( str==0 ) return;
+ while( *str ){
+ putc(*str,out);
+ if( *str=='\n' ) (*lineno)++;
+ str++;
+ }
+ if( str[-1]!='\n' ){
+ putc('\n',out);
+ (*lineno)++;
+ }
+ if (!lemp->nolinenosflag) {
+ (*lineno)++; tplt_linedir(out,*lineno,lemp->outname);
+ }
+ return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(
+ FILE *out,
+ struct symbol *sp,
+ struct lemon *lemp,
+ int *lineno
+){
+ char *cp = 0;
+
+ if( sp->type==TERMINAL ){
+ cp = lemp->tokendest;
+ if( cp==0 ) return;
+ fprintf(out,"{\n"); (*lineno)++;
+ }else if( sp->destructor ){
+ cp = sp->destructor;
+ fprintf(out,"{\n"); (*lineno)++;
+ if( !lemp->nolinenosflag ){
+ (*lineno)++;
+ tplt_linedir(out,sp->destLineno,lemp->filename);
+ }
+ }else if( lemp->vardest ){
+ cp = lemp->vardest;
+ if( cp==0 ) return;
+ fprintf(out,"{\n"); (*lineno)++;
+ }else{
+ assert( 0 ); /* Cannot happen */
+ }
+ for(; *cp; cp++){
+ if( *cp=='$' && cp[1]=='$' ){
+ fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+ cp++;
+ continue;
+ }
+ if( *cp=='\n' ) (*lineno)++;
+ fputc(*cp,out);
+ }
+ fprintf(out,"\n"); (*lineno)++;
+ if (!lemp->nolinenosflag) {
+ (*lineno)++; tplt_linedir(out,*lineno,lemp->outname);
+ }
+ fprintf(out,"}\n"); (*lineno)++;
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(struct symbol *sp, struct lemon *lemp)
+{
+ int ret;
+ if( sp->type==TERMINAL ){
+ ret = lemp->tokendest!=0;
+ }else{
+ ret = lemp->vardest!=0 || sp->destructor!=0;
+ }
+ return ret;
+}
+
+/*
+** Append text to a dynamically allocated string. If zText is 0 then
+** reset the string to be empty again. Always return the complete text
+** of the string (which is overwritten with each call).
+**
+** n bytes of zText are stored. If n==0 then all of zText up to the first
+** \000 terminator is stored. zText can contain up to two instances of
+** %d. The values of p1 and p2 are written into the first and second
+** %d.
+**
+** If n==-1, then the previous character is overwritten.
+*/
+PRIVATE char *append_str(const char *zText, int n, int p1, int p2){
+ static char empty[1] = { 0 };
+ static char *z = 0;
+ static int alloced = 0;
+ static int used = 0;
+ int c;
+ char zInt[40];
+ if( zText==0 ){
+ if( used==0 && z!=0 ) z[0] = 0;
+ used = 0;
+ return z;
+ }
+ if( n<=0 ){
+ if( n<0 ){
+ used += n;
+ assert( used>=0 );
+ }
+ n = lemonStrlen(zText);
+ }
+ if( (int) (n+sizeof(zInt)*2+used) >= alloced ){
+ alloced = n + sizeof(zInt)*2 + used + 200;
+ z = (char *) realloc(z, alloced);
+ }
+ if( z==0 ) return empty;
+ while( n-- > 0 ){
+ c = *(zText++);
+ if( c=='%' && n>0 && zText[0]=='d' ){
+ lemon_sprintf(zInt, "%d", p1);
+ p1 = p2;
+ lemon_strcpy(&z[used], zInt);
+ used += lemonStrlen(&z[used]);
+ zText++;
+ n--;
+ }else{
+ z[used++] = (char)c;
+ }
+ }
+ z[used] = 0;
+ return z;
+}
+
+/*
+** Write and transform the rp->code string so that symbols are expanded.
+** Populate the rp->codePrefix and rp->codeSuffix strings, as appropriate.
+**
+** Return 1 if the expanded code requires that "yylhsminor" local variable
+** to be defined.
+*/
+PRIVATE int translate_code(struct lemon *lemp, struct rule *rp){
+ char *cp, *xp;
+ int i;
+ int rc = 0; /* True if yylhsminor is used */
+ int dontUseRhs0 = 0; /* If true, use of left-most RHS label is illegal */
+ const char *zSkip = 0; /* The zOvwrt comment within rp->code, or NULL */
+ char lhsused = 0; /* True if the LHS element has been used */
+ char lhsdirect; /* True if LHS writes directly into stack */
+ char used[MAXRHS]; /* True for each RHS element which is used */
+ char zLhs[50]; /* Convert the LHS symbol into this string */
+ char zOvwrt[900]; /* Comment that to allow LHS to overwrite RHS */
+
+ for(i=0; i<rp->nrhs; i++) used[i] = 0;
+ lhsused = 0;
+
+ if( rp->code==0 ){
+ static char newlinestr[2] = { '\n', '\0' };
+ rp->code = newlinestr;
+ rp->line = rp->ruleline;
+ rp->noCode = 1;
+ }else{
+ rp->noCode = 0;
+ }
+
+
+ if( rp->nrhs==0 ){
+ /* If there are no RHS symbols, then writing directly to the LHS is ok */
+ lhsdirect = 1;
+ }else if( rp->rhsalias[0]==0 ){
+ /* The left-most RHS symbol has no value. LHS direct is ok. But
+ ** we have to call the destructor on the RHS symbol first. */
+ lhsdirect = 1;
+ if( has_destructor(rp->rhs[0],lemp) ){
+ append_str(0,0,0,0);
+ append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0,
+ rp->rhs[0]->index,1-rp->nrhs);
+ rp->codePrefix = Strsafe(append_str(0,0,0,0));
+ rp->noCode = 0;
+ }
+ }else if( rp->lhsalias==0 ){
+ /* There is no LHS value symbol. */
+ lhsdirect = 1;
+ }else if( strcmp(rp->lhsalias,rp->rhsalias[0])==0 ){
+ /* The LHS symbol and the left-most RHS symbol are the same, so
+ ** direct writing is allowed */
+ lhsdirect = 1;
+ lhsused = 1;
+ used[0] = 1;
+ if( rp->lhs->dtnum!=rp->rhs[0]->dtnum ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "%s(%s) and %s(%s) share the same label but have "
+ "different datatypes.",
+ rp->lhs->name, rp->lhsalias, rp->rhs[0]->name, rp->rhsalias[0]);
+ lemp->errorcnt++;
+ }
+ }else{
+ lemon_sprintf(zOvwrt, "/*%s-overwrites-%s*/",
+ rp->lhsalias, rp->rhsalias[0]);
+ zSkip = strstr(rp->code, zOvwrt);
+ if( zSkip!=0 ){
+ /* The code contains a special comment that indicates that it is safe
+ ** for the LHS label to overwrite left-most RHS label. */
+ lhsdirect = 1;
+ }else{
+ lhsdirect = 0;
+ }
+ }
+ if( lhsdirect ){
+ sprintf(zLhs, "yymsp[%d].minor.yy%d",1-rp->nrhs,rp->lhs->dtnum);
+ }else{
+ rc = 1;
+ sprintf(zLhs, "yylhsminor.yy%d",rp->lhs->dtnum);
+ }
+
+ append_str(0,0,0,0);
+
+ /* This const cast is wrong but harmless, if we're careful. */
+ for(cp=(char *)rp->code; *cp; cp++){
+ if( cp==zSkip ){
+ append_str(zOvwrt,0,0,0);
+ cp += lemonStrlen(zOvwrt)-1;
+ dontUseRhs0 = 1;
+ continue;
+ }
+ if( ISALPHA(*cp) && (cp==rp->code || (!ISALNUM(cp[-1]) && cp[-1]!='_')) ){
+ char saved;
+ for(xp= &cp[1]; ISALNUM(*xp) || *xp=='_'; xp++);
+ saved = *xp;
+ *xp = 0;
+ if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+ append_str(zLhs,0,0,0);
+ cp = xp;
+ lhsused = 1;
+ }else{
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+ if( i==0 && dontUseRhs0 ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s used after '%s'.",
+ rp->rhsalias[0], zOvwrt);
+ lemp->errorcnt++;
+ }else if( cp!=rp->code && cp[-1]=='@' ){
+ /* If the argument is of the form @X then substituted
+ ** the token number of X, not the value of X */
+ append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0);
+ }else{
+ struct symbol *sp = rp->rhs[i];
+ int dtnum;
+ if( sp->type==MULTITERMINAL ){
+ dtnum = sp->subsym[0]->dtnum;
+ }else{
+ dtnum = sp->dtnum;
+ }
+ append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum);
+ }
+ cp = xp;
+ used[i] = 1;
+ break;
+ }
+ }
+ }
+ *xp = saved;
+ }
+ append_str(cp, 1, 0, 0);
+ } /* End loop */
+
+ /* Main code generation completed */
+ cp = append_str(0,0,0,0);
+ if( cp && cp[0] ) rp->code = Strsafe(cp);
+ append_str(0,0,0,0);
+
+ /* Check to make sure the LHS has been used */
+ if( rp->lhsalias && !lhsused ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label \"%s\" for \"%s(%s)\" is never used.",
+ rp->lhsalias,rp->lhs->name,rp->lhsalias);
+ lemp->errorcnt++;
+ }
+
+ /* Generate destructor code for RHS minor values which are not referenced.
+ ** Generate error messages for unused labels and duplicate labels.
+ */
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] ){
+ if( i>0 ){
+ int j;
+ if( rp->lhsalias && strcmp(rp->lhsalias,rp->rhsalias[i])==0 ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "%s(%s) has the same label as the LHS but is not the left-most "
+ "symbol on the RHS.",
+ rp->rhs[i]->name, rp->rhsalias[i]);
+ lemp->errorcnt++;
+ }
+ for(j=0; j<i; j++){
+ if( rp->rhsalias[j] && strcmp(rp->rhsalias[j],rp->rhsalias[i])==0 ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s used for multiple symbols on the RHS of a rule.",
+ rp->rhsalias[i]);
+ lemp->errorcnt++;
+ break;
+ }
+ }
+ }
+ if( !used[i] ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s for \"%s(%s)\" is never used.",
+ rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+ lemp->errorcnt++;
+ }
+ }else if( i>0 && has_destructor(rp->rhs[i],lemp) ){
+ append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0,
+ rp->rhs[i]->index,i-rp->nrhs+1);
+ }
+ }
+
+ /* If unable to write LHS values directly into the stack, write the
+ ** saved LHS value now. */
+ if( lhsdirect==0 ){
+ append_str(" yymsp[%d].minor.yy%d = ", 0, 1-rp->nrhs, rp->lhs->dtnum);
+ append_str(zLhs, 0, 0, 0);
+ append_str(";\n", 0, 0, 0);
+ }
+
+ /* Suffix code generation complete */
+ cp = append_str(0,0,0,0);
+ if( cp && cp[0] ){
+ rp->codeSuffix = Strsafe(cp);
+ rp->noCode = 0;
+ }
+
+ return rc;
+}
+
+/*
+** Generate code which executes when the rule "rp" is reduced. Write
+** the code to "out". Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(
+ FILE *out,
+ struct rule *rp,
+ struct lemon *lemp,
+ int *lineno
+){
+ const char *cp;
+
+ /* Setup code prior to the #line directive */
+ if( rp->codePrefix && rp->codePrefix[0] ){
+ fprintf(out, "{%s", rp->codePrefix);
+ for(cp=rp->codePrefix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+ }
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+ if( !lemp->nolinenosflag ){
+ (*lineno)++;
+ tplt_linedir(out,rp->line,lemp->filename);
+ }
+ fprintf(out,"{%s",rp->code);
+ for(cp=rp->code; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+ fprintf(out,"}\n"); (*lineno)++;
+ if( !lemp->nolinenosflag ){
+ (*lineno)++;
+ tplt_linedir(out,*lineno,lemp->outname);
+ }
+ }
+
+ /* Generate breakdown code that occurs after the #line directive */
+ if( rp->codeSuffix && rp->codeSuffix[0] ){
+ fprintf(out, "%s", rp->codeSuffix);
+ for(cp=rp->codeSuffix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+ }
+
+ if( rp->codePrefix ){
+ fprintf(out, "}\n"); (*lineno)++;
+ }
+
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals. In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(
+ FILE *out, /* The output stream */
+ struct lemon *lemp, /* The main info structure for this parser */
+ int *plineno, /* Pointer to the line number */
+ int mhflag /* True if generating makeheaders output */
+){
+ int lineno; /* The line number of the output */
+ char **types; /* A hash table of datatypes */
+ int arraysize; /* Size of the "types" array */
+ int maxdtlength; /* Maximum length of any ".datatype" field. */
+ char *stddt; /* Standardized name for a datatype */
+ int i,j; /* Loop counters */
+ unsigned hash; /* For hashing the name of a type */
+ const char *name; /* Name of the parser */
+
+ /* Allocate and initialize types[] and allocate stddt[] */
+ arraysize = lemp->nsymbol * 2;
+ types = (char**)calloc( arraysize, sizeof(char*) );
+ if( types==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+ for(i=0; i<arraysize; i++) types[i] = 0;
+ maxdtlength = 0;
+ if( lemp->vartype ){
+ maxdtlength = lemonStrlen(lemp->vartype);
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ int len;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->datatype==0 ) continue;
+ len = lemonStrlen(sp->datatype);
+ if( len>maxdtlength ) maxdtlength = len;
+ }
+ stddt = (char*)malloc( maxdtlength*2 + 1 );
+ if( stddt==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+
+ /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+ ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is
+ ** used for terminal symbols. If there is no %default_type defined then
+ ** 0 is also used as the .dtnum value for nonterminals which do not specify
+ ** a datatype using the %type directive.
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ char *cp;
+ if( sp==lemp->errsym ){
+ sp->dtnum = arraysize+1;
+ continue;
+ }
+ if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+ sp->dtnum = 0;
+ continue;
+ }
+ cp = sp->datatype;
+ if( cp==0 ) cp = lemp->vartype;
+ j = 0;
+ while( ISSPACE(*cp) ) cp++;
+ while( *cp ) stddt[j++] = *cp++;
+ while( j>0 && ISSPACE(stddt[j-1]) ) j--;
+ stddt[j] = 0;
+ if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){
+ sp->dtnum = 0;
+ continue;
+ }
+ hash = 0;
+ for(j=0; stddt[j]; j++){
+ hash = hash*53 + stddt[j];
+ }
+ hash = (hash & 0x7fffffff)%arraysize;
+ while( types[hash] ){
+ if( strcmp(types[hash],stddt)==0 ){
+ sp->dtnum = hash + 1;
+ break;
+ }
+ hash++;
+ if( hash>=(unsigned)arraysize ) hash = 0;
+ }
+ if( types[hash]==0 ){
+ sp->dtnum = hash + 1;
+ types[hash] = (char*)malloc( lemonStrlen(stddt)+1 );
+ if( types[hash]==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+ lemon_strcpy(types[hash],stddt);
+ }
+ }
+
+ /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+ name = lemp->name ? lemp->name : "Parse";
+ lineno = *plineno;
+ if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+ fprintf(out,"#define %sTOKENTYPE %s\n",name,
+ lemp->tokentype?lemp->tokentype:"void*"); lineno++;
+ if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+ fprintf(out,"typedef union {\n"); lineno++;
+ fprintf(out," int yyinit;\n"); lineno++;
+ fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++;
+ for(i=0; i<arraysize; i++){
+ if( types[i]==0 ) continue;
+ fprintf(out," %s yy%d;\n",types[i],i+1); lineno++;
+ free(types[i]);
+ }
+ if( lemp->errsym && lemp->errsym->useCnt ){
+ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++;
+ }
+ free(stddt);
+ free(types);
+ fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+ *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive. If pnByte!=NULL then also write the sizeof
+** for that type (1, 2, or 4) into *pnByte.
+*/
+static const char *minimum_size_type(int lwr, int upr, int *pnByte){
+ const char *zType = "int";
+ int nByte = 4;
+ if( lwr>=0 ){
+ if( upr<=255 ){
+ zType = "unsigned char";
+ nByte = 1;
+ }else if( upr<65535 ){
+ zType = "unsigned short int";
+ nByte = 2;
+ }else{
+ zType = "unsigned int";
+ nByte = 4;
+ }
+ }else if( lwr>=-127 && upr<=127 ){
+ zType = "signed char";
+ nByte = 1;
+ }else if( lwr>=-32767 && upr<32767 ){
+ zType = "short";
+ nByte = 2;
+ }
+ if( pnByte ) *pnByte = nByte;
+ return zType;
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions. Each of these sets makes an instance
+** of the following structure. An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+ struct state *stp; /* A pointer to a state */
+ int isTkn; /* True to use tokens. False for non-terminals */
+ int nAction; /* Number of actions */
+ int iOrder; /* Original order of action sets */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+ struct axset *p1 = (struct axset*)a;
+ struct axset *p2 = (struct axset*)b;
+ int c;
+ c = p2->nAction - p1->nAction;
+ if( c==0 ){
+ c = p1->iOrder - p2->iOrder;
+ }
+ assert( c!=0 || p1==p2 );
+ return c;
+}
+
+/*
+** Write text on "out" that describes the rule "rp".
+*/
+static void writeRuleText(FILE *out, struct rule *rp){
+ int j;
+ fprintf(out,"%s ::=", rp->lhs->name);
+ for(j=0; j<rp->nrhs; j++){
+ struct symbol *sp = rp->rhs[j];
+ if( sp->type!=MULTITERMINAL ){
+ fprintf(out," %s", sp->name);
+ }else{
+ int k;
+ fprintf(out," %s", sp->subsym[0]->name);
+ for(k=1; k<sp->nsubsym; k++){
+ fprintf(out,"|%s",sp->subsym[k]->name);
+ }
+ }
+ }
+}
+
+
+/* Generate C source code for the parser */
+void ReportTable(
+ struct lemon *lemp,
+ int mhflag, /* Output in makeheaders format if true */
+ int sqlFlag /* Generate the *.sql file too */
+){
+ FILE *out, *in, *sql;
+ int lineno;
+ struct state *stp;
+ struct action *ap;
+ struct rule *rp;
+ struct acttab *pActtab;
+ int i, j, n, sz;
+ int nLookAhead;
+ int szActionType; /* sizeof(YYACTIONTYPE) */
+ int szCodeType; /* sizeof(YYCODETYPE) */
+ const char *name;
+ int mnTknOfst, mxTknOfst;
+ int mnNtOfst, mxNtOfst;
+ struct axset *ax;
+ char *prefix;
+
+ lemp->minShiftReduce = lemp->nstate;
+ lemp->errAction = lemp->minShiftReduce + lemp->nrule;
+ lemp->accAction = lemp->errAction + 1;
+ lemp->noAction = lemp->accAction + 1;
+ lemp->minReduce = lemp->noAction + 1;
+ lemp->maxAction = lemp->minReduce + lemp->nrule;
+
+ in = tplt_open(lemp);
+ if( in==0 ) return;
+ out = file_open(lemp,".c","wb");
+ if( out==0 ){
+ fclose(in);
+ return;
+ }
+ if( sqlFlag==0 ){
+ sql = 0;
+ }else{
+ sql = file_open(lemp, ".sql", "wb");
+ if( sql==0 ){
+ fclose(in);
+ fclose(out);
+ return;
+ }
+ fprintf(sql,
+ "BEGIN;\n"
+ "CREATE TABLE symbol(\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL,\n"
+ " isTerminal BOOLEAN NOT NULL,\n"
+ " fallback INTEGER REFERENCES symbol"
+ " DEFERRABLE INITIALLY DEFERRED\n"
+ ");\n"
+ );
+ for(i=0; i<lemp->nsymbol; i++){
+ fprintf(sql,
+ "INSERT INTO symbol(id,name,isTerminal,fallback)"
+ "VALUES(%d,'%s',%s",
+ i, lemp->symbols[i]->name,
+ i<lemp->nterminal ? "TRUE" : "FALSE"
+ );
+ if( lemp->symbols[i]->fallback ){
+ fprintf(sql, ",%d);\n", lemp->symbols[i]->fallback->index);
+ }else{
+ fprintf(sql, ",NULL);\n");
+ }
+ }
+ fprintf(sql,
+ "CREATE TABLE rule(\n"
+ " ruleid INTEGER PRIMARY KEY,\n"
+ " lhs INTEGER REFERENCES symbol(id),\n"
+ " txt TEXT\n"
+ ");\n"
+ "CREATE TABLE rulerhs(\n"
+ " ruleid INTEGER REFERENCES rule(ruleid),\n"
+ " pos INTEGER,\n"
+ " sym INTEGER REFERENCES symbol(id)\n"
+ ");\n"
+ );
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ assert( i==rp->iRule );
+ fprintf(sql,
+ "INSERT INTO rule(ruleid,lhs,txt)VALUES(%d,%d,'",
+ rp->iRule, rp->lhs->index
+ );
+ writeRuleText(sql, rp);
+ fprintf(sql,"');\n");
+ for(j=0; j<rp->nrhs; j++){
+ struct symbol *sp = rp->rhs[j];
+ if( sp->type!=MULTITERMINAL ){
+ fprintf(sql,
+ "INSERT INTO rulerhs(ruleid,pos,sym)VALUES(%d,%d,%d);\n",
+ i,j,sp->index
+ );
+ }else{
+ int k;
+ for(k=0; k<sp->nsubsym; k++){
+ fprintf(sql,
+ "INSERT INTO rulerhs(ruleid,pos,sym)VALUES(%d,%d,%d);\n",
+ i,j,sp->subsym[k]->index
+ );
+ }
+ }
+ }
+ }
+ fprintf(sql, "COMMIT;\n");
+ }
+ lineno = 1;
+
+ fprintf(out,
+ "/* This file is automatically generated by Lemon from input grammar\n"
+ "** source file \"%s\"", lemp->filename); lineno++;
+ if( nDefineUsed==0 ){
+ fprintf(out, ".\n*/\n"); lineno += 2;
+ }else{
+ fprintf(out, " with these options:\n**\n"); lineno += 2;
+ for(i=0; i<nDefine; i++){
+ if( !bDefineUsed[i] ) continue;
+ fprintf(out, "** -D%s\n", azDefine[i]); lineno++;
+ }
+ fprintf(out, "*/\n"); lineno++;
+ }
+
+ /* The first %include directive begins with a C-language comment,
+ ** then skip over the header comment of the template file
+ */
+ if( lemp->include==0 ) lemp->include = "";
+ for(i=0; ISSPACE(lemp->include[i]); i++){
+ if( lemp->include[i]=='\n' ){
+ lemp->include += i+1;
+ i = -1;
+ }
+ }
+ if( lemp->include[0]=='/' ){
+ tplt_skip_header(in,&lineno);
+ }else{
+ tplt_xfer(lemp->name,in,out,&lineno);
+ }
+
+ /* Generate the include code, if any */
+ tplt_print(out,lemp,lemp->include,&lineno);
+ if( mhflag ){
+ char *incName = file_makename(lemp, ".h");
+ fprintf(out,"#include \"%s\"\n", incName); lineno++;
+ free(incName);
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate #defines for all tokens */
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ if( mhflag ){
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ }else{
+ fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name);
+ }
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ lineno++;
+ }
+ fprintf(out,"#endif\n"); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the defines */
+ fprintf(out,"#define YYCODETYPE %s\n",
+ minimum_size_type(0, lemp->nsymbol, &szCodeType)); lineno++;
+ fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol); lineno++;
+ fprintf(out,"#define YYACTIONTYPE %s\n",
+ minimum_size_type(0,lemp->maxAction,&szActionType)); lineno++;
+ if( lemp->wildcard ){
+ fprintf(out,"#define YYWILDCARD %d\n",
+ lemp->wildcard->index); lineno++;
+ }
+ print_stack_union(out,lemp,&lineno,mhflag);
+ fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++;
+ if( lemp->stacksize ){
+ fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++;
+ }else{
+ fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++;
+ }
+ fprintf(out, "#endif\n"); lineno++;
+ if( mhflag ){
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ }
+ name = lemp->name ? lemp->name : "Parse";
+ if( lemp->arg && lemp->arg[0] ){
+ i = lemonStrlen(lemp->arg);
+ while( i>=1 && ISSPACE(lemp->arg[i-1]) ) i--;
+ while( i>=1 && (ISALNUM(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+ fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_PARAM ,%s\n",name,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_FETCH %s=yypParser->%s;\n",
+ name,lemp->arg,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_STORE yypParser->%s=%s;\n",
+ name,&lemp->arg[i],&lemp->arg[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PARAM\n",name); lineno++;
+ fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+ }
+ if( lemp->ctx && lemp->ctx[0] ){
+ i = lemonStrlen(lemp->ctx);
+ while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--;
+ while( i>=1 && (ISALNUM(lemp->ctx[i-1]) || lemp->ctx[i-1]=='_') ) i--;
+ fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx); lineno++;
+ fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx); lineno++;
+ fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]); lineno++;
+ fprintf(out,"#define %sCTX_FETCH %s=yypParser->%s;\n",
+ name,lemp->ctx,&lemp->ctx[i]); lineno++;
+ fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n",
+ name,&lemp->ctx[i],&lemp->ctx[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sCTX_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sCTX_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sCTX_PARAM\n",name); lineno++;
+ fprintf(out,"#define %sCTX_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sCTX_STORE\n",name); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ if( lemp->errsym && lemp->errsym->useCnt ){
+ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
+ fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
+ }
+ if( lemp->has_fallback ){
+ fprintf(out,"#define YYFALLBACK 1\n"); lineno++;
+ }
+
+ /* Compute the action table, but do not output it yet. The action
+ ** table must be computed before generating the YYNSTATE macro because
+ ** we need to know how many states can be eliminated.
+ */
+ ax = (struct axset *) calloc(lemp->nxstate*2, sizeof(ax[0]));
+ if( ax==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=0; i<lemp->nxstate; i++){
+ stp = lemp->sorted[i];
+ ax[i*2].stp = stp;
+ ax[i*2].isTkn = 1;
+ ax[i*2].nAction = stp->nTknAct;
+ ax[i*2+1].stp = stp;
+ ax[i*2+1].isTkn = 0;
+ ax[i*2+1].nAction = stp->nNtAct;
+ }
+ mxTknOfst = mnTknOfst = 0;
+ mxNtOfst = mnNtOfst = 0;
+ /* In an effort to minimize the action table size, use the heuristic
+ ** of placing the largest action sets first */
+ for(i=0; i<lemp->nxstate*2; i++) ax[i].iOrder = i;
+ qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare);
+ pActtab = acttab_alloc(lemp->nsymbol, lemp->nterminal);
+ for(i=0; i<lemp->nxstate*2 && ax[i].nAction>0; i++){
+ stp = ax[i].stp;
+ if( ax[i].isTkn ){
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index>=lemp->nterminal ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iTknOfst = acttab_insert(pActtab, 1);
+ if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+ if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+ }else{
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index<lemp->nterminal ) continue;
+ if( ap->sp->index==lemp->nsymbol ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iNtOfst = acttab_insert(pActtab, 0);
+ if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+ if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+ }
+#if 0 /* Uncomment for a trace of how the yy_action[] table fills out */
+ { int jj, nn;
+ for(jj=nn=0; jj<pActtab->nAction; jj++){
+ if( pActtab->aAction[jj].action<0 ) nn++;
+ }
+ printf("%4d: State %3d %s n: %2d size: %5d freespace: %d\n",
+ i, stp->statenum, ax[i].isTkn ? "Token" : "Var ",
+ ax[i].nAction, pActtab->nAction, nn);
+ }
+#endif
+ }
+ free(ax);
+
+ /* Mark rules that are actually used for reduce actions after all
+ ** optimizations have been applied
+ */
+ for(rp=lemp->rule; rp; rp=rp->next) rp->doesReduce = LEMON_FALSE;
+ for(i=0; i<lemp->nxstate; i++){
+ for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE || ap->type==SHIFTREDUCE ){
+ ap->x.rp->doesReduce = 1;
+ }
+ }
+ }
+
+ /* Finish rendering the constants now that the action table has
+ ** been computed */
+ fprintf(out,"#define YYNSTATE %d\n",lemp->nxstate); lineno++;
+ fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++;
+ fprintf(out,"#define YYNRULE_WITH_ACTION %d\n",lemp->nruleWithAction);
+ lineno++;
+ fprintf(out,"#define YYNTOKEN %d\n",lemp->nterminal); lineno++;
+ fprintf(out,"#define YY_MAX_SHIFT %d\n",lemp->nxstate-1); lineno++;
+ i = lemp->minShiftReduce;
+ fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",i); lineno++;
+ i += lemp->nrule;
+ fprintf(out,"#define YY_MAX_SHIFTREDUCE %d\n", i-1); lineno++;
+ fprintf(out,"#define YY_ERROR_ACTION %d\n", lemp->errAction); lineno++;
+ fprintf(out,"#define YY_ACCEPT_ACTION %d\n", lemp->accAction); lineno++;
+ fprintf(out,"#define YY_NO_ACTION %d\n", lemp->noAction); lineno++;
+ fprintf(out,"#define YY_MIN_REDUCE %d\n", lemp->minReduce); lineno++;
+ i = lemp->minReduce + lemp->nrule;
+ fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Now output the action table and its associates:
+ **
+ ** yy_action[] A single table containing all actions.
+ ** yy_lookahead[] A table containing the lookahead for each entry in
+ ** yy_action. Used to detect hash collisions.
+ ** yy_shift_ofst[] For each state, the offset into yy_action for
+ ** shifting terminals.
+ ** yy_reduce_ofst[] For each state, the offset into yy_action for
+ ** shifting non-terminals after a reduce.
+ ** yy_default[] Default action for each state.
+ */
+
+ /* Output the yy_action table */
+ lemp->nactiontab = n = acttab_action_size(pActtab);
+ lemp->tablesize += n*szActionType;
+ fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++;
+ fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++;
+ for(i=j=0; i<n; i++){
+ int action = acttab_yyaction(pActtab, i);
+ if( action<0 ) action = lemp->noAction;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", action);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_lookahead table */
+ lemp->nlookaheadtab = n = acttab_lookahead_size(pActtab);
+ lemp->tablesize += n*szCodeType;
+ fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+ for(i=j=0; i<n; i++){
+ int la = acttab_yylookahead(pActtab, i);
+ if( la<0 ) la = lemp->nsymbol;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", la);
+ if( j==9 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ /* Add extra entries to the end of the yy_lookahead[] table so that
+ ** yy_shift_ofst[]+iToken will always be a valid index into the array,
+ ** even for the largest possible value of yy_shift_ofst[] and iToken. */
+ nLookAhead = lemp->nterminal + lemp->nactiontab;
+ while( i<nLookAhead ){
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", lemp->nterminal);
+ if( j==9 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ i++;
+ }
+ if( j>0 ){ fprintf(out, "\n"); lineno++; }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_shift_ofst[] table */
+ n = lemp->nxstate;
+ while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--;
+ fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++;
+ fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++;
+ fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++;
+ fprintf(out, "static const %s yy_shift_ofst[] = {\n",
+ minimum_size_type(mnTknOfst, lemp->nterminal+lemp->nactiontab, &sz));
+ lineno++;
+ lemp->tablesize += n*sz;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iTknOfst;
+ if( ofst==NO_OFFSET ) ofst = lemp->nactiontab;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_reduce_ofst[] table */
+ n = lemp->nxstate;
+ while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--;
+ fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++;
+ fprintf(out, "#define YY_REDUCE_MIN (%d)\n", mnNtOfst); lineno++;
+ fprintf(out, "#define YY_REDUCE_MAX (%d)\n", mxNtOfst); lineno++;
+ fprintf(out, "static const %s yy_reduce_ofst[] = {\n",
+ minimum_size_type(mnNtOfst-1, mxNtOfst, &sz)); lineno++;
+ lemp->tablesize += n*sz;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iNtOfst;
+ if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the default action table */
+ fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++;
+ n = lemp->nxstate;
+ lemp->tablesize += n*szActionType;
+ for(i=j=0; i<n; i++){
+ stp = lemp->sorted[i];
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ if( stp->iDfltReduce<0 ){
+ fprintf(out, " %4d,", lemp->errAction);
+ }else{
+ fprintf(out, " %4d,", stp->iDfltReduce + lemp->minReduce);
+ }
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of fallback tokens.
+ */
+ if( lemp->has_fallback ){
+ int mx = lemp->nterminal - 1;
+ /* 2019-08-28: Generate fallback entries for every token to avoid
+ ** having to do a range check on the index */
+ /* while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } */
+ lemp->tablesize += (mx+1)*szCodeType;
+ for(i=0; i<=mx; i++){
+ struct symbol *p = lemp->symbols[i];
+ if( p->fallback==0 ){
+ fprintf(out, " 0, /* %10s => nothing */\n", p->name);
+ }else{
+ fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index,
+ p->name, p->fallback->name);
+ }
+ lineno++;
+ }
+ }
+ tplt_xfer(lemp->name, in, out, &lineno);
+
+ /* Generate a table containing the symbolic name of every symbol
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ fprintf(out," /* %4d */ \"%s\",\n",i, lemp->symbols[i]->name); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate a table containing a text string that describes every
+ ** rule in the rule set of the grammar. This information is used
+ ** when tracing REDUCE actions.
+ */
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ assert( rp->iRule==i );
+ fprintf(out," /* %3d */ \"", i);
+ writeRuleText(out, rp);
+ fprintf(out,"\",\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes every time a symbol is popped from
+ ** the stack while processing errors or while destroying the parser.
+ ** (In other words, generate the %destructor actions)
+ */
+ if( lemp->tokendest ){
+ int once = 1;
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type!=TERMINAL ) continue;
+ if( once ){
+ fprintf(out, " /* TERMINAL Destructor */\n"); lineno++;
+ once = 0;
+ }
+ fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++;
+ }
+ for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+ if( i<lemp->nsymbol ){
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ if( lemp->vardest ){
+ struct symbol *dflt_sp = 0;
+ int once = 1;
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL ||
+ sp->index<=0 || sp->destructor!=0 ) continue;
+ if( once ){
+ fprintf(out, " /* Default NON-TERMINAL Destructor */\n");lineno++;
+ once = 0;
+ }
+ fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++;
+ dflt_sp = sp;
+ }
+ if( dflt_sp!=0 ){
+ emit_destructor_code(out,dflt_sp,lemp,&lineno);
+ }
+ fprintf(out," break;\n"); lineno++;
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+ if( sp->destLineno<0 ) continue; /* Already emitted */
+ fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++;
+
+ /* Combine duplicate destructors into a single case */
+ for(j=i+1; j<lemp->nsymbol; j++){
+ struct symbol *sp2 = lemp->symbols[j];
+ if( sp2 && sp2->type!=TERMINAL && sp2->destructor
+ && sp2->dtnum==sp->dtnum
+ && strcmp(sp->destructor,sp2->destructor)==0 ){
+ fprintf(out," case %d: /* %s */\n",
+ sp2->index, sp2->name); lineno++;
+ sp2->destLineno = -1; /* Avoid emitting this destructor again */
+ }
+ }
+
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes whenever the parser stack overflows */
+ tplt_print(out,lemp,lemp->overflow,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the tables of rule information. yyRuleInfoLhs[] and
+ ** yyRuleInfoNRhs[].
+ **
+ ** Note: This code depends on the fact that rules are number
+ ** sequentially beginning with 0.
+ */
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ fprintf(out," %4d, /* (%d) ", rp->lhs->index, i);
+ rule_print(out, rp);
+ fprintf(out," */\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ fprintf(out," %3d, /* (%d) ", -rp->nrhs, i);
+ rule_print(out, rp);
+ fprintf(out," */\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which execution during each REDUCE action */
+ i = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ i += translate_code(lemp, rp);
+ }
+ if( i ){
+ fprintf(out," YYMINORTYPE yylhsminor;\n"); lineno++;
+ }
+ /* First output rules other than the default: rule */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ struct rule *rp2; /* Other rules with the same action */
+ if( rp->codeEmitted ) continue;
+ if( rp->noCode ){
+ /* No C code actions, so this will be part of the "default:" rule */
+ continue;
+ }
+ fprintf(out," case %d: /* ", rp->iRule);
+ writeRuleText(out, rp);
+ fprintf(out, " */\n"); lineno++;
+ for(rp2=rp->next; rp2; rp2=rp2->next){
+ if( rp2->code==rp->code && rp2->codePrefix==rp->codePrefix
+ && rp2->codeSuffix==rp->codeSuffix ){
+ fprintf(out," case %d: /* ", rp2->iRule);
+ writeRuleText(out, rp2);
+ fprintf(out," */ yytestcase(yyruleno==%d);\n", rp2->iRule); lineno++;
+ rp2->codeEmitted = 1;
+ }
+ }
+ emit_code(out,rp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ rp->codeEmitted = 1;
+ }
+ /* Finally, output the default: rule. We choose as the default: all
+ ** empty actions. */
+ fprintf(out," default:\n"); lineno++;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->codeEmitted ) continue;
+ assert( rp->noCode );
+ fprintf(out," /* (%d) ", rp->iRule);
+ writeRuleText(out, rp);
+ if( rp->neverReduce ){
+ fprintf(out, " (NEVER REDUCES) */ assert(yyruleno!=%d);\n",
+ rp->iRule); lineno++;
+ }else if( rp->doesReduce ){
+ fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->iRule); lineno++;
+ }else{
+ fprintf(out, " (OPTIMIZED OUT) */ assert(yyruleno!=%d);\n",
+ rp->iRule); lineno++;
+ }
+ }
+ fprintf(out," break;\n"); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes if a parse fails */
+ tplt_print(out,lemp,lemp->failure,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when a syntax error occurs */
+ tplt_print(out,lemp,lemp->error,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when the parser accepts its input */
+ tplt_print(out,lemp,lemp->accept,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Append any addition code the user desires */
+ tplt_print(out,lemp,lemp->extracode,&lineno);
+
+ acttab_free(pActtab);
+ fclose(in);
+ fclose(out);
+ if( sql ) fclose(sql);
+ return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(struct lemon *lemp)
+{
+ FILE *out, *in;
+ const char *prefix;
+ char line[LINESIZE];
+ char pattern[LINESIZE];
+ int i;
+
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ in = file_open(lemp,".h","rb");
+ if( in ){
+ int nextChar;
+ for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+ lemon_sprintf(pattern,"#define %s%-30s %3d\n",
+ prefix,lemp->symbols[i]->name,i);
+ if( strcmp(line,pattern) ) break;
+ }
+ nextChar = fgetc(in);
+ fclose(in);
+ if( i==lemp->nterminal && nextChar==EOF ){
+ /* No change in the file. Don't rewrite it. */
+ return;
+ }
+ }
+ out = file_open(lemp,".h","wb");
+ if( out ){
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %3d\n",prefix,lemp->symbols[i]->name,i);
+ }
+ fclose(out);
+ }
+ return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default. Except, there is no default if the wildcard token
+** is a possible look-ahead.
+*/
+void CompressTables(struct lemon *lemp)
+{
+ struct state *stp;
+ struct action *ap, *ap2, *nextap;
+ struct rule *rp, *rp2, *rbest;
+ int nbest, n;
+ int i;
+ int usesWildcard;
+
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ nbest = 0;
+ rbest = 0;
+ usesWildcard = 0;
+
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==SHIFT && ap->sp==lemp->wildcard ){
+ usesWildcard = 1;
+ }
+ if( ap->type!=REDUCE ) continue;
+ rp = ap->x.rp;
+ if( rp->lhsStart ) continue;
+ if( rp==rbest ) continue;
+ n = 1;
+ for(ap2=ap->next; ap2; ap2=ap2->next){
+ if( ap2->type!=REDUCE ) continue;
+ rp2 = ap2->x.rp;
+ if( rp2==rbest ) continue;
+ if( rp2==rp ) n++;
+ }
+ if( n>nbest ){
+ nbest = n;
+ rbest = rp;
+ }
+ }
+
+ /* Do not make a default if the number of rules to default
+ ** is not at least 1 or if the wildcard token is a possible
+ ** lookahead.
+ */
+ if( nbest<1 || usesWildcard ) continue;
+
+
+ /* Combine matching REDUCE actions into a single default */
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+ }
+ assert( ap );
+ ap->sp = Symbol_new("{default}");
+ for(ap=ap->next; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+ }
+ stp->ap = Action_sort(stp->ap);
+
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==SHIFT ) break;
+ if( ap->type==REDUCE && ap->x.rp!=rbest ) break;
+ }
+ if( ap==0 ){
+ stp->autoReduce = 1;
+ stp->pDfltReduce = rbest;
+ }
+ }
+
+ /* Make a second pass over all states and actions. Convert
+ ** every action that is a SHIFT to an autoReduce state into
+ ** a SHIFTREDUCE action.
+ */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(ap=stp->ap; ap; ap=ap->next){
+ struct state *pNextState;
+ if( ap->type!=SHIFT ) continue;
+ pNextState = ap->x.stp;
+ if( pNextState->autoReduce && pNextState->pDfltReduce!=0 ){
+ ap->type = SHIFTREDUCE;
+ ap->x.rp = pNextState->pDfltReduce;
+ }
+ }
+ }
+
+ /* If a SHIFTREDUCE action specifies a rule that has a single RHS term
+ ** (meaning that the SHIFTREDUCE will land back in the state where it
+ ** started) and if there is no C-code associated with the reduce action,
+ ** then we can go ahead and convert the action to be the same as the
+ ** action for the RHS of the rule.
+ */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(ap=stp->ap; ap; ap=nextap){
+ nextap = ap->next;
+ if( ap->type!=SHIFTREDUCE ) continue;
+ rp = ap->x.rp;
+ if( rp->noCode==0 ) continue;
+ if( rp->nrhs!=1 ) continue;
+#if 1
+ /* Only apply this optimization to non-terminals. It would be OK to
+ ** apply it to terminal symbols too, but that makes the parser tables
+ ** larger. */
+ if( ap->sp->index<lemp->nterminal ) continue;
+#endif
+ /* If we reach this point, it means the optimization can be applied */
+ nextap = ap;
+ for(ap2=stp->ap; ap2 && (ap2==ap || ap2->sp!=rp->lhs); ap2=ap2->next){}
+ assert( ap2!=0 );
+ ap->spOpt = ap2->sp;
+ ap->type = ap2->type;
+ ap->x = ap2->x;
+ }
+ }
+}
+
+
+/*
+** Compare two states for sorting purposes. The smaller state is the
+** one with the most non-terminal actions. If they have the same number
+** of non-terminal actions, then the smaller is the one with the most
+** token actions.
+*/
+static int stateResortCompare(const void *a, const void *b){
+ const struct state *pA = *(const struct state**)a;
+ const struct state *pB = *(const struct state**)b;
+ int n;
+
+ n = pB->nNtAct - pA->nNtAct;
+ if( n==0 ){
+ n = pB->nTknAct - pA->nTknAct;
+ if( n==0 ){
+ n = pB->statenum - pA->statenum;
+ }
+ }
+ assert( n!=0 );
+ return n;
+}
+
+
+/*
+** Renumber and resort states so that states with fewer choices
+** occur at the end. Except, keep state 0 as the first state.
+*/
+void ResortStates(struct lemon *lemp)
+{
+ int i;
+ struct state *stp;
+ struct action *ap;
+
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ stp->nTknAct = stp->nNtAct = 0;
+ stp->iDfltReduce = -1; /* Init dflt action to "syntax error" */
+ stp->iTknOfst = NO_OFFSET;
+ stp->iNtOfst = NO_OFFSET;
+ for(ap=stp->ap; ap; ap=ap->next){
+ int iAction = compute_action(lemp,ap);
+ if( iAction>=0 ){
+ if( ap->sp->index<lemp->nterminal ){
+ stp->nTknAct++;
+ }else if( ap->sp->index<lemp->nsymbol ){
+ stp->nNtAct++;
+ }else{
+ assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp );
+ stp->iDfltReduce = iAction;
+ }
+ }
+ }
+ }
+ qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]),
+ stateResortCompare);
+ for(i=0; i<lemp->nstate; i++){
+ lemp->sorted[i]->statenum = i;
+ }
+ lemp->nxstate = lemp->nstate;
+ while( lemp->nxstate>1 && lemp->sorted[lemp->nxstate-1]->autoReduce ){
+ lemp->nxstate--;
+ }
+}
+
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(int n)
+{
+ size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(void){
+ char *s;
+ s = (char*)calloc( size, 1);
+ if( s==0 ){
+ memory_error();
+ }
+ return s;
+}
+
+/* Deallocate a set */
+void SetFree(char *s)
+{
+ free(s);
+}
+
+/* Add a new element to the set. Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(char *s, int e)
+{
+ int rv;
+ assert( e>=0 && e<size );
+ rv = s[e];
+ s[e] = 1;
+ return !rv;
+}
+
+/* Add every element of s2 to s1. Return TRUE if s1 changes. */
+int SetUnion(char *s1, char *s2)
+{
+ int i, progress;
+ progress = 0;
+ for(i=0; i<size; i++){
+ if( s2[i]==0 ) continue;
+ if( s1[i]==0 ){
+ progress = 1;
+ s1[i] = 1;
+ }
+ }
+ return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE unsigned strhash(const char *x)
+{
+ unsigned h = 0;
+ while( *x ) h = h*13 + *(x++);
+ return h;
+}
+
+/* Works like strdup, sort of. Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+const char *Strsafe(const char *y)
+{
+ const char *z;
+ char *cpy;
+
+ if( y==0 ) return 0;
+ z = Strsafe_find(y);
+ if( z==0 && (cpy=(char *)malloc( lemonStrlen(y)+1 ))!=0 ){
+ lemon_strcpy(cpy,y);
+ z = cpy;
+ Strsafe_insert(z);
+ }
+ MemoryCheck(z);
+ return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x1node *tbl; /* The data stored here */
+ struct s_x1node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+ const char *data; /* The data */
+ struct s_x1node *next; /* Next entry with the same hash */
+ struct s_x1node **from; /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(void){
+ if( x1a ) return;
+ x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+ if( x1a ){
+ x1a->size = 1024;
+ x1a->count = 0;
+ x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*));
+ if( x1a->tbl==0 ){
+ free(x1a);
+ x1a = 0;
+ }else{
+ int i;
+ x1a->ht = (x1node**)&(x1a->tbl[1024]);
+ for(i=0; i<1024; i++) x1a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(const char *data)
+{
+ x1node *np;
+ unsigned h;
+ unsigned ph;
+
+ if( x1a==0 ) return 0;
+ ph = strhash(data);
+ h = ph & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x1a->count>=x1a->size ){
+ /* Need to make the hash table bigger */
+ int i,arrSize;
+ struct s_x1 array;
+ array.size = arrSize = x1a->size*2;
+ array.count = x1a->count;
+ array.tbl = (x1node*)calloc(arrSize, sizeof(x1node) + sizeof(x1node*));
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x1node**)&(array.tbl[arrSize]);
+ for(i=0; i<arrSize; i++) array.ht[i] = 0;
+ for(i=0; i<x1a->count; i++){
+ x1node *oldnp, *newnp;
+ oldnp = &(x1a->tbl[i]);
+ h = strhash(oldnp->data) & (arrSize-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ /* free(x1a->tbl); // This program was originally for 16-bit machines.
+ ** Don't worry about freeing memory on modern platforms. */
+ *x1a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x1a->size-1);
+ np = &(x1a->tbl[x1a->count++]);
+ np->data = data;
+ if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+ np->next = x1a->ht[h];
+ x1a->ht[h] = np;
+ np->from = &(x1a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+const char *Strsafe_find(const char *key)
+{
+ unsigned h;
+ x1node *np;
+
+ if( x1a==0 ) return 0;
+ h = strhash(key) & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(const char *x)
+{
+ struct symbol *sp;
+
+ sp = Symbol_find(x);
+ if( sp==0 ){
+ sp = (struct symbol *)calloc(1, sizeof(struct symbol) );
+ MemoryCheck(sp);
+ sp->name = Strsafe(x);
+ sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL;
+ sp->rule = 0;
+ sp->fallback = 0;
+ sp->prec = -1;
+ sp->assoc = UNK;
+ sp->firstset = 0;
+ sp->lambda = LEMON_FALSE;
+ sp->destructor = 0;
+ sp->destLineno = 0;
+ sp->datatype = 0;
+ sp->useCnt = 0;
+ Symbol_insert(sp,sp->name);
+ }
+ sp->useCnt++;
+ return sp;
+}
+
+/* Compare two symbols for sorting purposes. Return negative,
+** zero, or positive if a is less then, equal to, or greater
+** than b.
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals). And MULTITERMINAL symbols (created using the
+** %token_class directive) must sort at the very end. Other than
+** that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(const void *_a, const void *_b)
+{
+ const struct symbol *a = *(const struct symbol **) _a;
+ const struct symbol *b = *(const struct symbol **) _b;
+ int i1 = a->type==MULTITERMINAL ? 3 : a->name[0]>'Z' ? 2 : 1;
+ int i2 = b->type==MULTITERMINAL ? 3 : b->name[0]>'Z' ? 2 : 1;
+ return i1==i2 ? a->index - b->index : i1 - i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x2node *tbl; /* The data stored here */
+ struct s_x2node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+ struct symbol *data; /* The data */
+ const char *key; /* The key */
+ struct s_x2node *next; /* Next entry with the same hash */
+ struct s_x2node **from; /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(void){
+ if( x2a ) return;
+ x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+ if( x2a ){
+ x2a->size = 128;
+ x2a->count = 0;
+ x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*));
+ if( x2a->tbl==0 ){
+ free(x2a);
+ x2a = 0;
+ }else{
+ int i;
+ x2a->ht = (x2node**)&(x2a->tbl[128]);
+ for(i=0; i<128; i++) x2a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(struct symbol *data, const char *key)
+{
+ x2node *np;
+ unsigned h;
+ unsigned ph;
+
+ if( x2a==0 ) return 0;
+ ph = strhash(key);
+ h = ph & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x2a->count>=x2a->size ){
+ /* Need to make the hash table bigger */
+ int i,arrSize;
+ struct s_x2 array;
+ array.size = arrSize = x2a->size*2;
+ array.count = x2a->count;
+ array.tbl = (x2node*)calloc(arrSize, sizeof(x2node) + sizeof(x2node*));
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x2node**)&(array.tbl[arrSize]);
+ for(i=0; i<arrSize; i++) array.ht[i] = 0;
+ for(i=0; i<x2a->count; i++){
+ x2node *oldnp, *newnp;
+ oldnp = &(x2a->tbl[i]);
+ h = strhash(oldnp->key) & (arrSize-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ /* free(x2a->tbl); // This program was originally written for 16-bit
+ ** machines. Don't worry about freeing this trivial amount of memory
+ ** on modern platforms. Just leak it. */
+ *x2a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x2a->size-1);
+ np = &(x2a->tbl[x2a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+ np->next = x2a->ht[h];
+ x2a->ht[h] = np;
+ np->from = &(x2a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct symbol *Symbol_find(const char *key)
+{
+ unsigned h;
+ x2node *np;
+
+ if( x2a==0 ) return 0;
+ h = strhash(key) & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return the n-th data. Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(int n)
+{
+ struct symbol *data;
+ if( x2a && n>0 && n<=x2a->count ){
+ data = x2a->tbl[n-1].data;
+ }else{
+ data = 0;
+ }
+ return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+ return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+ struct symbol **array;
+ int i,arrSize;
+ if( x2a==0 ) return 0;
+ arrSize = x2a->count;
+ array = (struct symbol **)calloc(arrSize, sizeof(struct symbol *));
+ if( array ){
+ for(i=0; i<arrSize; i++) array[i] = x2a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Compare two configurations */
+int Configcmp(const char *_a,const char *_b)
+{
+ const struct config *a = (struct config *) _a;
+ const struct config *b = (struct config *) _b;
+ int x;
+ x = a->rp->index - b->rp->index;
+ if( x==0 ) x = a->dot - b->dot;
+ return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(struct config *a, struct config *b)
+{
+ int rc;
+ for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){
+ rc = a->rp->index - b->rp->index;
+ if( rc==0 ) rc = a->dot - b->dot;
+ }
+ if( rc==0 ){
+ if( a ) rc = 1;
+ if( b ) rc = -1;
+ }
+ return rc;
+}
+
+/* Hash a state */
+PRIVATE unsigned statehash(struct config *a)
+{
+ unsigned h=0;
+ while( a ){
+ h = h*571 + a->rp->index*37 + a->dot;
+ a = a->bp;
+ }
+ return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+ struct state *newstate;
+ newstate = (struct state *)calloc(1, sizeof(struct state) );
+ MemoryCheck(newstate);
+ return newstate;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x3node *tbl; /* The data stored here */
+ struct s_x3node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+ struct state *data; /* The data */
+ struct config *key; /* The key */
+ struct s_x3node *next; /* Next entry with the same hash */
+ struct s_x3node **from; /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(void){
+ if( x3a ) return;
+ x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+ if( x3a ){
+ x3a->size = 128;
+ x3a->count = 0;
+ x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*));
+ if( x3a->tbl==0 ){
+ free(x3a);
+ x3a = 0;
+ }else{
+ int i;
+ x3a->ht = (x3node**)&(x3a->tbl[128]);
+ for(i=0; i<128; i++) x3a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(struct state *data, struct config *key)
+{
+ x3node *np;
+ unsigned h;
+ unsigned ph;
+
+ if( x3a==0 ) return 0;
+ ph = statehash(key);
+ h = ph & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x3a->count>=x3a->size ){
+ /* Need to make the hash table bigger */
+ int i,arrSize;
+ struct s_x3 array;
+ array.size = arrSize = x3a->size*2;
+ array.count = x3a->count;
+ array.tbl = (x3node*)calloc(arrSize, sizeof(x3node) + sizeof(x3node*));
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x3node**)&(array.tbl[arrSize]);
+ for(i=0; i<arrSize; i++) array.ht[i] = 0;
+ for(i=0; i<x3a->count; i++){
+ x3node *oldnp, *newnp;
+ oldnp = &(x3a->tbl[i]);
+ h = statehash(oldnp->key) & (arrSize-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x3a->tbl);
+ *x3a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x3a->size-1);
+ np = &(x3a->tbl[x3a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+ np->next = x3a->ht[h];
+ x3a->ht[h] = np;
+ np->from = &(x3a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct state *State_find(struct config *key)
+{
+ unsigned h;
+ x3node *np;
+
+ if( x3a==0 ) return 0;
+ h = statehash(key) & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof(void)
+{
+ struct state **array;
+ int i,arrSize;
+ if( x3a==0 ) return 0;
+ arrSize = x3a->count;
+ array = (struct state **)calloc(arrSize, sizeof(struct state *));
+ if( array ){
+ for(i=0; i<arrSize; i++) array[i] = x3a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Hash a configuration */
+PRIVATE unsigned confighash(struct config *a)
+{
+ unsigned h=0;
+ h = h*571 + a->rp->index*37 + a->dot;
+ return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x4node *tbl; /* The data stored here */
+ struct s_x4node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+ struct config *data; /* The data */
+ struct s_x4node *next; /* Next entry with the same hash */
+ struct s_x4node **from; /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(void){
+ if( x4a ) return;
+ x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+ if( x4a ){
+ x4a->size = 64;
+ x4a->count = 0;
+ x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*));
+ if( x4a->tbl==0 ){
+ free(x4a);
+ x4a = 0;
+ }else{
+ int i;
+ x4a->ht = (x4node**)&(x4a->tbl[64]);
+ for(i=0; i<64; i++) x4a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(struct config *data)
+{
+ x4node *np;
+ unsigned h;
+ unsigned ph;
+
+ if( x4a==0 ) return 0;
+ ph = confighash(data);
+ h = ph & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp((const char *) np->data,(const char *) data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x4a->count>=x4a->size ){
+ /* Need to make the hash table bigger */
+ int i,arrSize;
+ struct s_x4 array;
+ array.size = arrSize = x4a->size*2;
+ array.count = x4a->count;
+ array.tbl = (x4node*)calloc(arrSize, sizeof(x4node) + sizeof(x4node*));
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x4node**)&(array.tbl[arrSize]);
+ for(i=0; i<arrSize; i++) array.ht[i] = 0;
+ for(i=0; i<x4a->count; i++){
+ x4node *oldnp, *newnp;
+ oldnp = &(x4a->tbl[i]);
+ h = confighash(oldnp->data) & (arrSize-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ /* free(x4a->tbl); // This code was originall written for 16-bit machines.
+ ** on modern machines, don't worry about freeing this trival amount of
+ ** memory. */
+ *x4a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x4a->size-1);
+ np = &(x4a->tbl[x4a->count++]);
+ np->data = data;
+ if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+ np->next = x4a->ht[h];
+ x4a->ht[h] = np;
+ np->from = &(x4a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct config *Configtable_find(struct config *key)
+{
+ int h;
+ x4node *np;
+
+ if( x4a==0 ) return 0;
+ h = confighash(key) & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp((const char *) np->data,(const char *) key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Remove all data from the table. Pass each data to the function "f"
+** as it is removed. ("f" may be null to avoid this step.) */
+void Configtable_clear(int(*f)(struct config *))
+{
+ int i;
+ if( x4a==0 || x4a->count==0 ) return;
+ if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+ for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+ x4a->count = 0;
+ return;
+}
diff --git a/tool/lempar.c b/tool/lempar.c
new file mode 100644
index 0000000..8cc5789
--- /dev/null
+++ b/tool/lempar.c
@@ -0,0 +1,1068 @@
+/*
+** 2000-05-29
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Driver template for the LEMON parser generator.
+**
+** The "lemon" program processes an LALR(1) input grammar file, then uses
+** this template to construct a parser. The "lemon" program inserts text
+** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the
+** interstitial "-" characters) contained in this template is changed into
+** the value of the %name directive from the grammar. Otherwise, the content
+** of this template is copied straight through into the generate parser
+** source file.
+**
+** The following is the concatenation of all %include directives from the
+** input grammar file:
+*/
+/************ Begin %include sections from the grammar ************************/
+%%
+/**************** End of %include directives **********************************/
+/* These constants specify the various numeric values for terminal symbols.
+***************** Begin token definitions *************************************/
+%%
+/**************** End token definitions ***************************************/
+
+/* The next sections is a series of control #defines.
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used to store the integer codes
+** that represent terminal and non-terminal symbols.
+** "unsigned char" is used if there are fewer than
+** 256 symbols. Larger types otherwise.
+** YYNOCODE is a number of type YYCODETYPE that is not used for
+** any terminal or nonterminal symbol.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** (also known as: "terminal symbols") have fall-back
+** values which should be used if the original symbol
+** would not parse. This permits keywords to sometimes
+** be used as identifiers, for example.
+** YYACTIONTYPE is the data type used for "action codes" - numbers
+** that indicate what to do in response to the next
+** token.
+** ParseTOKENTYPE is the data type used for minor type for terminal
+** symbols. Background: A "minor type" is a semantic
+** value associated with a terminal or non-terminal
+** symbols. For example, for an "ID" terminal symbol,
+** the minor type might be the name of the identifier.
+** Each non-terminal can have a different minor type.
+** Terminal symbols all have the same minor type, though.
+** This macros defines the minor type for terminal
+** symbols.
+** YYMINORTYPE is the data type used for all minor types.
+** This is typically a union of many types, one of
+** which is ParseTOKENTYPE. The entry in the union
+** for terminal symbols is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack. If
+** zero the stack is dynamically sized using realloc()
+** ParseARG_SDECL A static variable declaration for the %extra_argument
+** ParseARG_PDECL A parameter declaration for the %extra_argument
+** ParseARG_PARAM Code to pass %extra_argument as a subroutine parameter
+** ParseARG_STORE Code to store %extra_argument into yypParser
+** ParseARG_FETCH Code to extract %extra_argument from yypParser
+** ParseCTX_* As ParseARG_ except for %extra_context
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYNTOKEN Number of terminal symbols
+** YY_MAX_SHIFT Maximum value for shift actions
+** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
+** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
+** YY_ERROR_ACTION The yy_action[] code for syntax error
+** YY_ACCEPT_ACTION The yy_action[] code for accept
+** YY_NO_ACTION The yy_action[] code for no-op
+** YY_MIN_REDUCE Minimum value for reduce actions
+** YY_MAX_REDUCE Maximum value for reduce actions
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/************* Begin control #defines *****************************************/
+%%
+/************* End control #defines *******************************************/
+#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage. For production
+** code the yytestcase() macro should be turned off. But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
+** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE.
+**
+** N == YY_ERROR_ACTION A syntax error has occurred.
+**
+** N == YY_ACCEPT_ACTION The parser accepts its input.
+**
+** N == YY_NO_ACTION No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
+** and YY_MAX_REDUCE
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as either:
+**
+** (A) N = yy_action[ yy_shift_ofst[S] + X ]
+** (B) N = yy_default[S]
+**
+** The (A) formula is preferred. The B formula is used instead if
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X.
+**
+** The formulas above are for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+**
+*********** Begin parsing tables **********************************************/
+%%
+/********** End of lemon-generated parsing tables *****************************/
+
+/* The next table maps tokens (terminal symbols) into fallback tokens.
+** If a construct like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+**
+** This feature can be used, for example, to cause some keywords in a language
+** to revert to identifiers if they keyword does not apply in the context where
+** it appears.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+%%
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+**
+** After the "shift" half of a SHIFTREDUCE action, the stateno field
+** actually contains the reduce action for the second half of the
+** SHIFTREDUCE.
+*/
+struct yyStackEntry {
+ YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */
+ YYCODETYPE major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ yyStackEntry *yytos; /* Pointer to top element of the stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+ int yyhwm; /* High-water mark of the stack */
+#endif
+#ifndef YYNOERRORRECOVERY
+ int yyerrcnt; /* Shifts left before out of the error */
+#endif
+ ParseARG_SDECL /* A place to hold %extra_argument */
+ ParseCTX_SDECL /* A place to hold %extra_context */
+#if YYSTACKDEPTH<=0
+ int yystksz; /* Current side of the stack */
+ yyStackEntry *yystack; /* The parser's stack */
+ yyStackEntry yystk0; /* First stack entry */
+#else
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+ yyStackEntry *yystackEnd; /* Last entry in the stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#include <assert.h>
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#if defined(YYCOVERAGE) || !defined(NDEBUG)
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *const yyTokenName[] = {
+%%
+};
+#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack. Return the number
+** of errors. Return 0 on success.
+*/
+static int yyGrowStack(yyParser *p){
+ int newSize;
+ int idx;
+ yyStackEntry *pNew;
+
+ newSize = p->yystksz*2 + 100;
+ idx = p->yytos ? (int)(p->yytos - p->yystack) : 0;
+ if( p->yystack==&p->yystk0 ){
+ pNew = malloc(newSize*sizeof(pNew[0]));
+ if( pNew ) pNew[0] = p->yystk0;
+ }else{
+ pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+ }
+ if( pNew ){
+ p->yystack = pNew;
+ p->yytos = &p->yystack[idx];
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n",
+ yyTracePrompt, p->yystksz, newSize);
+ }
+#endif
+ p->yystksz = newSize;
+ }
+ return pNew==0;
+}
+#endif
+
+/* Datatype of the argument to the memory allocated passed as the
+** second argument to ParseAlloc() below. This can be changed by
+** putting an appropriate #define in the %include section of the input
+** grammar.
+*/
+#ifndef YYMALLOCARGTYPE
+# define YYMALLOCARGTYPE size_t
+#endif
+
+/* Initialize a new parser that has already been allocated.
+*/
+void ParseInit(void *yypRawParser ParseCTX_PDECL){
+ yyParser *yypParser = (yyParser*)yypRawParser;
+ ParseCTX_STORE
+#ifdef YYTRACKMAXSTACKDEPTH
+ yypParser->yyhwm = 0;
+#endif
+#if YYSTACKDEPTH<=0
+ yypParser->yytos = NULL;
+ yypParser->yystack = NULL;
+ yypParser->yystksz = 0;
+ if( yyGrowStack(yypParser) ){
+ yypParser->yystack = &yypParser->yystk0;
+ yypParser->yystksz = 1;
+ }
+#endif
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt = -1;
+#endif
+ yypParser->yytos = yypParser->yystack;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+#if YYSTACKDEPTH>0
+ yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
+#endif
+}
+
+#ifndef Parse_ENGINEALWAYSONSTACK
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) ParseCTX_PDECL){
+ yyParser *yypParser;
+ yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
+ if( yypParser ){
+ ParseCTX_STORE
+ ParseInit(yypParser ParseCTX_PARAM);
+ }
+ return (void*)yypParser;
+}
+#endif /* Parse_ENGINEALWAYSONSTACK */
+
+
+/* The following function deletes the "minor type" or semantic value
+** associated with a symbol. The symbol can be either a terminal
+** or nonterminal. "yymajor" is the symbol code, and "yypminor" is
+** a pointer to the value to be deleted. The code used to do the
+** deletions is derived from the %destructor and/or %token_destructor
+** directives of the input grammar.
+*/
+static void yy_destructor(
+ yyParser *yypParser, /* The parser */
+ YYCODETYPE yymajor, /* Type code for object to destroy */
+ YYMINORTYPE *yypminor /* The object to be destroyed */
+){
+ ParseARG_FETCH
+ ParseCTX_FETCH
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are *not* used
+ ** inside the C code.
+ */
+/********* Begin destructor definitions ***************************************/
+%%
+/********* End destructor definitions *****************************************/
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+*/
+static void yy_pop_parser_stack(yyParser *pParser){
+ yyStackEntry *yytos;
+ assert( pParser->yytos!=0 );
+ assert( pParser->yytos > pParser->yystack );
+ yytos = pParser->yytos--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yy_destructor(pParser, yytos->major, &yytos->minor);
+}
+
+/*
+** Clear all secondary memory allocations from the parser
+*/
+void ParseFinalize(void *p){
+ yyParser *pParser = (yyParser*)p;
+ while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+ if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
+#endif
+}
+
+#ifndef Parse_ENGINEALWAYSONSTACK
+/*
+** Deallocate and destroy a parser. Destructors are called for
+** all stack elements before shutting the parser down.
+**
+** If the YYPARSEFREENEVERNULL macro exists (for example because it
+** is defined in a %include section of the input grammar) then it is
+** assumed that the input pointer is never NULL.
+*/
+void ParseFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+#ifndef YYPARSEFREENEVERNULL
+ if( p==0 ) return;
+#endif
+ ParseFinalize(p);
+ (*freeProc)(p);
+}
+#endif /* Parse_ENGINEALWAYSONSTACK */
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int ParseStackPeak(void *p){
+ yyParser *pParser = (yyParser*)p;
+ return pParser->yyhwm;
+}
+#endif
+
+/* This array of booleans keeps track of the parser statement
+** coverage. The element yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y. In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(YYCOVERAGE)
+static unsigned char yycoverage[YYNSTATE][YYNTOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+** (1) has not been used by the parser, and
+** (2) is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(YYCOVERAGE)
+int ParseCoverage(FILE *out){
+ int stateno, iLookAhead, i;
+ int nMissed = 0;
+ for(stateno=0; stateno<YYNSTATE; stateno++){
+ i = yy_shift_ofst[stateno];
+ for(iLookAhead=0; iLookAhead<YYNTOKEN; iLookAhead++){
+ if( yy_lookahead[i+iLookAhead]!=iLookAhead ) continue;
+ if( yycoverage[stateno][iLookAhead]==0 ) nMissed++;
+ if( out ){
+ fprintf(out,"State %d lookahead %s %s\n", stateno,
+ yyTokenName[iLookAhead],
+ yycoverage[stateno][iLookAhead] ? "ok" : "missed");
+ }
+ }
+ }
+ return nMissed;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_shift_action(
+ YYCODETYPE iLookAhead, /* The look-ahead token */
+ YYACTIONTYPE stateno /* Current state number */
+){
+ int i;
+
+ if( stateno>YY_MAX_SHIFT ) return stateno;
+ assert( stateno <= YY_SHIFT_COUNT );
+#if defined(YYCOVERAGE)
+ yycoverage[stateno][iLookAhead] = 1;
+#endif
+ do{
+ i = yy_shift_ofst[stateno];
+ assert( i>=0 );
+ assert( i<=YY_ACTTAB_COUNT );
+ assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD );
+ assert( iLookAhead!=YYNOCODE );
+ assert( iLookAhead < YYNTOKEN );
+ i += iLookAhead;
+ assert( i<(int)YY_NLOOKAHEAD );
+ if( yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ YYCODETYPE iFallback; /* Fallback token */
+ assert( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) );
+ iFallback = yyFallback[iLookAhead];
+ if( iFallback!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */
+ iLookAhead = iFallback;
+ continue;
+ }
+#endif
+#ifdef YYWILDCARD
+ {
+ int j = i - iLookAhead + YYWILDCARD;
+ assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) );
+ if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead],
+ yyTokenName[YYWILDCARD]);
+ }
+#endif /* NDEBUG */
+ return yy_action[j];
+ }
+ }
+#endif /* YYWILDCARD */
+ return yy_default[stateno];
+ }else{
+ assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) );
+ return yy_action[i];
+ }
+ }while(1);
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_reduce_action(
+ YYACTIONTYPE stateno, /* Current state number */
+ YYCODETYPE iLookAhead /* The look-ahead token */
+){
+ int i;
+#ifdef YYERRORSYMBOL
+ if( stateno>YY_REDUCE_COUNT ){
+ return yy_default[stateno];
+ }
+#else
+ assert( stateno<=YY_REDUCE_COUNT );
+#endif
+ i = yy_reduce_ofst[stateno];
+ assert( iLookAhead!=YYNOCODE );
+ i += iLookAhead;
+#ifdef YYERRORSYMBOL
+ if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }
+#else
+ assert( i>=0 && i<YY_ACTTAB_COUNT );
+ assert( yy_lookahead[i]==iLookAhead );
+#endif
+ return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser){
+ ParseARG_FETCH
+ ParseCTX_FETCH
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+/******** Begin %stack_overflow code ******************************************/
+%%
+/******** End %stack_overflow code ********************************************/
+ ParseARG_STORE /* Suppress warning about unused %extra_argument var */
+ ParseCTX_STORE
+}
+
+/*
+** Print tracing information for a SHIFT action
+*/
+#ifndef NDEBUG
+static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){
+ if( yyTraceFILE ){
+ if( yyNewState<YYNSTATE ){
+ fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+ yyNewState);
+ }else{
+ fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+ yyNewState - YY_MIN_REDUCE);
+ }
+ }
+}
+#else
+# define yyTraceShift(X,Y,Z)
+#endif
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ YYACTIONTYPE yyNewState, /* The new state to shift in */
+ YYCODETYPE yyMajor, /* The major token to shift in */
+ ParseTOKENTYPE yyMinor /* The minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yytos++;
+#ifdef YYTRACKMAXSTACKDEPTH
+ if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+ yypParser->yyhwm++;
+ assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) );
+ }
+#endif
+#if YYSTACKDEPTH>0
+ if( yypParser->yytos>yypParser->yystackEnd ){
+ yypParser->yytos--;
+ yyStackOverflow(yypParser);
+ return;
+ }
+#else
+ if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){
+ if( yyGrowStack(yypParser) ){
+ yypParser->yytos--;
+ yyStackOverflow(yypParser);
+ return;
+ }
+ }
+#endif
+ if( yyNewState > YY_MAX_SHIFT ){
+ yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
+ }
+ yytos = yypParser->yytos;
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor.yy0 = yyMinor;
+ yyTraceShift(yypParser, yyNewState, "Shift");
+}
+
+/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
+** of that rule */
+static const YYCODETYPE yyRuleInfoLhs[] = {
+%%
+};
+
+/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
+** of symbols on the right-hand side of that rule. */
+static const signed char yyRuleInfoNRhs[] = {
+%%
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+**
+** The yyLookahead and yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any). The yyLookahead will be YYNOCODE
+** if the lookahead token has already been consumed. As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
+*/
+static YYACTIONTYPE yy_reduce(
+ yyParser *yypParser, /* The parser */
+ unsigned int yyruleno, /* Number of the rule by which to reduce */
+ int yyLookahead, /* Lookahead token, or YYNOCODE if none */
+ ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */
+ ParseCTX_PDECL /* %extra_context */
+){
+ int yygoto; /* The next state */
+ YYACTIONTYPE yyact; /* The next action */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ ParseARG_FETCH
+ (void)yyLookahead;
+ (void)yyLookaheadToken;
+ yymsp = yypParser->yytos;
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+/********** Begin reduce actions **********************************************/
+%%
+/********** End reduce actions ************************************************/
+ };
+ assert( yyruleno<sizeof(yyRuleInfoLhs)/sizeof(yyRuleInfoLhs[0]) );
+ yygoto = yyRuleInfoLhs[yyruleno];
+ yysize = yyRuleInfoNRhs[yyruleno];
+ yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto);
+
+ /* There are no SHIFTREDUCE actions on nonterminals because the table
+ ** generator has simplified them to pure REDUCE actions. */
+ assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) );
+
+ /* It is not possible for a REDUCE to be followed by an error */
+ assert( yyact!=YY_ERROR_ACTION );
+
+ yymsp += yysize+1;
+ yypParser->yytos = yymsp;
+ yymsp->stateno = (YYACTIONTYPE)yyact;
+ yymsp->major = (YYCODETYPE)yygoto;
+ yyTraceShift(yypParser, yyact, "... then shift");
+ return yyact;
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH
+ ParseCTX_FETCH
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+/************ Begin %parse_failure code ***************************************/
+%%
+/************ End %parse_failure code *****************************************/
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ ParseTOKENTYPE yyminor /* The minor type of the error token */
+){
+ ParseARG_FETCH
+ ParseCTX_FETCH
+#define TOKEN yyminor
+/************ Begin %syntax_error code ****************************************/
+%%
+/************ End %syntax_error code ******************************************/
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH
+ ParseCTX_FETCH
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt = -1;
+#endif
+ assert( yypParser->yytos==yypParser->yystack );
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+/*********** Begin %parse_accept code *****************************************/
+%%
+/*********** End %parse_accept code *******************************************/
+ ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+ ParseCTX_STORE
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ ParseTOKENTYPE yyminor /* The value for the token */
+ ParseARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ YYACTIONTYPE yyact; /* The parser action. */
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+ int yyendofinput; /* True if we are at the end of input */
+#endif
+#ifdef YYERRORSYMBOL
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+#endif
+ yyParser *yypParser = (yyParser*)yyp; /* The parser */
+ ParseCTX_FETCH
+ ParseARG_STORE
+
+ assert( yypParser->yytos!=0 );
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+ yyendofinput = (yymajor==0);
+#endif
+
+ yyact = yypParser->yytos->stateno;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ if( yyact < YY_MIN_REDUCE ){
+ fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
+ yyTracePrompt,yyTokenName[yymajor],yyact);
+ }else{
+ fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+ yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
+ }
+ }
+#endif
+
+ while(1){ /* Exit by "break" */
+ assert( yypParser->yytos>=yypParser->yystack );
+ assert( yyact==yypParser->yytos->stateno );
+ yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
+ if( yyact >= YY_MIN_REDUCE ){
+ unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */
+#ifndef NDEBUG
+ assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) );
+ if( yyTraceFILE ){
+ int yysize = yyRuleInfoNRhs[yyruleno];
+ if( yysize ){
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+ yyTracePrompt,
+ yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
+ yypParser->yytos[yysize].stateno);
+ }else{
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
+ yyTracePrompt, yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
+ }
+ }
+#endif /* NDEBUG */
+
+ /* Check that the stack is large enough to grow by a single entry
+ ** if the RHS of the rule is empty. This ensures that there is room
+ ** enough on the stack to push the LHS value */
+ if( yyRuleInfoNRhs[yyruleno]==0 ){
+#ifdef YYTRACKMAXSTACKDEPTH
+ if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+ yypParser->yyhwm++;
+ assert( yypParser->yyhwm ==
+ (int)(yypParser->yytos - yypParser->yystack));
+ }
+#endif
+#if YYSTACKDEPTH>0
+ if( yypParser->yytos>=yypParser->yystackEnd ){
+ yyStackOverflow(yypParser);
+ break;
+ }
+#else
+ if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
+ if( yyGrowStack(yypParser) ){
+ yyStackOverflow(yypParser);
+ break;
+ }
+ }
+#endif
+ }
+ yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor ParseCTX_PARAM);
+ }else if( yyact <= YY_MAX_SHIFTREDUCE ){
+ yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt--;
+#endif
+ break;
+ }else if( yyact==YY_ACCEPT_ACTION ){
+ yypParser->yytos--;
+ yy_accept(yypParser);
+ return;
+ }else{
+ assert( yyact == YY_ERROR_ACTION );
+ yyminorunion.yy0 = yyminor;
+#ifdef YYERRORSYMBOL
+ int yymx;
+#endif
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminor);
+ }
+ yymx = yypParser->yytos->major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while( yypParser->yytos > yypParser->yystack ){
+ yyact = yy_find_reduce_action(yypParser->yytos->stateno,
+ YYERRORSYMBOL);
+ if( yyact<=YY_MAX_SHIFTREDUCE ) break;
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){
+ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt = -1;
+#endif
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+ if( yymajor==YYNOCODE ) break;
+ yyact = yypParser->yytos->stateno;
+#elif defined(YYNOERRORRECOVERY)
+ /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+ ** do any kind of error recovery. Instead, simply invoke the syntax
+ ** error routine and continue going as if nothing had happened.
+ **
+ ** Applications can set this macro (for example inside %include) if
+ ** they intend to abandon the parse upon the first syntax error seen.
+ */
+ yy_syntax_error(yypParser,yymajor, yyminor);
+ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+ break;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor, yyminor);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt = -1;
+#endif
+ }
+ break;
+#endif
+ }
+ }
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ yyStackEntry *i;
+ char cDiv = '[';
+ fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt);
+ for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){
+ fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]);
+ cDiv = ' ';
+ }
+ fprintf(yyTraceFILE,"]\n");
+ }
+#endif
+ return;
+}
+
+/*
+** Return the fallback token corresponding to canonical token iToken, or
+** 0 if iToken has no fallback.
+*/
+int ParseFallback(int iToken){
+#ifdef YYFALLBACK
+ assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) );
+ return yyFallback[iToken];
+#else
+ (void)iToken;
+ return 0;
+#endif
+}
diff --git a/tool/libvers.c b/tool/libvers.c
new file mode 100644
index 0000000..6911dbd
--- /dev/null
+++ b/tool/libvers.c
@@ -0,0 +1,15 @@
+/*
+** Compile this program against an SQLite library of unknown version
+** and then run this program, and it will print out the SQLite version
+** information.
+*/
+#include <stdio.h>
+
+extern const char *sqlite3_libversion(void);
+extern const char *sqlite3_sourceid(void);
+
+int main(int argc, char **argv){
+ printf("SQLite version %s\n", sqlite3_libversion());
+ printf("SQLite source %s\n", sqlite3_sourceid());
+ return 0;
+}
diff --git a/tool/loadfts.c b/tool/loadfts.c
new file mode 100644
index 0000000..0000797
--- /dev/null
+++ b/tool/loadfts.c
@@ -0,0 +1,242 @@
+/*
+** 2014-07-28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements a utility program that will load many disk
+** files (all files under a given directory) into a FTS table. This is
+** used for performance testing of FTS3, FTS4, and FTS5.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include "sqlite3.h"
+
+/*
+** Implementation of the "readtext(X)" SQL function. The entire content
+** of the file named X is read and returned as a TEXT value. It is assumed
+** the file contains UTF-8 text. NULL is returned if the file does not
+** exist or is unreadable.
+*/
+static void readfileFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zName;
+ FILE *in;
+ long nIn;
+ void *pBuf;
+
+ zName = (const char*)sqlite3_value_text(argv[0]);
+ if( zName==0 ) return;
+ in = fopen(zName, "rb");
+ if( in==0 ) return;
+ fseek(in, 0, SEEK_END);
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc( nIn );
+ if( pBuf && 1==fread(pBuf, nIn, 1, in) ){
+ sqlite3_result_text(context, pBuf, nIn, sqlite3_free);
+ }else{
+ sqlite3_free(pBuf);
+ }
+ fclose(in);
+}
+
+/*
+** Print usage text for this program and exit.
+*/
+static void showHelp(const char *zArgv0){
+ printf("\n"
+"Usage: %s SWITCHES... DB\n"
+"\n"
+" This program opens the database named on the command line and attempts to\n"
+" create an FTS table named \"fts\" with a single column. If successful, it\n"
+" recursively traverses the directory named by the -dir option and inserts\n"
+" the contents of each file into the fts table. All files are assumed to\n"
+" contain UTF-8 text.\n"
+"\n"
+"Switches are:\n"
+" -fts [345] FTS version to use (default=5)\n"
+" -idx [01] Create a mapping from filename to rowid (default=0)\n"
+" -dir <path> Root of directory tree to load data from (default=.)\n"
+" -trans <integer> Number of inserts per transaction (default=1)\n"
+, zArgv0
+);
+ exit(1);
+}
+
+/*
+** Exit with a message based on the argument and the current value of errno.
+*/
+static void error_out(const char *zText){
+ fprintf(stderr, "%s: %s\n", zText, strerror(errno));
+ exit(-1);
+}
+
+/*
+** Exit with a message based on the first argument and the error message
+** currently stored in database handle db.
+*/
+static void sqlite_error_out(const char *zText, sqlite3 *db){
+ fprintf(stderr, "%s: %s\n", zText, sqlite3_errmsg(db));
+ exit(-1);
+}
+
+/*
+** Context object for visit_file().
+*/
+typedef struct VisitContext VisitContext;
+struct VisitContext {
+ int nRowPerTrans;
+ sqlite3 *db; /* Database handle */
+ sqlite3_stmt *pInsert; /* INSERT INTO fts VALUES(readtext(:1)) */
+};
+
+/*
+** Callback used with traverse(). The first argument points to an object
+** of type VisitContext. This function inserts the contents of the text
+** file zPath into the FTS table.
+*/
+void visit_file(void *pCtx, const char *zPath){
+ int rc;
+ VisitContext *p = (VisitContext*)pCtx;
+ /* printf("%s\n", zPath); */
+ sqlite3_bind_text(p->pInsert, 1, zPath, -1, SQLITE_STATIC);
+ sqlite3_step(p->pInsert);
+ rc = sqlite3_reset(p->pInsert);
+ if( rc!=SQLITE_OK ){
+ sqlite_error_out("insert", p->db);
+ }else if( p->nRowPerTrans>0
+ && (sqlite3_last_insert_rowid(p->db) % p->nRowPerTrans)==0
+ ){
+ sqlite3_exec(p->db, "COMMIT ; BEGIN", 0, 0, 0);
+ }
+}
+
+/*
+** Recursively traverse directory zDir. For each file that is not a
+** directory, invoke the supplied callback with its path.
+*/
+static void traverse(
+ const char *zDir, /* Directory to traverse */
+ void *pCtx, /* First argument passed to callback */
+ void (*xCallback)(void*, const char *zPath)
+){
+ DIR *d;
+ struct dirent *e;
+
+ d = opendir(zDir);
+ if( d==0 ) error_out("opendir()");
+
+ for(e=readdir(d); e; e=readdir(d)){
+ if( strcmp(e->d_name, ".")==0 || strcmp(e->d_name, "..")==0 ) continue;
+ char *zPath = sqlite3_mprintf("%s/%s", zDir, e->d_name);
+ if (e->d_type & DT_DIR) {
+ traverse(zPath, pCtx, xCallback);
+ }else{
+ xCallback(pCtx, zPath);
+ }
+ sqlite3_free(zPath);
+ }
+
+ closedir(d);
+}
+
+int main(int argc, char **argv){
+ int iFts = 5; /* Value of -fts option */
+ int bMap = 0; /* True to create mapping table */
+ const char *zDir = "."; /* Directory to scan */
+ int i;
+ int rc;
+ int nRowPerTrans = 0;
+ sqlite3 *db;
+ char *zSql;
+ VisitContext sCtx;
+
+ int nCmd = 0;
+ char **aCmd = 0;
+
+ if( argc % 2 ) showHelp(argv[0]);
+
+ for(i=1; i<(argc-1); i+=2){
+ char *zOpt = argv[i];
+ char *zArg = argv[i+1];
+ if( strcmp(zOpt, "-fts")==0 ){
+ iFts = atoi(zArg);
+ if( iFts!=3 && iFts!=4 && iFts!= 5) showHelp(argv[0]);
+ }
+ else if( strcmp(zOpt, "-trans")==0 ){
+ nRowPerTrans = atoi(zArg);
+ }
+ else if( strcmp(zOpt, "-idx")==0 ){
+ bMap = atoi(zArg);
+ if( bMap!=0 && bMap!=1 ) showHelp(argv[0]);
+ }
+ else if( strcmp(zOpt, "-dir")==0 ){
+ zDir = zArg;
+ }
+ else if( strcmp(zOpt, "-special")==0 ){
+ nCmd++;
+ aCmd = sqlite3_realloc(aCmd, sizeof(char*) * nCmd);
+ aCmd[nCmd-1] = zArg;
+ }
+ else{
+ showHelp(argv[0]);
+ }
+ }
+
+ /* Open the database file */
+ rc = sqlite3_open(argv[argc-1], &db);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_open()", db);
+
+ rc = sqlite3_create_function(db, "readtext", 1, SQLITE_UTF8, 0,
+ readfileFunc, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_create_function()", db);
+
+ /* Create the FTS table */
+ zSql = sqlite3_mprintf("CREATE VIRTUAL TABLE fts USING fts%d(content)", iFts);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db);
+ sqlite3_free(zSql);
+
+ for(i=0; i<nCmd; i++){
+ zSql = sqlite3_mprintf("INSERT INTO fts(fts) VALUES(%Q)", aCmd[i]);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db);
+ sqlite3_free(zSql);
+ }
+
+ /* Compile the INSERT statement to write data to the FTS table. */
+ memset(&sCtx, 0, sizeof(VisitContext));
+ sCtx.db = db;
+ sCtx.nRowPerTrans = nRowPerTrans;
+ rc = sqlite3_prepare_v2(db,
+ "INSERT INTO fts VALUES(readtext(?))", -1, &sCtx.pInsert, 0
+ );
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_prepare_v2(1)", db);
+
+ /* Load all files in the directory hierarchy into the FTS table. */
+ if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "BEGIN", 0, 0, 0);
+ traverse(zDir, (void*)&sCtx, visit_file);
+ if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
+
+ /* Clean up and exit. */
+ sqlite3_finalize(sCtx.pInsert);
+ sqlite3_close(db);
+ sqlite3_free(aCmd);
+ return 0;
+}
diff --git a/tool/logest.c b/tool/logest.c
new file mode 100644
index 0000000..d916b43
--- /dev/null
+++ b/tool/logest.c
@@ -0,0 +1,173 @@
+/*
+** 2013-06-10
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a simple command-line utility for converting from
+** integers and LogEst values and back again and for doing simple
+** arithmetic operations (multiple and add) on LogEst values.
+**
+** Usage:
+**
+** ./LogEst ARGS
+**
+** See the showHelp() routine for a description of valid arguments.
+** Examples:
+**
+** To convert 123 from LogEst to integer:
+**
+** ./LogEst ^123
+**
+** To convert 123456 from integer to LogEst:
+**
+** ./LogEst 123456
+**
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+#include "sqlite3.h"
+
+typedef short int LogEst; /* 10 times log2() */
+
+LogEst logEstMultiply(LogEst a, LogEst b){ return a+b; }
+LogEst logEstAdd(LogEst a, LogEst b){
+ static const unsigned char x[] = {
+ 10, 10, /* 0,1 */
+ 9, 9, /* 2,3 */
+ 8, 8, /* 4,5 */
+ 7, 7, 7, /* 6,7,8 */
+ 6, 6, 6, /* 9,10,11 */
+ 5, 5, 5, /* 12-14 */
+ 4, 4, 4, 4, /* 15-18 */
+ 3, 3, 3, 3, 3, 3, /* 19-24 */
+ 2, 2, 2, 2, 2, 2, 2, /* 25-31 */
+ };
+ if( a<b ){ LogEst t = a; a = b; b = t; }
+ if( a>b+49 ) return a;
+ if( a>b+31 ) return a+1;
+ return a+x[a-b];
+}
+LogEst logEstFromInteger(sqlite3_uint64 x){
+ static LogEst a[] = { 0, 2, 3, 5, 6, 7, 8, 9 };
+ LogEst y = 40;
+ if( x<8 ){
+ if( x<2 ) return 0;
+ while( x<8 ){ y -= 10; x <<= 1; }
+ }else{
+ while( x>255 ){ y += 40; x >>= 4; }
+ while( x>15 ){ y += 10; x >>= 1; }
+ }
+ return a[x&7] + y - 10;
+}
+static sqlite3_uint64 logEstToInt(LogEst x){
+ sqlite3_uint64 n;
+ if( x<10 ) return 1;
+ n = x%10;
+ x /= 10;
+ if( n>=5 ) n -= 2;
+ else if( n>=1 ) n -= 1;
+ if( x>60 ) return (((sqlite3_uint64)0xffffffff)<<32)+(sqlite3_uint64)0xffffffff;
+ if( x>=3 ) return (n+8)<<(x-3);
+ return (n+8)>>(3-x);
+}
+static LogEst logEstFromDouble(double x){
+ sqlite3_uint64 a;
+ LogEst e;
+ assert( sizeof(x)==8 && sizeof(a)==8 );
+ if( x<=0.0 ) return -32768;
+ if( x<0.01 ) return -logEstFromDouble(1.0/x);
+ if( x<1.0 ) return logEstFromDouble(100.0*x) - 66;
+ if( x<1024.0 ) return logEstFromInteger((sqlite3_uint64)(1024.0*x)) - 100;
+ if( x<=2000000000.0 ) return logEstFromInteger((sqlite3_uint64)x);
+ memcpy(&a, &x, 8);
+ e = (a>>52) - 1022;
+ return e*10;
+}
+
+int isInteger(const char *z){
+ while( z[0]>='0' && z[0]<='9' ) z++;
+ return z[0]==0;
+}
+
+int isFloat(const char *z){
+ char c;
+ while( ((c=z[0])>='0' && c<='9') || c=='.' || c=='E' || c=='e'
+ || c=='+' || c=='-' ) z++;
+ return z[0]==0;
+}
+
+static void showHelp(const char *zArgv0){
+ printf("Usage: %s ARGS...\n", zArgv0);
+ printf("Arguments:\n"
+ " NUM Convert NUM from integer to LogEst and push onto the stack\n"
+ " ^NUM Interpret NUM as a LogEst and push onto stack\n"
+ " x Multiple the top two elements of the stack\n"
+ " + Add the top two elements of the stack\n"
+ " dup Dupliate the top element on the stack\n"
+ " inv Take the reciprocal of the top of stack. N = 1/N.\n"
+ " log Find the LogEst of the number on top of stack\n"
+ " nlogn Compute NlogN where N is the top of stack\n"
+ );
+ exit(1);
+}
+
+int main(int argc, char **argv){
+ int i;
+ int n = 0;
+ LogEst a[100];
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( strcmp(z,"+")==0 ){
+ if( n>=2 ){
+ a[n-2] = logEstAdd(a[n-2],a[n-1]);
+ n--;
+ }
+ }else if( strcmp(z,"x")==0 ){
+ if( n>=2 ){
+ a[n-2] = logEstMultiply(a[n-2],a[n-1]);
+ n--;
+ }
+ }else if( strcmp(z,"dup")==0 ){
+ if( n>0 ){
+ a[n] = a[n-1];
+ n++;
+ }
+ }else if( strcmp(z,"log")==0 ){
+ if( n>0 ) a[n-1] = logEstFromInteger(a[n-1]) - 33;
+ }else if( strcmp(z,"nlogn")==0 ){
+ if( n>0 ) a[n-1] += logEstFromInteger(a[n-1]) - 33;
+ }else if( strcmp(z,"inv")==0 ){
+ if( n>0 ) a[n-1] = -a[n-1];
+ }else if( z[0]=='^' ){
+ a[n++] = (LogEst)atoi(z+1);
+ }else if( isInteger(z) ){
+ a[n++] = logEstFromInteger(atoll(z));
+ }else if( isFloat(z) && z[0]!='-' ){
+ a[n++] = logEstFromDouble(atof(z));
+ }else{
+ showHelp(argv[0]);
+ }
+ }
+ for(i=n-1; i>=0; i--){
+ if( a[i]<-40 ){
+ printf("%5d (%f)\n", a[i], 1.0/(double)logEstToInt(-a[i]));
+ }else if( a[i]<10 ){
+ printf("%5d (%f)\n", a[i], logEstToInt(a[i]+100)/1024.0);
+ }else if( a[i]>100 ){
+ printf("%5d (%lld)\n", a[i], logEstToInt(a[i]));
+ }else{
+ sqlite3_uint64 x = logEstToInt(a[i]+100)*100/1024;
+ printf("%5d (%lld.%02lld)\n", a[i], x/100, x%100);
+ }
+ }
+ return 0;
+}
diff --git a/tool/max-limits.c b/tool/max-limits.c
new file mode 100644
index 0000000..d019974
--- /dev/null
+++ b/tool/max-limits.c
@@ -0,0 +1,41 @@
+/*
+** Link this program against an SQLite library of unknown provenance in order
+** to display the compile-time maximum values for various settings.
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+
+static const struct {
+ int eCode;
+ char *zName;
+} aLimit[] = {
+ { SQLITE_LIMIT_LENGTH, "SQLITE_MAX_LENGTH" },
+ { SQLITE_LIMIT_SQL_LENGTH, "SQLITE_MAX_SQL_LENGTH" },
+ { SQLITE_LIMIT_COLUMN, "SQLITE_MAX_COLUMN" },
+ { SQLITE_LIMIT_EXPR_DEPTH, "SQLITE_MAX_EXPR_DEPTH" },
+ { SQLITE_LIMIT_COMPOUND_SELECT, "SQLITE_MAX_COMPOUND_SELECT" },
+ { SQLITE_LIMIT_VDBE_OP, "SQLITE_MAX_VDBE_OP" },
+ { SQLITE_LIMIT_FUNCTION_ARG, "SQLITE_MAX_FUNCTION_ARG" },
+ { SQLITE_LIMIT_ATTACHED, "SQLITE_MAX_ATTACHED" },
+ { SQLITE_LIMIT_LIKE_PATTERN_LENGTH, "SQLITE_MAX_LIKE_PATTERN_LENGTH" },
+ { SQLITE_LIMIT_VARIABLE_NUMBER, "SQLITE_MAX_VARIABLE_NUMBER" },
+ { SQLITE_LIMIT_TRIGGER_DEPTH, "SQLITE_MAX_TRIGGER_DEPTH" },
+ { SQLITE_LIMIT_WORKER_THREADS, "SQLITE_MAX_WORKER_THREADS" },
+};
+
+static int maxLimit(sqlite3 *db, int eCode){
+ int iOrig = sqlite3_limit(db, eCode, 0x7fffffff);
+ return sqlite3_limit(db, eCode, iOrig);
+}
+
+int main(int argc, char **argv){
+ sqlite3 *db;
+ int j, rc;
+ rc = sqlite3_open(":memory:", &db);
+ if( rc==SQLITE_OK ){
+ for(j=0; j<sizeof(aLimit)/sizeof(aLimit[0]); j++){
+ printf("%-35s %10d\n", aLimit[j].zName, maxLimit(db, aLimit[j].eCode));
+ }
+ sqlite3_close(db);
+ }
+}
diff --git a/tool/merge-test.tcl b/tool/merge-test.tcl
new file mode 100644
index 0000000..2010d67
--- /dev/null
+++ b/tool/merge-test.tcl
@@ -0,0 +1,99 @@
+#!/usr/bin/tcl
+#
+# Run this script to test to see that the latest trunk changes can be
+# merged into LTS branches without breaking anything.
+#
+# To Use:
+#
+# * Copy this script into a directory above the sqlite checkout
+# * Run "fossil update trunk" and "fossil revert"
+# * Run "tclsh ../merge-test.tcl" (in other words run this script)
+#
+# Operation:
+#
+# This script changes to each LTS branch to be tested, merges the latest
+# trunk changes into the branch (without committing them) and then
+# runs "make test". Any errors are stored in local files.
+#
+# Limitations:
+#
+# Some LTS branches are not synced directly from trunk but rather from
+# other LTS branches. These other branches cannot be tested because
+# there is no good way to generate the intermediate merges.
+#
+###############################################################################
+
+# Run a shell command contained in arguments. Put the return code in
+# global variable ::res and the output string in global variable ::result
+#
+proc safeexec {args} {
+ global res result
+ set res [catch "exec $args" result]
+}
+
+# Run the shell command contained in arguments. Print an error and exit
+# if anything goes wrong.
+#
+proc mustbeok {args} {
+ global res result
+ set res [catch "exec $args" result]
+ if {$res} {
+ puts "FAILED: $args"
+ puts $result
+ exit 1
+ }
+}
+
+# Write $content into a file named $filename. The file is overwritten if it
+# already exist. The file is create if it does not already exist.
+#
+proc writefile {filename content} {
+ set fd [open $filename wb]
+ puts $fd $content
+ close $fd
+}
+
+# Run the merge-test
+#
+foreach {branch configopts} {
+ begin-concurrent {--enable-json1}
+ begin-concurrent-pnu {--enable-json1}
+ wal2 {--enable-all}
+ reuse-schema {--enable-all}
+} {
+ puts $branch
+ set errorfile ${branch}-error.txt
+ mustbeok fossil revert
+ mustbeok fossil up $branch
+ safeexec fossil merge trunk
+ if {$res} {
+ puts " merge failed - see $errorfile"
+ writefile $errorfile $result
+ } else {
+ puts " merge ok"
+ safeexec ./configure --enable-debug {*}$configopts
+ if {$res} {
+ puts " configure failed - see $errorfile"
+ writefile $errorfile $result
+ } else {
+ puts " configure ok"
+ safeexec make fuzzcheck sqlite3 testfixture
+ if {$res} {
+ puts " build failed - see $errorfile"
+ writefile $errorfile $result
+ } else {
+ puts " build ok"
+ safeexec make test
+ if {$res} {
+ puts " test failed - see $errorfile"
+ writefile $errorfile $result
+ } else {
+ puts " test ok"
+ }
+ }
+ }
+ }
+}
+mustbeok fossil revert
+mustbeok fossil up trunk
+puts "reset back to trunk"
diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh
new file mode 100644
index 0000000..35dbfb4
--- /dev/null
+++ b/tool/mkautoconfamal.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+# This script is used to build the amalgamation autoconf package.
+# It assumes the following:
+#
+# 1. The files "sqlite3.c", "sqlite3.h", "sqlite3ext.h", "shell.c",
+# and "sqlite3rc.h" are available in the current directory.
+#
+# 2. Variable $TOP is set to the full path of the root directory
+# of the SQLite source tree.
+#
+# 3. There is nothing of value in the ./mkpkg_tmp_dir directory.
+# This is important, as the script executes "rm -rf ./mkpkg_tmp_dir".
+#
+
+
+# Bail out of the script if any command returns a non-zero exit
+# status. Or if the script tries to use an unset variable. These
+# may fail for old /bin/sh interpreters.
+#
+set -e
+set -u
+
+TMPSPACE=./mkpkg_tmp_dir
+VERSION=`cat $TOP/VERSION`
+HASH=`sed 's/^\(..........\).*/\1/' $TOP/manifest.uuid`
+DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\).*/\1/'`
+
+# Verify that the version number in the TEA autoconf file is correct.
+# Fail with an error if not.
+#
+if grep $VERSION $TOP/autoconf/tea/configure.ac
+then echo "TEA version number ok"
+else echo "TEA version number mismatch. Should be $VERSION"; exit 1
+fi
+
+# If this script is given an argument of --snapshot, then generate a
+# snapshot tarball named for the current checkout SHA1 hash, rather than
+# the version number.
+#
+if test "$#" -ge 1 -a x$1 != x--snapshot
+then
+ # Set global variable $ARTIFACT to the "3xxyyzz" string incorporated
+ # into artifact filenames. And $VERSION2 to the "3.x.y[.z]" form.
+ xx=`echo $VERSION|sed 's/3\.\([0-9]*\)\..*/\1/'`
+ yy=`echo $VERSION|sed 's/3\.[^.]*\.\([0-9]*\).*/\1/'`
+ zz=0
+ set +e
+ zz=`echo $VERSION|sed 's/3\.[^.]*\.[^.]*\.\([0-9]*\).*/\1/'|grep -v '\.'`
+ set -e
+ TARBALLNAME=`printf "sqlite-autoconf-3%.2d%.2d%.2d" $xx $yy $zz`
+else
+ TARBALLNAME=sqlite-snapshot-$DATETIME
+fi
+
+rm -rf $TMPSPACE
+cp -R $TOP/autoconf $TMPSPACE
+cp sqlite3.c $TMPSPACE
+cp sqlite3.h $TMPSPACE
+cp sqlite3ext.h $TMPSPACE
+cp sqlite3rc.h $TMPSPACE
+cp $TOP/sqlite3.1 $TMPSPACE
+cp $TOP/sqlite3.pc.in $TMPSPACE
+cp shell.c $TMPSPACE
+cp $TOP/src/sqlite3.rc $TMPSPACE
+cp $TOP/tool/Replace.cs $TMPSPACE
+
+cat $TMPSPACE/configure.ac |
+sed "s/--SQLITE-VERSION--/$VERSION/" > $TMPSPACE/tmp
+mv $TMPSPACE/tmp $TMPSPACE/configure.ac
+
+cd $TMPSPACE
+autoreconf -i
+#libtoolize
+#aclocal
+#autoconf
+#automake --add-missing
+
+mkdir -p tea/generic
+echo "#ifdef USE_SYSTEM_SQLITE" > tea/generic/tclsqlite3.c
+echo "# include <sqlite3.h>" >> tea/generic/tclsqlite3.c
+echo "#else" >> tea/generic/tclsqlite3.c
+echo "#include \"sqlite3.c\"" >> tea/generic/tclsqlite3.c
+echo "#endif" >> tea/generic/tclsqlite3.c
+cat $TOP/src/tclsqlite.c >> tea/generic/tclsqlite3.c
+
+cat tea/configure.ac |
+ sed "s/AC_INIT(\[sqlite\], .*)/AC_INIT([sqlite], [$VERSION])/" > tmp
+mv tmp tea/configure.ac
+
+cd tea
+autoconf
+rm -rf autom4te.cache
+
+cd ../
+./configure && make dist
+tar -xzf sqlite-$VERSION.tar.gz
+mv sqlite-$VERSION $TARBALLNAME
+tar -czf $TARBALLNAME.tar.gz $TARBALLNAME
+mv $TARBALLNAME.tar.gz ..
+cd ..
+ls -l $TARBALLNAME.tar.gz
diff --git a/tool/mkccode.tcl b/tool/mkccode.tcl
new file mode 100755
index 0000000..41b09f1
--- /dev/null
+++ b/tool/mkccode.tcl
@@ -0,0 +1,93 @@
+#!/usr/bin/tclsh
+#
+# Use this script to build C-language source code for a program that uses
+# tclsqlite.c together with custom TCL scripts and/or C extensions for
+# either SQLite or TCL.
+#
+# Usage example:
+#
+# tclsh mktclsqliteprog.tcl demoapp.c.in >demoapp.c
+#
+# The demoapp.c.in file contains a mixture of C code, TCL script, and
+# processing directives used by mktclsqliteprog.tcl to build the final C-code
+# output file. Most lines of demoapp.c.in are copied straight through into
+# the output. The following control directives are recognized:
+#
+# BEGIN_STRING
+#
+# This marks the beginning of large string literal - usually a TCL
+# script of some kind. Subsequent lines of text through the first
+# line that begins with END_STRING are converted into a C-language
+# string literal.
+#
+# INCLUDE path
+#
+# The path argument is the name of a file to be inserted in place of
+# the INCLUDE line. The path can begin with $ROOT to signify the
+# root of the SQLite source tree, or $HOME to signify the directory
+# that contains the demoapp.c.in input script itself. If the path does
+# not begin with either $ROOT or $HOME, then it is interpreted relative
+# to the current working directory.
+#
+# If the INCLUDE occurs in the middle of BEGIN_STRING...END_STRING
+# then all of the text in the input file is converted into C-language
+# string literals.
+#
+# None of the control directives described above will nest. Only the
+# top-level input file ("demoapp.c.in" in the example) is interpreted.
+# referenced files are copied verbatim.
+#
+if {[llength $argv]!=1} {
+ puts stderr "Usage: $argv0 TEMPLATE >OUTPUT"
+ exit 1
+}
+set infile [lindex $argv 0]
+set ROOT [file normalize [file dir $argv0]/..]
+set HOME [file normalize [file dir $infile]]
+set in [open $infile rb]
+puts [subst {/* DO NOT EDIT
+**
+** This file was generated by \"$argv0 $infile\".
+** To make changes, edit $infile then rerun the generator
+** command.
+*/}]
+set instr 0
+while {1} {
+ set line [gets $in]
+ if {[eof $in]} break
+ if {[regexp {^INCLUDE (.*)} $line all path]} {
+ regsub {^\$ROOT\y} $path $ROOT path
+ regsub {^\$HOME\y} $path $HOME path
+ set in2 [open $path rb]
+ puts "/* INCLUDE $path */"
+ if {$instr} {
+ while {1} {
+ set line [gets $in2]
+ if {[eof $in2]} break
+ set x [string map "\\\\ \\\\\\\\ \\\" \\\\\"" $line]
+ puts "\"$x\\n\""
+ }
+ } else {
+ puts [read $in2]
+ }
+ puts "/* END $path */"
+ close $in2
+ continue
+ }
+ if {[regexp {^BEGIN_STRING} $line]} {
+ set instr 1
+ puts "/* BEGIN_STRING */"
+ continue
+ }
+ if {[regexp {^END_STRING} $line]} {
+ set instr 0
+ puts "/* END_STRING */"
+ continue
+ }
+ if {$instr} {
+ set x [string map "\\\\ \\\\\\\\ \\\" \\\\\"" $line]
+ puts "\"$x\\n\""
+ } else {
+ puts $line
+ }
+}
diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl
new file mode 100755
index 0000000..9e425c0
--- /dev/null
+++ b/tool/mkctimec.tcl
@@ -0,0 +1,458 @@
+#!/usr/bin/tclsh
+#
+# To build the
+#
+# const char **azCompileOpt[]
+#
+# definition used in src/ctime.c, run this script from
+# the checkout root. It generates src/ctime.c .
+#
+# Results are normally written into src/ctime.c. But if an argument is
+# provided, results are written there instead. Examples:
+#
+# tclsh tool/mkctimec.tcl ;# <-- results to src/ctime.c
+#
+# tclsh tool/mkctimec.tcl /dev/tty ;# <-- results to the terminal
+#
+
+
+set ::headWarning {/* DO NOT EDIT!
+** This file is automatically generated by the script in the canonical
+** SQLite source tree at tool/mkctimec.tcl.
+**
+** To modify this header, edit any of the various lists in that script
+** which specify categories of generated conditionals in this file.
+*/}
+
+# Make { and } easier to put into literals (even on EBCDIC machines.)
+regexp {(\{)(\})} "{}" ma ::lb ::rb
+
+set ::headCode "
+/*
+** 2010 February 23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements routines used to report what compile-time options
+** SQLite was built with.
+*/
+#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */
+
+/*
+** Include the configuration header output by 'configure' if we're using the
+** autoconf-based build
+*/
+#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H)
+#include \"sqlite_cfg.h\"
+#define SQLITECONFIG_H 1
+#endif
+
+/* These macros are provided to \"stringify\" the value of the define
+** for those options in which the value is meaningful. */
+#define CTIMEOPT_VAL_(opt) #opt
+#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
+
+/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This
+** option requires a separate macro because legal values contain a single
+** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE=\"100,100\") */
+#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 \",\" #opt2
+#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt)
+#include \"sqliteInt.h\"
+
+/*
+** An array of names of all compile-time options. This array should
+** be sorted A-Z.
+**
+** This array looks large, but in a typical installation actually uses
+** only a handful of compile-time options, so most times this array is usually
+** rather short and uses little memory space.
+*/
+static const char * const sqlite3azCompileOpt\[\] = $::lb
+"
+
+set ::tailCode "
+$::rb ;
+
+const char **sqlite3CompileOptions(int *pnOpt){
+ *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt\[0\]);
+ return (const char**)sqlite3azCompileOpt;
+}
+
+#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
+"
+
+# All Boolean compile time options which default to something
+# other than 0 or empty. The default is paired with the PP
+# symbol so that a differing define can be detected.
+#
+set boolean_defnnz_options {
+ {SQLITE_HOMEGROWN_RECURSIVE_MUTEX 1}
+ {SQLITE_POWERSAFE_OVERWRITE 1}
+ {SQLITE_DEFAULT_MEMSTATUS 1}
+ {SQLITE_OMIT_TRACE 1}
+ {SQLITE_ALLOW_COVERING_INDEX_SCAN 1}
+}
+
+# All Boolean compile time options which default to 0 or empty.
+#
+set boolean_defnil_options {
+ SQLITE_32BIT_ROWID
+ SQLITE_4_BYTE_ALIGNED_MALLOC
+ SQLITE_ALLOW_URI_AUTHORITY
+ SQLITE_BUG_COMPATIBLE_20160819
+ SQLITE_CASE_SENSITIVE_LIKE
+ SQLITE_CHECK_PAGES
+ SQLITE_COVERAGE_TEST
+ SQLITE_DEBUG
+ SQLITE_DEFAULT_AUTOMATIC_INDEX
+ SQLITE_DEFAULT_AUTOVACUUM
+ SQLITE_DEFAULT_CKPTFULLFSYNC
+ SQLITE_DEFAULT_FOREIGN_KEYS
+ SQLITE_DEFAULT_LOCKING_MODE
+ SQLITE_DEFAULT_RECURSIVE_TRIGGERS
+ SQLITE_DEFAULT_SYNCHRONOUS
+ SQLITE_DEFAULT_WAL_SYNCHRONOUS
+ SQLITE_DIRECT_OVERFLOW_READ
+ SQLITE_DISABLE_DIRSYNC
+ SQLITE_DISABLE_FTS3_UNICODE
+ SQLITE_DISABLE_FTS4_DEFERRED
+ SQLITE_DISABLE_INTRINSIC
+ SQLITE_DISABLE_LFS
+ SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS
+ SQLITE_DISABLE_SKIPAHEAD_DISTINCT
+ SQLITE_ENABLE_8_3_NAMES
+ SQLITE_ENABLE_API_ARMOR
+ SQLITE_ENABLE_ATOMIC_WRITE
+ SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ SQLITE_ENABLE_BYTECODE_VTAB
+ SQLITE_ENABLE_COLUMN_METADATA
+ SQLITE_ENABLE_COLUMN_USED_MASK
+ SQLITE_ENABLE_COSTMULT
+ SQLITE_ENABLE_CURSOR_HINTS
+ SQLITE_ENABLE_DBPAGE_VTAB
+ SQLITE_ENABLE_DBSTAT_VTAB
+ SQLITE_ENABLE_EXPENSIVE_ASSERT
+ SQLITE_ENABLE_EXPLAIN_COMMENTS
+ SQLITE_ENABLE_FTS3
+ SQLITE_ENABLE_FTS3_PARENTHESIS
+ SQLITE_ENABLE_FTS3_TOKENIZER
+ SQLITE_ENABLE_FTS4
+ SQLITE_ENABLE_FTS5
+ SQLITE_ENABLE_GEOPOLY
+ SQLITE_ENABLE_HIDDEN_COLUMNS
+ SQLITE_ENABLE_ICU
+ SQLITE_ENABLE_IOTRACE
+ SQLITE_ENABLE_LOAD_EXTENSION
+ SQLITE_ENABLE_LOCKING_STYLE
+ SQLITE_ENABLE_MATH_FUNCTIONS
+ SQLITE_ENABLE_MEMORY_MANAGEMENT
+ SQLITE_ENABLE_MEMSYS3
+ SQLITE_ENABLE_MEMSYS5
+ SQLITE_ENABLE_MULTIPLEX
+ SQLITE_ENABLE_NORMALIZE
+ SQLITE_ENABLE_NULL_TRIM
+ SQLITE_ENABLE_OFFSET_SQL_FUNC
+ SQLITE_ENABLE_OVERSIZE_CELL_CHECK
+ SQLITE_ENABLE_PREUPDATE_HOOK
+ SQLITE_ENABLE_QPSG
+ SQLITE_ENABLE_RBU
+ SQLITE_ENABLE_RTREE
+ SQLITE_ENABLE_SESSION
+ SQLITE_ENABLE_SNAPSHOT
+ SQLITE_ENABLE_SORTER_REFERENCES
+ SQLITE_ENABLE_SQLLOG
+ SQLITE_ENABLE_STAT4
+ SQLITE_ENABLE_STMT_SCANSTATUS
+ SQLITE_ENABLE_STMTVTAB
+ SQLITE_ENABLE_TREETRACE
+ SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+ SQLITE_ENABLE_UNLOCK_NOTIFY
+ SQLITE_ENABLE_UPDATE_DELETE_LIMIT
+ SQLITE_ENABLE_URI_00_ERROR
+ SQLITE_ENABLE_VFSTRACE
+ SQLITE_ENABLE_WHERETRACE
+ SQLITE_ENABLE_ZIPVFS
+ SQLITE_EXPLAIN_ESTIMATED_ROWS
+ SQLITE_EXTRA_IFNULLROW
+ SQLITE_FTS5_ENABLE_TEST_MI
+ SQLITE_FTS5_NO_WITHOUT_ROWID
+ SQLITE_IGNORE_AFP_LOCK_ERRORS
+ SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+ SQLITE_INLINE_MEMCPY
+ SQLITE_INT64_TYPE
+ SQLITE_LEGACY_JSON_VALID
+ SQLITE_LIKE_DOESNT_MATCH_BLOBS
+ SQLITE_LOCK_TRACE
+ SQLITE_LOG_CACHE_SPILL
+ SQLITE_MEMDEBUG
+ SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+ SQLITE_MMAP_READWRITE
+ SQLITE_MUTEX_NOOP
+ SQLITE_MUTEX_OMIT
+ SQLITE_MUTEX_PTHREADS
+ SQLITE_MUTEX_W32
+ SQLITE_NEED_ERR_NAME
+ SQLITE_NO_SYNC
+ SQLITE_OMIT_ALTERTABLE
+ SQLITE_OMIT_ANALYZE
+ SQLITE_OMIT_ATTACH
+ SQLITE_OMIT_AUTHORIZATION
+ SQLITE_OMIT_AUTOINCREMENT
+ SQLITE_OMIT_AUTOINIT
+ SQLITE_OMIT_AUTOMATIC_INDEX
+ SQLITE_OMIT_AUTORESET
+ SQLITE_OMIT_AUTOVACUUM
+ SQLITE_OMIT_BETWEEN_OPTIMIZATION
+ SQLITE_OMIT_BLOB_LITERAL
+ SQLITE_OMIT_CAST
+ SQLITE_OMIT_CHECK
+ SQLITE_OMIT_COMPLETE
+ SQLITE_OMIT_COMPOUND_SELECT
+ SQLITE_OMIT_CONFLICT_CLAUSE
+ SQLITE_OMIT_CTE
+ SQLITE_OMIT_DECLTYPE
+ SQLITE_OMIT_DEPRECATED
+ SQLITE_OMIT_DESERIALIZE
+ SQLITE_OMIT_DISKIO
+ SQLITE_OMIT_EXPLAIN
+ SQLITE_OMIT_FLAG_PRAGMAS
+ SQLITE_OMIT_FLOATING_POINT
+ SQLITE_OMIT_FOREIGN_KEY
+ SQLITE_OMIT_GET_TABLE
+ SQLITE_OMIT_HEX_INTEGER
+ SQLITE_OMIT_INCRBLOB
+ SQLITE_OMIT_INTEGRITY_CHECK
+ SQLITE_OMIT_INTROSPECTION_PRAGMAS
+ SQLITE_OMIT_JSON
+ SQLITE_OMIT_LIKE_OPTIMIZATION
+ SQLITE_OMIT_LOAD_EXTENSION
+ SQLITE_OMIT_LOCALTIME
+ SQLITE_OMIT_LOOKASIDE
+ SQLITE_OMIT_MEMORYDB
+ SQLITE_OMIT_OR_OPTIMIZATION
+ SQLITE_OMIT_PAGER_PRAGMAS
+ SQLITE_OMIT_PARSER_TRACE
+ SQLITE_OMIT_POPEN
+ SQLITE_OMIT_PRAGMA
+ SQLITE_OMIT_PROGRESS_CALLBACK
+ SQLITE_OMIT_QUICKBALANCE
+ SQLITE_OMIT_REINDEX
+ SQLITE_OMIT_SCHEMA_PRAGMAS
+ SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+ SQLITE_OMIT_SEH
+ SQLITE_OMIT_SHARED_CACHE
+ SQLITE_OMIT_SHUTDOWN_DIRECTORIES
+ SQLITE_OMIT_SUBQUERY
+ SQLITE_OMIT_TCL_VARIABLE
+ SQLITE_OMIT_TEMPDB
+ SQLITE_OMIT_TEST_CONTROL
+ SQLITE_OMIT_TRIGGER
+ SQLITE_OMIT_TRUNCATE_OPTIMIZATION
+ SQLITE_OMIT_UTF16
+ SQLITE_OMIT_VACUUM
+ SQLITE_OMIT_VIEW
+ SQLITE_OMIT_VIRTUALTABLE
+ SQLITE_OMIT_WAL
+ SQLITE_OMIT_WSD
+ SQLITE_OMIT_XFER_OPT
+ SQLITE_PERFORMANCE_TRACE
+ SQLITE_PREFER_PROXY_LOCKING
+ SQLITE_PROXY_DEBUG
+ SQLITE_REVERSE_UNORDERED_SELECTS
+ SQLITE_RTREE_INT_ONLY
+ SQLITE_SECURE_DELETE
+ SQLITE_SMALL_STACK
+ SQLITE_SOUNDEX
+ SQLITE_SUBSTR_COMPATIBILITY
+ SQLITE_TCL
+ SQLITE_TEST
+ SQLITE_UNLINK_AFTER_CLOSE
+ SQLITE_UNTESTABLE
+ SQLITE_USE_ALLOCA
+ SQLITE_USE_FCNTL_TRACE
+ SQLITE_USER_AUTHENTICATION
+ SQLITE_USE_URI
+ SQLITE_VDBE_COVERAGE
+ SQLITE_WIN32_MALLOC
+ SQLITE_ZERO_MALLOC
+}
+
+# All compile time options for which the assigned value is other than boolean
+# and is a comma-separated scalar pair.
+#
+set value2_options {
+ SQLITE_DEFAULT_LOOKASIDE
+}
+
+# All compile time options for which the assigned value is other than boolean
+# and is a single scalar.
+#
+set value_options {
+ SQLITE_ATOMIC_INTRINSICS
+ SQLITE_BITMASK_TYPE
+ SQLITE_DEFAULT_CACHE_SIZE
+ SQLITE_DEFAULT_FILE_FORMAT
+ SQLITE_DEFAULT_FILE_PERMISSIONS
+ SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
+ SQLITE_DEFAULT_LOCKING_MODE
+ SQLITE_DEFAULT_MMAP_SIZE
+ SQLITE_DEFAULT_PAGE_SIZE
+ SQLITE_DEFAULT_PCACHE_INITSZ
+ SQLITE_DEFAULT_PROXYDIR_PERMISSIONS
+ SQLITE_DEFAULT_ROWEST
+ SQLITE_DEFAULT_SECTOR_SIZE
+ SQLITE_DEFAULT_SYNCHRONOUS
+ SQLITE_DEFAULT_WAL_AUTOCHECKPOINT
+ SQLITE_DEFAULT_WAL_SYNCHRONOUS
+ SQLITE_DEFAULT_WORKER_THREADS
+ SQLITE_DQS
+ SQLITE_ENABLE_8_3_NAMES
+ SQLITE_ENABLE_CEROD
+ SQLITE_ENABLE_LOCKING_STYLE
+ SQLITE_EXTRA_AUTOEXT
+ SQLITE_EXTRA_INIT
+ SQLITE_EXTRA_SHUTDOWN
+ SQLITE_FTS3_MAX_EXPR_DEPTH
+ SQLITE_INTEGRITY_CHECK_ERROR_MAX
+ SQLITE_MALLOC_SOFT_LIMIT
+ SQLITE_MAX_ATTACHED
+ SQLITE_MAX_COLUMN
+ SQLITE_MAX_COMPOUND_SELECT
+ SQLITE_MAX_DEFAULT_PAGE_SIZE
+ SQLITE_MAX_EXPR_DEPTH
+ SQLITE_MAX_FUNCTION_ARG
+ SQLITE_MAX_LENGTH
+ SQLITE_MAX_LIKE_PATTERN_LENGTH
+ SQLITE_MAX_MEMORY
+ SQLITE_MAX_MMAP_SIZE
+ SQLITE_MAX_MMAP_SIZE_
+ SQLITE_MAX_PAGE_COUNT
+ SQLITE_MAX_PAGE_SIZE
+ SQLITE_MAX_SCHEMA_RETRY
+ SQLITE_MAX_SQL_LENGTH
+ SQLITE_MAX_TRIGGER_DEPTH
+ SQLITE_MAX_VARIABLE_NUMBER
+ SQLITE_MAX_VDBE_OP
+ SQLITE_MAX_WORKER_THREADS
+ SQLITE_SORTER_PMASZ
+ SQLITE_STAT4_SAMPLES
+ SQLITE_STMTJRNL_SPILL
+ SQLITE_TEMP_STORE
+}
+
+# Options that require custom code.
+#
+set options(COMPILER) {
+#if defined(__clang__) && defined(__clang_major__)
+ "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "."
+ CTIMEOPT_VAL(__clang_minor__) "."
+ CTIMEOPT_VAL(__clang_patchlevel__),
+#elif defined(_MSC_VER)
+ "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER),
+#elif defined(__GNUC__) && defined(__VERSION__)
+ "COMPILER=gcc-" __VERSION__,
+#endif
+}
+set options(HAVE_ISNAN) {
+#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
+ "HAVE_ISNAN",
+#endif
+}
+set options(OMIT_DATETIME_FUNCS) {
+#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT)
+ "OMIT_DATETIME_FUNCS",
+#endif
+}
+set options(SYSTEM_MALLOC) "\
+#if (!defined(SQLITE_WIN32_MALLOC) \\
+ && !defined(SQLITE_ZERO_MALLOC) \\
+ && !defined(SQLITE_MEMDEBUG) \\
+ ) || defined(SQLITE_SYSTEM_MALLOC)
+ \"SYSTEM_MALLOC\",
+#endif
+"
+set options(THREADSAFE) {
+#if defined(SQLITE_THREADSAFE)
+ "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE),
+#elif defined(THREADSAFE)
+ "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE),
+#else
+ "THREADSAFE=1",
+#endif
+}
+
+proc trim_name {in} {
+ set ret $in
+ if {[string range $in 0 6]=="SQLITE_"} {
+ set ret [string range $in 7 end]
+ }
+ return $ret
+}
+
+foreach name_defval $boolean_defnnz_options {
+ set b [lindex $name_defval 0]
+ set defval [lindex $name_defval 1]
+ set name [trim_name $b]
+ set options($name) [subst {
+#ifdef $b
+# if $b != $defval
+ "$name=" CTIMEOPT_VAL($b),
+# endif
+#endif
+}]
+}
+
+foreach b $boolean_defnil_options {
+ set name [trim_name $b]
+ set options($name) [subst {
+#ifdef $b
+ "$name",
+#endif
+}]
+}
+
+foreach v $value_options {
+ set name [trim_name $v]
+ set options($name) [subst {
+#ifdef $v
+ "$name=" CTIMEOPT_VAL($v),
+#endif
+}]
+}
+
+foreach v $value2_options {
+ set name [trim_name $v]
+ set options($name) [subst {
+#ifdef $v
+ "$name=" CTIMEOPT_VAL2($v),
+#endif
+}]
+}
+
+if {$argc>0} {
+ set destfile [lindex $argv 0]
+} else {
+ set destfile "[file dir [file dir [file normal $argv0]]]/src/ctime.c"
+ puts "Overwriting $destfile..."
+}
+
+if {[catch {set cfd [open $destfile w]}]!=0} {
+ puts stderr "File '$destfile' unwritable."
+ exit 1;
+}
+
+puts $cfd $::headWarning;
+puts $cfd $::headCode;
+foreach o [lsort [array names options]] {
+ puts $cfd [string trim $options($o)]
+}
+puts -nonewline $cfd $::tailCode;
+
+close $cfd
diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c
new file mode 100644
index 0000000..5386a36
--- /dev/null
+++ b/tool/mkkeywordhash.c
@@ -0,0 +1,716 @@
+/*
+** Compile and run this standalone program in order to generate code that
+** implements a function that will translate alphabetic identifiers into
+** parser token codes.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/*
+** A header comment placed at the beginning of generated code.
+*/
+static const char zHdr[] =
+ "/***** This file contains automatically generated code ******\n"
+ "**\n"
+ "** The code in this file has been automatically generated by\n"
+ "**\n"
+ "** sqlite/tool/mkkeywordhash.c\n"
+ "**\n"
+ "** The code in this file implements a function that determines whether\n"
+ "** or not a given identifier is really an SQL keyword. The same thing\n"
+ "** might be implemented more directly using a hand-written hash table.\n"
+ "** But by using this automatically generated code, the size of the code\n"
+ "** is substantially reduced. This is important for embedded applications\n"
+ "** on platforms with limited memory.\n"
+ "*/\n"
+;
+
+/*
+** All the keywords of the SQL language are stored in a hash
+** table composed of instances of the following structure.
+*/
+typedef struct Keyword Keyword;
+struct Keyword {
+ char *zName; /* The keyword name */
+ char *zTokenType; /* Token value for this keyword */
+ int mask; /* Code this keyword if non-zero */
+ int priority; /* Put higher priorities earlier in the hash chain */
+ int id; /* Unique ID for this record */
+ int hash; /* Hash on the keyword */
+ int offset; /* Offset to start of name string */
+ int len; /* Length of this keyword, not counting final \000 */
+ int prefix; /* Number of characters in prefix */
+ int longestSuffix; /* Longest suffix that is a prefix on another word */
+ int iNext; /* Index in aKeywordTable[] of next with same hash */
+ int substrId; /* Id to another keyword this keyword is embedded in */
+ int substrOffset; /* Offset into substrId for start of this keyword */
+ char zOrigName[20]; /* Original keyword name before processing */
+};
+
+/*
+** Define masks used to determine which keywords are allowed
+*/
+#if defined(SQLITE_OMIT_ALTERTABLE) || defined(SQLITE_OMIT_VIRTUALTABLE)
+# define ALTER 0
+#else
+# define ALTER 0x00000001
+#endif
+#define ALWAYS 0x00000002
+#ifdef SQLITE_OMIT_ANALYZE
+# define ANALYZE 0
+#else
+# define ANALYZE 0x00000004
+#endif
+#ifdef SQLITE_OMIT_ATTACH
+# define ATTACH 0
+#else
+# define ATTACH 0x00000008
+#endif
+#ifdef SQLITE_OMIT_AUTOINCREMENT
+# define AUTOINCR 0
+#else
+# define AUTOINCR 0x00000010
+#endif
+#ifdef SQLITE_OMIT_CAST
+# define CAST 0
+#else
+# define CAST 0x00000020
+#endif
+#ifdef SQLITE_OMIT_COMPOUND_SELECT
+# define COMPOUND 0
+#else
+# define COMPOUND 0x00000040
+#endif
+#ifdef SQLITE_OMIT_CONFLICT_CLAUSE
+# define CONFLICT 0
+#else
+# define CONFLICT 0x00000080
+#endif
+#ifdef SQLITE_OMIT_EXPLAIN
+# define EXPLAIN 0
+#else
+# define EXPLAIN 0x00000100
+#endif
+#ifdef SQLITE_OMIT_FOREIGN_KEY
+# define FKEY 0
+#else
+# define FKEY 0x00000200
+#endif
+#ifdef SQLITE_OMIT_PRAGMA
+# define PRAGMA 0
+#else
+# define PRAGMA 0x00000400
+#endif
+#ifdef SQLITE_OMIT_REINDEX
+# define REINDEX 0
+#else
+# define REINDEX 0x00000800
+#endif
+#ifdef SQLITE_OMIT_SUBQUERY
+# define SUBQUERY 0
+#else
+# define SUBQUERY 0x00001000
+#endif
+#ifdef SQLITE_OMIT_TRIGGER
+# define TRIGGER 0
+#else
+# define TRIGGER 0x00002000
+#endif
+#if defined(SQLITE_OMIT_AUTOVACUUM) && \
+ (defined(SQLITE_OMIT_VACUUM) || defined(SQLITE_OMIT_ATTACH))
+# define VACUUM 0
+#else
+# define VACUUM 0x00004000
+#endif
+#ifdef SQLITE_OMIT_VIEW
+# define VIEW 0
+#else
+# define VIEW 0x00008000
+#endif
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+# define VTAB 0
+#else
+# define VTAB 0x00010000
+#endif
+#ifdef SQLITE_OMIT_AUTOVACUUM
+# define AUTOVACUUM 0
+#else
+# define AUTOVACUUM 0x00020000
+#endif
+#ifdef SQLITE_OMIT_CTE
+# define CTE 0
+#else
+# define CTE 0x00040000
+#endif
+#ifdef SQLITE_OMIT_UPSERT
+# define UPSERT 0
+#else
+# define UPSERT 0x00080000
+#endif
+#ifdef SQLITE_OMIT_WINDOWFUNC
+# define WINDOWFUNC 0
+#else
+# define WINDOWFUNC 0x00100000
+#endif
+#ifdef SQLITE_OMIT_GENERATED_COLUMNS
+# define GENCOL 0
+#else
+# define GENCOL 0x00200000
+#endif
+#ifdef SQLITE_OMIT_RETURNING
+# define RETURNING 0
+#else
+# define RETURNING 0x00400000
+#endif
+
+
+/*
+** These are the keywords
+*/
+static Keyword aKeywordTable[] = {
+ { "ABORT", "TK_ABORT", CONFLICT|TRIGGER, 0 },
+ { "ACTION", "TK_ACTION", FKEY, 0 },
+ { "ADD", "TK_ADD", ALTER, 1 },
+ { "AFTER", "TK_AFTER", TRIGGER, 0 },
+ { "ALL", "TK_ALL", ALWAYS, 0 },
+ { "ALTER", "TK_ALTER", ALTER, 0 },
+ { "ALWAYS", "TK_ALWAYS", GENCOL, 0 },
+ { "ANALYZE", "TK_ANALYZE", ANALYZE, 0 },
+ { "AND", "TK_AND", ALWAYS, 10 },
+ { "AS", "TK_AS", ALWAYS, 10 },
+ { "ASC", "TK_ASC", ALWAYS, 0 },
+ { "ATTACH", "TK_ATTACH", ATTACH, 1 },
+ { "AUTOINCREMENT", "TK_AUTOINCR", AUTOINCR, 0 },
+ { "BEFORE", "TK_BEFORE", TRIGGER, 0 },
+ { "BEGIN", "TK_BEGIN", ALWAYS, 1 },
+ { "BETWEEN", "TK_BETWEEN", ALWAYS, 5 },
+ { "BY", "TK_BY", ALWAYS, 10 },
+ { "CASCADE", "TK_CASCADE", FKEY, 1 },
+ { "CASE", "TK_CASE", ALWAYS, 5 },
+ { "CAST", "TK_CAST", CAST, 5 },
+ { "CHECK", "TK_CHECK", ALWAYS, 1 },
+ { "COLLATE", "TK_COLLATE", ALWAYS, 1 },
+ { "COLUMN", "TK_COLUMNKW", ALTER, 1 },
+ { "COMMIT", "TK_COMMIT", ALWAYS, 1 },
+ { "CONFLICT", "TK_CONFLICT", CONFLICT, 0 },
+ { "CONSTRAINT", "TK_CONSTRAINT", ALWAYS, 1 },
+ { "CREATE", "TK_CREATE", ALWAYS, 2 },
+ { "CROSS", "TK_JOIN_KW", ALWAYS, 3 },
+ { "CURRENT", "TK_CURRENT", WINDOWFUNC, 1 },
+ { "CURRENT_DATE", "TK_CTIME_KW", ALWAYS, 1 },
+ { "CURRENT_TIME", "TK_CTIME_KW", ALWAYS, 1 },
+ { "CURRENT_TIMESTAMP","TK_CTIME_KW", ALWAYS, 1 },
+ { "DATABASE", "TK_DATABASE", ATTACH, 0 },
+ { "DEFAULT", "TK_DEFAULT", ALWAYS, 1 },
+ { "DEFERRED", "TK_DEFERRED", ALWAYS, 1 },
+ { "DEFERRABLE", "TK_DEFERRABLE", FKEY, 1 },
+ { "DELETE", "TK_DELETE", ALWAYS, 10 },
+ { "DESC", "TK_DESC", ALWAYS, 3 },
+ { "DETACH", "TK_DETACH", ATTACH, 0 },
+ { "DISTINCT", "TK_DISTINCT", ALWAYS, 5 },
+ { "DO", "TK_DO", UPSERT, 2 },
+ { "DROP", "TK_DROP", ALWAYS, 1 },
+ { "END", "TK_END", ALWAYS, 1 },
+ { "EACH", "TK_EACH", TRIGGER, 1 },
+ { "ELSE", "TK_ELSE", ALWAYS, 2 },
+ { "ESCAPE", "TK_ESCAPE", ALWAYS, 4 },
+ { "EXCEPT", "TK_EXCEPT", COMPOUND, 4 },
+ { "EXCLUSIVE", "TK_EXCLUSIVE", ALWAYS, 1 },
+ { "EXCLUDE", "TK_EXCLUDE", WINDOWFUNC, 1 },
+ { "EXISTS", "TK_EXISTS", ALWAYS, 4 },
+ { "EXPLAIN", "TK_EXPLAIN", EXPLAIN, 1 },
+ { "FAIL", "TK_FAIL", CONFLICT|TRIGGER, 1 },
+ { "FILTER", "TK_FILTER", WINDOWFUNC, 4 },
+ { "FIRST", "TK_FIRST", ALWAYS, 4 },
+ { "FOLLOWING", "TK_FOLLOWING", WINDOWFUNC, 4 },
+ { "FOR", "TK_FOR", TRIGGER, 2 },
+ { "FOREIGN", "TK_FOREIGN", FKEY, 1 },
+ { "FROM", "TK_FROM", ALWAYS, 10 },
+ { "FULL", "TK_JOIN_KW", ALWAYS, 3 },
+ { "GENERATED", "TK_GENERATED", ALWAYS, 1 },
+ { "GLOB", "TK_LIKE_KW", ALWAYS, 3 },
+ { "GROUP", "TK_GROUP", ALWAYS, 5 },
+ { "GROUPS", "TK_GROUPS", WINDOWFUNC, 2 },
+ { "HAVING", "TK_HAVING", ALWAYS, 5 },
+ { "IF", "TK_IF", ALWAYS, 2 },
+ { "IGNORE", "TK_IGNORE", CONFLICT|TRIGGER, 1 },
+ { "IMMEDIATE", "TK_IMMEDIATE", ALWAYS, 1 },
+ { "IN", "TK_IN", ALWAYS, 10 },
+ { "INDEX", "TK_INDEX", ALWAYS, 1 },
+ { "INDEXED", "TK_INDEXED", ALWAYS, 0 },
+ { "INITIALLY", "TK_INITIALLY", FKEY, 1 },
+ { "INNER", "TK_JOIN_KW", ALWAYS, 1 },
+ { "INSERT", "TK_INSERT", ALWAYS, 10 },
+ { "INSTEAD", "TK_INSTEAD", TRIGGER, 1 },
+ { "INTERSECT", "TK_INTERSECT", COMPOUND, 5 },
+ { "INTO", "TK_INTO", ALWAYS, 10 },
+ { "IS", "TK_IS", ALWAYS, 5 },
+ { "ISNULL", "TK_ISNULL", ALWAYS, 5 },
+ { "JOIN", "TK_JOIN", ALWAYS, 5 },
+ { "KEY", "TK_KEY", ALWAYS, 1 },
+ { "LAST", "TK_LAST", ALWAYS, 4 },
+ { "LEFT", "TK_JOIN_KW", ALWAYS, 5 },
+ { "LIKE", "TK_LIKE_KW", ALWAYS, 5 },
+ { "LIMIT", "TK_LIMIT", ALWAYS, 3 },
+ { "MATCH", "TK_MATCH", ALWAYS, 2 },
+ { "MATERIALIZED", "TK_MATERIALIZED", CTE, 12 },
+ { "NATURAL", "TK_JOIN_KW", ALWAYS, 3 },
+ { "NO", "TK_NO", FKEY|WINDOWFUNC, 2 },
+ { "NOT", "TK_NOT", ALWAYS, 10 },
+ { "NOTHING", "TK_NOTHING", UPSERT, 1 },
+ { "NOTNULL", "TK_NOTNULL", ALWAYS, 3 },
+ { "NULL", "TK_NULL", ALWAYS, 10 },
+ { "NULLS", "TK_NULLS", ALWAYS, 3 },
+ { "OF", "TK_OF", ALWAYS, 3 },
+ { "OFFSET", "TK_OFFSET", ALWAYS, 1 },
+ { "ON", "TK_ON", ALWAYS, 1 },
+ { "OR", "TK_OR", ALWAYS, 9 },
+ { "ORDER", "TK_ORDER", ALWAYS, 10 },
+ { "OTHERS", "TK_OTHERS", WINDOWFUNC, 3 },
+ { "OUTER", "TK_JOIN_KW", ALWAYS, 5 },
+ { "OVER", "TK_OVER", WINDOWFUNC, 3 },
+ { "PARTITION", "TK_PARTITION", WINDOWFUNC, 3 },
+ { "PLAN", "TK_PLAN", EXPLAIN, 0 },
+ { "PRAGMA", "TK_PRAGMA", PRAGMA, 0 },
+ { "PRECEDING", "TK_PRECEDING", WINDOWFUNC, 3 },
+ { "PRIMARY", "TK_PRIMARY", ALWAYS, 1 },
+ { "QUERY", "TK_QUERY", EXPLAIN, 0 },
+ { "RAISE", "TK_RAISE", TRIGGER, 1 },
+ { "RANGE", "TK_RANGE", WINDOWFUNC, 3 },
+ { "RECURSIVE", "TK_RECURSIVE", CTE, 3 },
+ { "REFERENCES", "TK_REFERENCES", FKEY, 1 },
+ { "REGEXP", "TK_LIKE_KW", ALWAYS, 3 },
+ { "REINDEX", "TK_REINDEX", REINDEX, 1 },
+ { "RELEASE", "TK_RELEASE", ALWAYS, 1 },
+ { "RENAME", "TK_RENAME", ALTER, 1 },
+ { "REPLACE", "TK_REPLACE", CONFLICT, 10 },
+ { "RESTRICT", "TK_RESTRICT", FKEY, 1 },
+ { "RETURNING", "TK_RETURNING", RETURNING, 10 },
+ { "RIGHT", "TK_JOIN_KW", ALWAYS, 0 },
+ { "ROLLBACK", "TK_ROLLBACK", ALWAYS, 1 },
+ { "ROW", "TK_ROW", TRIGGER, 1 },
+ { "ROWS", "TK_ROWS", ALWAYS, 1 },
+ { "SAVEPOINT", "TK_SAVEPOINT", ALWAYS, 1 },
+ { "SELECT", "TK_SELECT", ALWAYS, 10 },
+ { "SET", "TK_SET", ALWAYS, 10 },
+ { "TABLE", "TK_TABLE", ALWAYS, 1 },
+ { "TEMP", "TK_TEMP", ALWAYS, 1 },
+ { "TEMPORARY", "TK_TEMP", ALWAYS, 1 },
+ { "THEN", "TK_THEN", ALWAYS, 3 },
+ { "TIES", "TK_TIES", WINDOWFUNC, 3 },
+ { "TO", "TK_TO", ALWAYS, 3 },
+ { "TRANSACTION", "TK_TRANSACTION", ALWAYS, 1 },
+ { "TRIGGER", "TK_TRIGGER", TRIGGER, 1 },
+ { "UNBOUNDED", "TK_UNBOUNDED", WINDOWFUNC, 3 },
+ { "UNION", "TK_UNION", COMPOUND, 3 },
+ { "UNIQUE", "TK_UNIQUE", ALWAYS, 1 },
+ { "UPDATE", "TK_UPDATE", ALWAYS, 10 },
+ { "USING", "TK_USING", ALWAYS, 8 },
+ { "VACUUM", "TK_VACUUM", VACUUM, 1 },
+ { "VALUES", "TK_VALUES", ALWAYS, 10 },
+ { "VIEW", "TK_VIEW", VIEW, 1 },
+ { "VIRTUAL", "TK_VIRTUAL", VTAB, 1 },
+ { "WHEN", "TK_WHEN", ALWAYS, 1 },
+ { "WHERE", "TK_WHERE", ALWAYS, 10 },
+ { "WINDOW", "TK_WINDOW", WINDOWFUNC, 3 },
+ { "WITH", "TK_WITH", CTE, 4 },
+ { "WITHOUT", "TK_WITHOUT", ALWAYS, 1 },
+};
+
+/* Number of keywords */
+static int nKeyword = (sizeof(aKeywordTable)/sizeof(aKeywordTable[0]));
+
+/* Map all alphabetic characters into lower-case for hashing. This is
+** only valid for alphabetics. In particular it does not work for '_'
+** and so the hash cannot be on a keyword position that might be an '_'.
+*/
+#define charMap(X) (0x20|(X))
+
+/*
+** Comparision function for two Keyword records
+*/
+static int keywordCompare1(const void *a, const void *b){
+ const Keyword *pA = (Keyword*)a;
+ const Keyword *pB = (Keyword*)b;
+ int n = pA->len - pB->len;
+ if( n==0 ){
+ n = strcmp(pA->zName, pB->zName);
+ }
+ assert( n!=0 );
+ return n;
+}
+static int keywordCompare2(const void *a, const void *b){
+ const Keyword *pA = (Keyword*)a;
+ const Keyword *pB = (Keyword*)b;
+ int n = pB->longestSuffix - pA->longestSuffix;
+ if( n==0 ){
+ n = strcmp(pA->zName, pB->zName);
+ }
+ assert( n!=0 );
+ return n;
+}
+static int keywordCompare3(const void *a, const void *b){
+ const Keyword *pA = (Keyword*)a;
+ const Keyword *pB = (Keyword*)b;
+ int n = pA->offset - pB->offset;
+ if( n==0 ) n = pB->id - pA->id;
+ assert( n!=0 );
+ return n;
+}
+
+/*
+** Return a KeywordTable entry with the given id
+*/
+static Keyword *findById(int id){
+ int i;
+ for(i=0; i<nKeyword; i++){
+ if( aKeywordTable[i].id==id ) break;
+ }
+ return &aKeywordTable[i];
+}
+
+/*
+** If aKeyword[*pFrom-1].iNext has a higher priority that aKeyword[*pFrom-1]
+** itself, then swap them.
+*/
+static void reorder(int *pFrom){
+ int i = *pFrom - 1;
+ int j;
+ if( i<0 ) return;
+ j = aKeywordTable[i].iNext;
+ if( j==0 ) return;
+ j--;
+ if( aKeywordTable[i].priority >= aKeywordTable[j].priority ) return;
+ aKeywordTable[i].iNext = aKeywordTable[j].iNext;
+ aKeywordTable[j].iNext = i+1;
+ *pFrom = j+1;
+ reorder(&aKeywordTable[i].iNext);
+}
+
+/* Parameter to the hash function
+*/
+#define HASH_OP ^
+#define HASH_CC '^'
+#define HASH_C0 4
+#define HASH_C1 3
+#define HASH_C2 1
+
+/*
+** This routine does the work. The generated code is printed on standard
+** output.
+*/
+int main(int argc, char **argv){
+ int i, j, k, h;
+ int bestSize, bestCount;
+ int count;
+ int nChar;
+ int totalLen = 0;
+ int aKWHash[1000]; /* 1000 is much bigger than nKeyword */
+ char zKWText[2000];
+
+ /* Remove entries from the list of keywords that have mask==0 */
+ for(i=j=0; i<nKeyword; i++){
+ if( aKeywordTable[i].mask==0 ) continue;
+ if( j<i ){
+ aKeywordTable[j] = aKeywordTable[i];
+ }
+ j++;
+ }
+ nKeyword = j;
+
+ /* Fill in the lengths of strings and hashes for all entries. */
+ for(i=0; i<nKeyword; i++){
+ Keyword *p = &aKeywordTable[i];
+ p->len = (int)strlen(p->zName);
+ assert( p->len<sizeof(p->zOrigName) );
+ memcpy(p->zOrigName, p->zName, p->len+1);
+ totalLen += p->len;
+ p->hash = (charMap(p->zName[0])*HASH_C0) HASH_OP
+ (charMap(p->zName[p->len-1])*HASH_C1) HASH_OP
+ (p->len*HASH_C2);
+ p->id = i+1;
+ }
+
+ /* Sort the table from shortest to longest keyword */
+ qsort(aKeywordTable, nKeyword, sizeof(aKeywordTable[0]), keywordCompare1);
+
+ /* Look for short keywords embedded in longer keywords */
+ for(i=nKeyword-2; i>=0; i--){
+ Keyword *p = &aKeywordTable[i];
+ for(j=nKeyword-1; j>i && p->substrId==0; j--){
+ Keyword *pOther = &aKeywordTable[j];
+ if( pOther->substrId ) continue;
+ if( pOther->len<=p->len ) continue;
+ for(k=0; k<=pOther->len-p->len; k++){
+ if( memcmp(p->zName, &pOther->zName[k], p->len)==0 ){
+ p->substrId = pOther->id;
+ p->substrOffset = k;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Compute the longestSuffix value for every word */
+ for(i=0; i<nKeyword; i++){
+ Keyword *p = &aKeywordTable[i];
+ if( p->substrId ) continue;
+ for(j=0; j<nKeyword; j++){
+ Keyword *pOther;
+ if( j==i ) continue;
+ pOther = &aKeywordTable[j];
+ if( pOther->substrId ) continue;
+ for(k=p->longestSuffix+1; k<p->len && k<pOther->len; k++){
+ if( memcmp(&p->zName[p->len-k], pOther->zName, k)==0 ){
+ p->longestSuffix = k;
+ }
+ }
+ }
+ }
+
+ /* Sort the table into reverse order by length */
+ qsort(aKeywordTable, nKeyword, sizeof(aKeywordTable[0]), keywordCompare2);
+
+ /* Fill in the offset for all entries */
+ nChar = 0;
+ for(i=0; i<nKeyword; i++){
+ Keyword *p = &aKeywordTable[i];
+ if( p->offset>0 || p->substrId ) continue;
+ p->offset = nChar;
+ nChar += p->len;
+ for(k=p->len-1; k>=1; k--){
+ for(j=i+1; j<nKeyword; j++){
+ Keyword *pOther = &aKeywordTable[j];
+ if( pOther->offset>0 || pOther->substrId ) continue;
+ if( pOther->len<=k ) continue;
+ if( memcmp(&p->zName[p->len-k], pOther->zName, k)==0 ){
+ p = pOther;
+ p->offset = nChar - k;
+ nChar = p->offset + p->len;
+ p->zName += k;
+ p->len -= k;
+ p->prefix = k;
+ j = i;
+ k = p->len;
+ }
+ }
+ }
+ }
+ for(i=0; i<nKeyword; i++){
+ Keyword *p = &aKeywordTable[i];
+ if( p->substrId ){
+ p->offset = findById(p->substrId)->offset + p->substrOffset;
+ }
+ }
+
+ /* Sort the table by offset */
+ qsort(aKeywordTable, nKeyword, sizeof(aKeywordTable[0]), keywordCompare3);
+
+ /* Figure out how big to make the hash table in order to minimize the
+ ** number of collisions */
+ bestSize = nKeyword;
+ bestCount = nKeyword*nKeyword;
+ for(i=nKeyword/2; i<=2*nKeyword; i++){
+ if( i<=0 ) continue;
+ for(j=0; j<i; j++) aKWHash[j] = 0;
+ for(j=0; j<nKeyword; j++){
+ h = aKeywordTable[j].hash % i;
+ aKWHash[h] *= 2;
+ aKWHash[h]++;
+ }
+ for(j=count=0; j<i; j++) count += aKWHash[j];
+ if( count<bestCount ){
+ bestCount = count;
+ bestSize = i;
+ }
+ }
+
+ /* Compute the hash */
+ for(i=0; i<bestSize; i++) aKWHash[i] = 0;
+ for(i=0; i<nKeyword; i++){
+ h = aKeywordTable[i].hash % bestSize;
+ aKeywordTable[i].iNext = aKWHash[h];
+ aKWHash[h] = i+1;
+ reorder(&aKWHash[h]);
+ }
+
+ /* Begin generating code */
+ printf("%s", zHdr);
+ printf("/* Hash score: %d */\n", bestCount);
+ printf("/* zKWText[] encodes %d bytes of keyword text in %d bytes */\n",
+ totalLen + nKeyword, nChar+1 );
+ for(i=j=k=0; i<nKeyword; i++){
+ Keyword *p = &aKeywordTable[i];
+ if( p->substrId ) continue;
+ memcpy(&zKWText[k], p->zName, p->len);
+ k += p->len;
+ if( j+p->len>70 ){
+ printf("%*s */\n", 74-j, "");
+ j = 0;
+ }
+ if( j==0 ){
+ printf("/* ");
+ j = 8;
+ }
+ printf("%s", p->zName);
+ j += p->len;
+ }
+ if( j>0 ){
+ printf("%*s */\n", 74-j, "");
+ }
+ printf("static const char zKWText[%d] = {\n", nChar);
+ zKWText[nChar] = 0;
+ for(i=j=0; i<k; i++){
+ if( j==0 ){
+ printf(" ");
+ }
+ if( zKWText[i]==0 ){
+ printf("0");
+ }else{
+ printf("'%c',", zKWText[i]);
+ }
+ j += 4;
+ if( j>68 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ if( j>0 ) printf("\n");
+ printf("};\n");
+
+ printf("/* aKWHash[i] is the hash value for the i-th keyword */\n");
+ printf("static const unsigned char aKWHash[%d] = {\n", bestSize);
+ for(i=j=0; i<bestSize; i++){
+ if( j==0 ) printf(" ");
+ printf(" %3d,", aKWHash[i]);
+ j++;
+ if( j>12 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ printf("%s};\n", j==0 ? "" : "\n");
+
+ printf("/* aKWNext[] forms the hash collision chain. If aKWHash[i]==0\n");
+ printf("** then the i-th keyword has no more hash collisions. Otherwise,\n");
+ printf("** the next keyword with the same hash is aKWHash[i]-1. */\n");
+ printf("static const unsigned char aKWNext[%d] = {0,\n", nKeyword+1);
+ for(i=j=0; i<nKeyword; i++){
+ if( j==0 ) printf(" ");
+ printf(" %3d,", aKeywordTable[i].iNext);
+ j++;
+ if( j>12 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ printf("%s};\n", j==0 ? "" : "\n");
+
+ printf("/* aKWLen[i] is the length (in bytes) of the i-th keyword */\n");
+ printf("static const unsigned char aKWLen[%d] = {0,\n", nKeyword+1);
+ for(i=j=0; i<nKeyword; i++){
+ if( j==0 ) printf(" ");
+ printf(" %3d,", aKeywordTable[i].len+aKeywordTable[i].prefix);
+ j++;
+ if( j>12 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ printf("%s};\n", j==0 ? "" : "\n");
+
+ printf("/* aKWOffset[i] is the index into zKWText[] of the start of\n");
+ printf("** the text for the i-th keyword. */\n");
+ printf("static const unsigned short int aKWOffset[%d] = {0,\n", nKeyword+1);
+ for(i=j=0; i<nKeyword; i++){
+ if( j==0 ) printf(" ");
+ printf(" %3d,", aKeywordTable[i].offset);
+ j++;
+ if( j>12 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ printf("%s};\n", j==0 ? "" : "\n");
+
+ printf("/* aKWCode[i] is the parser symbol code for the i-th keyword */\n");
+ printf("static const unsigned char aKWCode[%d] = {0,\n", nKeyword+1);
+ for(i=j=0; i<nKeyword; i++){
+ char *zToken = aKeywordTable[i].zTokenType;
+ if( j==0 ) printf(" ");
+ printf("%s,%*s", zToken, (int)(14-strlen(zToken)), "");
+ j++;
+ if( j>=5 ){
+ printf("\n");
+ j = 0;
+ }
+ }
+ printf("%s};\n", j==0 ? "" : "\n");
+ printf("/* Hash table decoded:\n");
+ for(i=0; i<bestSize; i++){
+ j = aKWHash[i];
+ printf("** %3d:", i);
+ while( j ){
+ printf(" %s", aKeywordTable[j-1].zOrigName);
+ j = aKeywordTable[j-1].iNext;
+ }
+ printf("\n");
+ }
+ printf("*/\n");
+ printf("/* Check to see if z[0..n-1] is a keyword. If it is, write the\n");
+ printf("** parser symbol code for that keyword into *pType. Always\n");
+ printf("** return the integer n (the length of the token). */\n");
+ printf("static int keywordCode(const char *z, int n, int *pType){\n");
+ printf(" int i, j;\n");
+ printf(" const char *zKW;\n");
+ printf(" assert( n>=2 );\n");
+ printf(" i = ((charMap(z[0])*%d) %c", HASH_C0, HASH_CC);
+ printf(" (charMap(z[n-1])*%d) %c", HASH_C1, HASH_CC);
+ printf(" n*%d) %% %d;\n", HASH_C2, bestSize);
+ printf(" for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){\n");
+ printf(" if( aKWLen[i]!=n ) continue;\n");
+ printf(" zKW = &zKWText[aKWOffset[i]];\n");
+ printf("#ifdef SQLITE_ASCII\n");
+ printf(" if( (z[0]&~0x20)!=zKW[0] ) continue;\n");
+ printf(" if( (z[1]&~0x20)!=zKW[1] ) continue;\n");
+ printf(" j = 2;\n");
+ printf(" while( j<n && (z[j]&~0x20)==zKW[j] ){ j++; }\n");
+ printf("#endif\n");
+ printf("#ifdef SQLITE_EBCDIC\n");
+ printf(" if( toupper(z[0])!=zKW[0] ) continue;\n");
+ printf(" if( toupper(z[1])!=zKW[1] ) continue;\n");
+ printf(" j = 2;\n");
+ printf(" while( j<n && toupper(z[j])==zKW[j] ){ j++; }\n");
+ printf("#endif\n");
+ printf(" if( j<n ) continue;\n");
+ for(i=0; i<nKeyword; i++){
+ printf(" testcase( i==%d ); /* %s */\n",
+ i+1, aKeywordTable[i].zOrigName);
+ }
+ printf(" *pType = aKWCode[i];\n");
+ printf(" break;\n");
+ printf(" }\n");
+ printf(" return n;\n");
+ printf("}\n");
+ printf("int sqlite3KeywordCode(const unsigned char *z, int n){\n");
+ printf(" int id = TK_ID;\n");
+ printf(" if( n>=2 ) keywordCode((char*)z, n, &id);\n");
+ printf(" return id;\n");
+ printf("}\n");
+ printf("#define SQLITE_N_KEYWORD %d\n", nKeyword);
+ printf("int sqlite3_keyword_name(int i,const char **pzName,int *pnName){\n");
+ printf(" if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR;\n");
+ printf(" i++;\n");
+ printf(" *pzName = zKWText + aKWOffset[i];\n");
+ printf(" *pnName = aKWLen[i];\n");
+ printf(" return SQLITE_OK;\n");
+ printf("}\n");
+ printf("int sqlite3_keyword_count(void){ return SQLITE_N_KEYWORD; }\n");
+ printf("int sqlite3_keyword_check(const char *zName, int nName){\n");
+ printf(" return TK_ID!=sqlite3KeywordCode((const u8*)zName, nName);\n");
+ printf("}\n");
+
+ return 0;
+}
diff --git a/tool/mkmsvcmin.tcl b/tool/mkmsvcmin.tcl
new file mode 100644
index 0000000..6cb145d
--- /dev/null
+++ b/tool/mkmsvcmin.tcl
@@ -0,0 +1,113 @@
+#!/usr/bin/tcl
+#
+# This script reads the regular MSVC makefile (../Makefile.msc) and outputs
+# a revised version of that Makefile that is "minimal" in the sense that
+# it uses the sqlite3.c amalgamation as input and does not require tclsh.
+# The resulting "../Makefile.min.msc" is suitable for use in the amalgamation
+# tarballs.
+#
+if {$argc==0} {
+ set basedir [file dir [file dir [file normalize $argv0]]]
+ set fromFileName [file join $basedir Makefile.msc]
+ set toFileName [file join $basedir autoconf Makefile.msc]
+} else {
+ set fromFileName [lindex $argv 0]
+ if {![file exists $fromFileName]} {
+ error "input file \"$fromFileName\" does not exist"
+ }
+ set toFileName [lindex $argv 1]
+ if {[file exists $toFileName]} {
+ error "output file \"$toFileName\" already exists"
+ }
+}
+
+proc readFile { fileName } {
+ set file_id [open $fileName RDONLY]
+ fconfigure $file_id -encoding binary -translation binary
+ set result [read $file_id]
+ close $file_id
+ return $result
+}
+
+proc writeFile { fileName data } {
+ set file_id [open $fileName {WRONLY CREAT TRUNC}]
+ fconfigure $file_id -encoding binary -translation binary
+ puts -nonewline $file_id $data
+ close $file_id
+ return ""
+}
+
+proc escapeSubSpec { data } {
+ regsub -all -- {&} $data {\\\&} data
+ regsub -all -- {\\(\d+)} $data {\\\\\1} data
+ return $data
+}
+
+proc substVars { data } {
+ return [uplevel 1 [list subst -nocommands -nobackslashes $data]]
+}
+
+#
+# NOTE: This block is used to replace the section marked <<block1>> in
+# the Makefile, if it exists.
+#
+set blocks(1) [string trimleft [string map [list \\\\ \\] {
+_HASHCHAR=^#
+!IF ![echo !IFNDEF VERSION > rcver.vc] && \\
+ ![for /F "delims=" %V in ('type "$(SQLITE3H)" ^| "%SystemRoot%\System32\find.exe" "$(_HASHCHAR)define SQLITE_VERSION "') do (echo VERSION = ^^%V >> rcver.vc)] && \\
+ ![echo !ENDIF >> rcver.vc]
+!INCLUDE rcver.vc
+!ENDIF
+
+RESOURCE_VERSION = $(VERSION:^#=)
+RESOURCE_VERSION = $(RESOURCE_VERSION:define=)
+RESOURCE_VERSION = $(RESOURCE_VERSION:SQLITE_VERSION=)
+RESOURCE_VERSION = $(RESOURCE_VERSION:"=)
+RESOURCE_VERSION = $(RESOURCE_VERSION:.=,)
+
+$(LIBRESOBJS): $(TOP)\sqlite3.rc rcver.vc $(SQLITE3H)
+ echo #ifndef SQLITE_RESOURCE_VERSION > sqlite3rc.h
+ echo #define SQLITE_RESOURCE_VERSION $(RESOURCE_VERSION) >> sqlite3rc.h
+ echo #endif >> sqlite3rc.h
+ $(LTRCOMPILE) -fo $(LIBRESOBJS) -DRC_VERONLY $(TOP)\sqlite3.rc
+}]]
+
+#
+# NOTE: This block is used to replace the section marked <<block2>> in
+# the Makefile, if it exists.
+#
+set blocks(2) [string trimleft [string map [list \\\\ \\] {
+Replace.exe:
+ $(CSC) /target:exe $(TOP)\Replace.cs
+
+sqlite3.def: Replace.exe $(LIBOBJ)
+ echo EXPORTS > sqlite3.def
+ dumpbin /all $(LIBOBJ) \\
+ | .\Replace.exe "^\s+/EXPORT:_?(sqlite3(?:session|changeset|changegroup|rebaser|rbu)?_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \\
+ | sort >> sqlite3.def
+}]]
+
+set data "#### DO NOT EDIT ####\n"
+append data "# This makefile is automatically "
+append data "generated from the [file tail $fromFileName] at\n"
+append data "# the root of the canonical SQLite source tree (not the\n"
+append data "# amalgamation tarball) using the tool/[file tail $argv0]\n"
+append data "# script.\n#\n\n"
+append data [readFile $fromFileName]
+
+regsub -all -- {# <<mark>>\n.*?# <</mark>>\n} \
+ $data "" data
+
+foreach i [lsort -integer [array names blocks]] {
+ regsub -all -- [substVars \
+ {# <<block${i}>>\n.*?# <</block${i}>>\n}] \
+ $data [escapeSubSpec $blocks($i)] data
+}
+
+set data [string map [list " -I\$(TOP)\\src" ""] $data]
+set data [string map [list " libsqlite3.lib" ""] $data]
+set data [string map [list " \$(ALL_TCL_TARGETS)" ""] $data]
+set data [string map [list "\$(TOP)\\src\\" "\$(TOP)\\"] $data]
+
+writeFile $toFileName $data
+puts "generated $toFileName from $fromFileName"
diff --git a/tool/mkopcodec.tcl b/tool/mkopcodec.tcl
new file mode 100644
index 0000000..5eac05f
--- /dev/null
+++ b/tool/mkopcodec.tcl
@@ -0,0 +1,51 @@
+#!/usr/bin/tclsh
+#
+# This TCL script scans the opcodes.h file (which is itself generated by
+# another TCL script) and uses the information gleaned to create the
+# opcodes.c source file.
+#
+# Opcodes.c contains strings which are the symbolic names for the various
+# opcodes used by the VDBE. These strings are used when disassembling a
+# VDBE program during tracing or as a result of the EXPLAIN keyword.
+#
+puts "/* Automatically generated. Do not edit */"
+puts "/* See the tool/mkopcodec.tcl script for details. */"
+puts "#if !defined(SQLITE_OMIT_EXPLAIN) \\"
+puts " || defined(VDBE_PROFILE) \\"
+puts " || defined(SQLITE_DEBUG)"
+puts "#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) || defined(SQLITE_DEBUG)"
+puts "# define OpHelp(X) \"\\0\" X"
+puts "#else"
+puts "# define OpHelp(X)"
+puts "#endif"
+puts "const char *sqlite3OpcodeName(int i)\173"
+puts " static const char *const azName\[\] = \173"
+set mx 0
+
+set in [open [lindex $argv 0]]
+fconfigure $in -translation binary
+while {![eof $in]} {
+ set line [gets $in]
+ if {[regexp {^#define OP_} $line]} {
+ set name [lindex $line 1]
+ regsub {^OP_} $name {} name
+ set i [lindex $line 2]
+ set label($i) $name
+ if {$mx<$i} {set mx $i}
+ if {[regexp {synopsis: (.*) \*/} $line all x]} {
+ set synopsis($i) [string trim $x]
+ } else {
+ set synopsis($i) {}
+ }
+ }
+}
+close $in
+
+for {set i 0} {$i<=$mx} {incr i} {
+ puts [format " /* %3d */ %-18s OpHelp(\"%s\")," \
+ $i \"$label($i)\" $synopsis($i)]
+}
+puts " \175;"
+puts " return azName\[i\];"
+puts "\175"
+puts "#endif"
diff --git a/tool/mkopcodeh.tcl b/tool/mkopcodeh.tcl
new file mode 100644
index 0000000..6fb3b75
--- /dev/null
+++ b/tool/mkopcodeh.tcl
@@ -0,0 +1,325 @@
+#!/usr/bin/tclsh
+#
+# Generate the file opcodes.h.
+#
+# This TCL script scans a concatenation of the parse.h output file from the
+# parser and the vdbe.c source file in order to generate the opcodes numbers
+# for all opcodes.
+#
+# The lines of the vdbe.c that we are interested in are of the form:
+#
+# case OP_aaaa: /* same as TK_bbbbb */
+#
+# The TK_ comment is optional. If it is present, then the value assigned to
+# the OP_ is the same as the TK_ value. If missing, the OP_ value is assigned
+# a small integer that is different from every other OP_ value.
+#
+# We go to the trouble of making some OP_ values the same as TK_ values
+# as an optimization. During parsing, things like expression operators
+# are coded with TK_ values such as TK_ADD, TK_DIVIDE, and so forth. Later
+# during code generation, we need to generate corresponding opcodes like
+# OP_Add and OP_Divide. By making TK_ADD==OP_Add and TK_DIVIDE==OP_Divide,
+# code to translate from one to the other is avoided. This makes the
+# code generator smaller and faster.
+#
+# This script also scans for lines of the form:
+#
+# case OP_aaaa: /* jump, in1, in2, in3, out2, out3 */
+#
+# When such comments are found on an opcode, it means that certain
+# properties apply to that opcode. Set corresponding flags using the
+# OPFLG_INITIALIZER macro.
+#
+
+set in stdin
+set currentOp {}
+set prevName {}
+set nOp 0
+set nGroup 0
+while {![eof $in]} {
+ set line [gets $in]
+
+ # Remember the TK_ values from the parse.h file.
+ # NB: The "TK_" prefix stands for "ToKen", not the graphical Tk toolkit
+ # commonly associated with TCL.
+ #
+ if {[regexp {^#define TK_} $line]} {
+ set tk([lindex $line 1]) [lindex $line 2]
+ continue
+ }
+
+ # Find "/* Opcode: " lines in the vdbe.c file. Each one introduces
+ # a new opcode. Remember which parameters are used.
+ #
+ if {[regexp {^.. Opcode: } $line]} {
+ set currentOp OP_[lindex $line 2]
+ set m 0
+ foreach term $line {
+ switch $term {
+ P1 {incr m 1}
+ P2 {incr m 2}
+ P3 {incr m 4}
+ P4 {incr m 8}
+ P5 {incr m 16}
+ }
+ }
+ set paramused($currentOp) $m
+ }
+
+ # Find "** Synopsis: " lines that follow Opcode:
+ #
+ if {[regexp {^.. Synopsis: (.*)} $line all x] && $currentOp!=""} {
+ set synopsis($currentOp) [string trim $x]
+ }
+
+ # Scan for "case OP_aaaa:" lines in the vdbe.c file
+ #
+ if {[regexp {^case OP_} $line]} {
+ set line [split $line]
+ set name [string trim [lindex $line 1] :]
+ if {$name=="OP_Abortable"} continue; # put OP_Abortable last
+ set op($name) -1
+ set group($name) 0
+ set jump($name) 0
+ set in1($name) 0
+ set in2($name) 0
+ set in3($name) 0
+ set out2($name) 0
+ set out3($name) 0
+ set ncycle($name) 0
+ for {set i 3} {$i<[llength $line]-1} {incr i} {
+ switch [string trim [lindex $line $i] ,] {
+ same {
+ incr i
+ if {[lindex $line $i]=="as"} {
+ incr i
+ set sym [string trim [lindex $line $i] ,]
+ set val $tk($sym)
+ set op($name) $val
+ set used($val) 1
+ set sameas($val) $sym
+ set def($val) $name
+ }
+ }
+ group {set group($name) 1}
+ jump {set jump($name) 1}
+ in1 {set in1($name) 1}
+ in2 {set in2($name) 1}
+ in3 {set in3($name) 1}
+ out2 {set out2($name) 1}
+ out3 {set out3($name) 1}
+ ncycle {set ncycle($name) 1}
+ }
+ }
+ if {$group($name)} {
+ set newGroup 0
+ if {[info exists groups($nGroup)]} {
+ if {$prevName=="" || !$group($prevName)} {
+ set newGroup 1
+ }
+ }
+ lappend groups($nGroup) $name
+ if {$newGroup} {incr nGroup}
+ } else {
+ if {$prevName!="" && $group($prevName)} {
+ incr nGroup
+ }
+ }
+ set order($nOp) $name
+ set prevName $name
+ incr nOp
+ }
+}
+
+# Assign numbers to all opcodes and output the result.
+#
+puts "/* Automatically generated. Do not edit */"
+puts "/* See the tool/mkopcodeh.tcl script for details */"
+foreach name {OP_Noop OP_Explain OP_Abortable} {
+ set jump($name) 0
+ set in1($name) 0
+ set in2($name) 0
+ set in3($name) 0
+ set out2($name) 0
+ set out3($name) 0
+ set ncycle($name) 0
+ set op($name) -1
+ set order($nOp) $name
+ incr nOp
+}
+
+# The following are the opcodes that receive special processing in the
+# resolveP2Values() routine. Update this list whenever new cases are
+# added to the pOp->opcode switch within resolveP2Values().
+#
+set rp2v_ops {
+ OP_Transaction
+ OP_AutoCommit
+ OP_Savepoint
+ OP_Checkpoint
+ OP_Vacuum
+ OP_JournalMode
+ OP_VUpdate
+ OP_VFilter
+ OP_Init
+}
+
+# Assign the smallest values to opcodes that are processed by resolveP2Values()
+# to make code generation for the switch() statement smaller and faster.
+#
+set cnt -1
+for {set i 0} {$i<$nOp} {incr i} {
+ set name $order($i)
+ if {[lsearch $rp2v_ops $name]>=0} {
+ incr cnt
+ while {[info exists used($cnt)]} {incr cnt}
+ set op($name) $cnt
+ set used($cnt) 1
+ set def($cnt) $name
+ }
+}
+set mxCase1 $cnt
+
+# Assign the next group of values to JUMP opcodes
+#
+for {set i 0} {$i<$nOp} {incr i} {
+ set name $order($i)
+ if {$op($name)>=0} continue
+ if {!$jump($name)} continue
+ incr cnt
+ while {[info exists used($cnt)]} {incr cnt}
+ set op($name) $cnt
+ set used($cnt) 1
+ set def($cnt) $name
+}
+
+# Find the numeric value for the largest JUMP opcode
+#
+set mxJump -1
+for {set i 0} {$i<$nOp} {incr i} {
+ set name $order($i)
+ if {$jump($name) && $op($name)>$mxJump} {set mxJump $op($name)}
+}
+
+
+# Generate the numeric values for all remaining opcodes, while
+# preserving any groupings of opcodes (i.e. those that must be
+# together).
+#
+for {set g 0} {$g<$nGroup} {incr g} {
+ set gLen [llength $groups($g)]
+ set ok 0; set start -1
+ set seek $cnt
+ while {!$ok} {
+ incr seek
+ while {[info exists used($seek)]} {incr seek}
+ set ok 1; set start $seek
+ for {set j 0} {$j<$gLen} {incr j} {
+ incr seek
+ if {[info exists used($seek)]} {
+ set ok 0; break
+ }
+ }
+ }
+ if {$ok} {
+ set next $start
+ for {set j 0} {$j<$gLen} {incr j} {
+ set name [lindex $groups($g) $j]
+ if {$op($name)>=0} continue
+ set op($name) $next
+ set used($next) 1
+ set def($next) $name
+ incr next
+ }
+ } else {
+ error "cannot find opcodes for group: $groups($g)"
+ }
+}
+
+for {set i 0} {$i<$nOp} {incr i} {
+ set name $order($i)
+ if {$op($name)<0} {
+ incr cnt
+ while {[info exists used($cnt)]} {incr cnt}
+ set op($name) $cnt
+ set used($cnt) 1
+ set def($cnt) $name
+ }
+}
+
+set max [lindex [lsort -decr -integer [array names used]] 0]
+for {set i 0} {$i<=$max} {incr i} {
+ if {![info exists used($i)]} {
+ set def($i) "OP_NotUsed_$i"
+ }
+ if {$i>$max} {set max $i}
+ set name $def($i)
+ puts -nonewline [format {#define %-16s %3d} $name $i]
+ set com {}
+ if {[info exists jump($name)] && $jump($name)} {
+ lappend com "jump"
+ }
+ if {[info exists sameas($i)]} {
+ lappend com "same as $sameas($i)"
+ }
+ if {[info exists synopsis($name)]} {
+ lappend com "synopsis: $synopsis($name)"
+ }
+ if {[llength $com]} {
+ puts -nonewline [format " /* %-42s */" [join $com {, }]]
+ }
+ puts ""
+}
+
+if {$max>255} {
+ error "More than 255 opcodes - VdbeOp.opcode is of type u8!"
+}
+
+# Generate the bitvectors:
+#
+set bv(0) 0
+for {set i 0} {$i<=$max} {incr i} {
+ set x 0
+ set name $def($i)
+ if {[string match OP_NotUsed* $name]==0} {
+ if {$jump($name)} {incr x 1}
+ if {$in1($name)} {incr x 2}
+ if {$in2($name)} {incr x 4}
+ if {$in3($name)} {incr x 8}
+ if {$out2($name)} {incr x 16}
+ if {$out3($name)} {incr x 32}
+ if {$ncycle($name)} {incr x 64}
+ }
+ set bv($i) $x
+}
+puts ""
+puts "/* Properties such as \"out2\" or \"jump\" that are specified in"
+puts "** comments following the \"case\" for each opcode in the vdbe.c"
+puts "** are encoded into bitvectors as follows:"
+puts "*/"
+puts "#define OPFLG_JUMP 0x01 /* jump: P2 holds jmp target */"
+puts "#define OPFLG_IN1 0x02 /* in1: P1 is an input */"
+puts "#define OPFLG_IN2 0x04 /* in2: P2 is an input */"
+puts "#define OPFLG_IN3 0x08 /* in3: P3 is an input */"
+puts "#define OPFLG_OUT2 0x10 /* out2: P2 is an output */"
+puts "#define OPFLG_OUT3 0x20 /* out3: P3 is an output */"
+puts "#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */"
+puts "#define OPFLG_INITIALIZER \173\\"
+for {set i 0} {$i<=$max} {incr i} {
+ if {$i%8==0} {
+ puts -nonewline [format "/* %3d */" $i]
+ }
+ puts -nonewline [format " 0x%02x," $bv($i)]
+ if {$i%8==7} {
+ puts "\\"
+ }
+}
+puts "\175"
+puts ""
+puts "/* The resolve3P2Values() routine is able to run faster if it knows"
+puts "** the value of the largest JUMP opcode. The smaller the maximum"
+puts "** JUMP opcode the better, so the mkopcodeh.tcl script that"
+puts "** generated this include file strives to group all JUMP opcodes"
+puts "** together near the beginning of the list."
+puts "*/"
+puts "#define SQLITE_MX_JUMP_OPCODE $mxJump /* Maximum JUMP opcode */"
diff --git a/tool/mkopts.tcl b/tool/mkopts.tcl
new file mode 100644
index 0000000..88f645b
--- /dev/null
+++ b/tool/mkopts.tcl
@@ -0,0 +1,51 @@
+#!/usr/bin/tclsh
+#
+# This script is used to generate the array of strings and the enum
+# that appear at the beginning of the C code implementation of a
+# a TCL command and that define the available subcommands for that
+# TCL command.
+
+set prefix {}
+while {![eof stdin]} {
+ set line [gets stdin]
+ if {$line==""} continue
+ regsub -all "\[ \t\n,\]+" [string trim $line] { } line
+ foreach token [split $line { }] {
+ if {![regexp {(([a-zA-Z]+)_)?([_a-zA-Z0-9]+)} $token all px p2 name]} continue
+ lappend namelist [string tolower $name]
+ if {$px!=""} {set prefix $p2}
+ }
+}
+
+puts " static const char *${prefix}_strs\[\] = \173"
+set col 0
+proc put_item x {
+ global col
+ if {$col==0} {puts -nonewline " "}
+ if {$col<2} {
+ puts -nonewline [format " %-25s" $x]
+ incr col
+ } else {
+ puts $x
+ set col 0
+ }
+}
+proc finalize {} {
+ global col
+ if {$col>0} {puts {}}
+ set col 0
+}
+
+foreach name [lsort $namelist] {
+ put_item \"$name\",
+}
+put_item 0
+finalize
+puts " \175;"
+puts " enum ${prefix}_enum \173"
+foreach name [lsort $namelist] {
+ regsub -all {@} $name {} name
+ put_item ${prefix}_[string toupper $name],
+}
+finalize
+puts " \175;"
diff --git a/tool/mkpragmatab.tcl b/tool/mkpragmatab.tcl
new file mode 100644
index 0000000..2b5b6fd
--- /dev/null
+++ b/tool/mkpragmatab.tcl
@@ -0,0 +1,637 @@
+#!/usr/bin/tclsh
+#
+# Run this script to generate the pragma name lookup table C code.
+#
+# To add new pragmas, first add the name and other relevant attributes
+# of the pragma to the "pragma_def" object below. Then run this script
+# to generate the ../src/pragma.h header file that contains macros and
+# the lookup table needed for pragma name lookup in the pragma.c module.
+# Then add the extra "case PragTyp_XXXXX:" and subsequent code for the
+# new pragma in ../src/pragma.c.
+#
+# The results are normally written into the ../src/pragma.h file. However,
+# if an alternative output file name is provided as an argument, then
+# results are written into the alternative. For example:
+#
+# tclsh tool/mkpragmatab.tcl ;# <--- Results to src/pragma.h
+#
+# tclsh tool/mkpragmatab.tcl /dev/tty ;# <-- results to terminal
+#
+
+# Flag meanings:
+set flagMeaning(NeedSchema) {Force schema load before running}
+set flagMeaning(ReadOnly) {Read-only HEADER_VALUE}
+set flagMeaning(Result0) {Acts as query when no argument}
+set flagMeaning(Result1) {Acts as query when has one argument}
+set flagMeaning(SchemaReq) {Schema required - "main" is default}
+set flagMeaning(SchemaOpt) {Schema restricts name search if present}
+set flagMeaning(NoColumns) {OP_ResultRow called with zero columns}
+set flagMeaning(NoColumns1) {zero columns if RHS argument is present}
+
+set pragma_def {
+ NAME: full_column_names
+ TYPE: FLAG
+ ARG: SQLITE_FullColNames
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: short_column_names
+ TYPE: FLAG
+ ARG: SQLITE_ShortColNames
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: count_changes
+ TYPE: FLAG
+ ARG: SQLITE_CountRows
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: empty_result_callbacks
+ TYPE: FLAG
+ ARG: SQLITE_NullCallback
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: fullfsync
+ TYPE: FLAG
+ ARG: SQLITE_FullFSync
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: checkpoint_fullfsync
+ TYPE: FLAG
+ ARG: SQLITE_CkptFullFSync
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: cache_spill
+ FLAG: Result0 SchemaReq NoColumns1
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: reverse_unordered_selects
+ TYPE: FLAG
+ ARG: SQLITE_ReverseOrder
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: query_only
+ TYPE: FLAG
+ ARG: SQLITE_QueryOnly
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: automatic_index
+ TYPE: FLAG
+ ARG: SQLITE_AutoIndex
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_AUTOMATIC_INDEX)
+
+ NAME: sql_trace
+ TYPE: FLAG
+ ARG: SQLITE_SqlTrace
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: vdbe_listing
+ TYPE: FLAG
+ ARG: SQLITE_VdbeListing
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: vdbe_trace
+ TYPE: FLAG
+ ARG: SQLITE_VdbeTrace
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: vdbe_addoptrace
+ TYPE: FLAG
+ ARG: SQLITE_VdbeAddopTrace
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: vdbe_debug
+ TYPE: FLAG
+ ARG: SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: vdbe_eqp
+ TYPE: FLAG
+ ARG: SQLITE_VdbeEQP
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: ignore_check_constraints
+ TYPE: FLAG
+ ARG: SQLITE_IgnoreChecks
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_CHECK)
+
+ NAME: writable_schema
+ TYPE: FLAG
+ ARG: SQLITE_WriteSchema|SQLITE_NoSchemaError
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: read_uncommitted
+ TYPE: FLAG
+ ARG: SQLITE_ReadUncommit
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: recursive_triggers
+ TYPE: FLAG
+ ARG: SQLITE_RecTriggers
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: trusted_schema
+ TYPE: FLAG
+ ARG: SQLITE_TrustedSchema
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+
+ NAME: foreign_keys
+ TYPE: FLAG
+ ARG: SQLITE_ForeignKeys
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
+
+ NAME: defer_foreign_keys
+ TYPE: FLAG
+ ARG: SQLITE_DeferFKs
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
+
+ NAME: cell_size_check
+ TYPE: FLAG
+ ARG: SQLITE_CellSizeCk
+
+ NAME: default_cache_size
+ FLAG: NeedSchema Result0 SchemaReq NoColumns1
+ COLS: cache_size
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
+
+ NAME: page_size
+ FLAG: Result0 SchemaReq NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: secure_delete
+ FLAG: Result0
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: page_count
+ FLAG: NeedSchema Result0 SchemaReq
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: max_page_count
+ TYPE: PAGE_COUNT
+ FLAG: NeedSchema Result0 SchemaReq
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: locking_mode
+ FLAG: Result0 SchemaReq
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: journal_mode
+ FLAG: NeedSchema Result0 SchemaReq
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: journal_size_limit
+ FLAG: Result0 SchemaReq
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: cache_size
+ FLAG: NeedSchema Result0 SchemaReq NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: mmap_size
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: auto_vacuum
+ FLAG: NeedSchema Result0 SchemaReq NoColumns1
+ IF: !defined(SQLITE_OMIT_AUTOVACUUM)
+
+ NAME: incremental_vacuum
+ FLAG: NeedSchema NoColumns
+ IF: !defined(SQLITE_OMIT_AUTOVACUUM)
+
+ NAME: temp_store
+ FLAG: Result0 NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: temp_store_directory
+ FLAG: NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: data_store_directory
+ FLAG: NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN
+
+ NAME: lock_proxy_file
+ FLAG: NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE
+
+ NAME: synchronous
+ FLAG: NeedSchema Result0 SchemaReq NoColumns1
+ IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+
+ NAME: table_info
+ FLAG: NeedSchema Result1 SchemaOpt
+ ARG: 0
+ COLS: cid name type notnull dflt_value pk
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: table_xinfo
+ TYPE: TABLE_INFO
+ FLAG: NeedSchema Result1 SchemaOpt
+ ARG: 1
+ COLS: cid name type notnull dflt_value pk hidden
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: table_list
+ TYPE: TABLE_LIST
+ FLAG: NeedSchema Result1
+ COLS: schema name type ncol wr strict
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: stats
+ FLAG: NeedSchema Result0 SchemaReq
+ COLS: tbl idx wdth hght flgs
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG)
+
+ NAME: index_info
+ TYPE: INDEX_INFO
+ ARG: 0
+ FLAG: NeedSchema Result1 SchemaOpt
+ COLS: seqno cid name
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: index_xinfo
+ TYPE: INDEX_INFO
+ ARG: 1
+ FLAG: NeedSchema Result1 SchemaOpt
+ COLS: seqno cid name desc coll key
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: index_list
+ FLAG: NeedSchema Result1 SchemaOpt
+ COLS: seq name unique origin partial
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: database_list
+ FLAG: Result0
+ COLS: seq name file
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: function_list
+ FLAG: Result0
+ COLS: name builtin type enc narg flags
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+
+ NAME: module_list
+ FLAG: Result0
+ COLS: name
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+ IF: !defined(SQLITE_OMIT_VIRTUALTABLE)
+ IF: !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+
+ NAME: pragma_list
+ FLAG: Result0
+ COLS: name
+ IF: !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+
+ NAME: collation_list
+ FLAG: Result0
+ COLS: seq name
+ IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+
+ NAME: foreign_key_list
+ FLAG: NeedSchema Result1 SchemaOpt
+ COLS: id seq table from to on_update on_delete match
+ IF: !defined(SQLITE_OMIT_FOREIGN_KEY)
+
+ NAME: foreign_key_check
+ FLAG: NeedSchema Result0 Result1 SchemaOpt
+ COLS: table rowid parent fkid
+ IF: !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
+
+ NAME: parser_trace
+ TYPE: FLAG
+ ARG: SQLITE_ParserTrace
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ IF: defined(SQLITE_DEBUG)
+
+ NAME: case_sensitive_like
+ FLAG: NoColumns
+ IF: !defined(SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA)
+
+ NAME: integrity_check
+ FLAG: NeedSchema Result0 Result1 SchemaOpt
+ IF: !defined(SQLITE_OMIT_INTEGRITY_CHECK)
+
+ NAME: quick_check
+ TYPE: INTEGRITY_CHECK
+ FLAG: NeedSchema Result0 Result1 SchemaOpt
+ IF: !defined(SQLITE_OMIT_INTEGRITY_CHECK)
+
+ NAME: encoding
+ FLAG: Result0 NoColumns1
+ IF: !defined(SQLITE_OMIT_UTF16)
+
+ NAME: schema_version
+ TYPE: HEADER_VALUE
+ ARG: BTREE_SCHEMA_VERSION
+ FLAG: NoColumns1 Result0
+ IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+
+ NAME: user_version
+ TYPE: HEADER_VALUE
+ ARG: BTREE_USER_VERSION
+ FLAG: NoColumns1 Result0
+ IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+
+ NAME: data_version
+ TYPE: HEADER_VALUE
+ ARG: BTREE_DATA_VERSION
+ FLAG: ReadOnly Result0
+ IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+
+ NAME: freelist_count
+ TYPE: HEADER_VALUE
+ ARG: BTREE_FREE_PAGE_COUNT
+ FLAG: ReadOnly Result0
+ IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+
+ NAME: application_id
+ TYPE: HEADER_VALUE
+ ARG: BTREE_APPLICATION_ID
+ FLAG: NoColumns1 Result0
+ IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
+
+ NAME: compile_options
+ FLAG: Result0
+ IF: !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
+
+ NAME: wal_checkpoint
+ FLAG: NeedSchema
+ COLS: busy log checkpointed
+ IF: !defined(SQLITE_OMIT_WAL)
+
+ NAME: wal_autocheckpoint
+ IF: !defined(SQLITE_OMIT_WAL)
+
+ NAME: shrink_memory
+ FLAG: NoColumns
+
+ NAME: busy_timeout
+ FLAG: Result0
+ COLS: timeout
+
+ NAME: lock_status
+ FLAG: Result0
+ COLS: database status
+ IF: defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+
+ NAME: activate_extensions
+ IF: defined(SQLITE_ENABLE_CEROD)
+
+ NAME: soft_heap_limit
+ FLAG: Result0
+
+ NAME: hard_heap_limit
+ FLAG: Result0
+
+ NAME: threads
+ FLAG: Result0
+
+ NAME: analysis_limit
+ FLAG: Result0
+
+ NAME: optimize
+ FLAG: Result1 NeedSchema
+
+ NAME: legacy_alter_table
+ TYPE: FLAG
+ ARG: SQLITE_LegacyAlter
+ IF: !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+}
+
+# Open the output file
+#
+if {$argc>0} {
+ set destfile [lindex $argv 0]
+} else {
+ set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h"
+ puts "Overwriting $destfile with new pragma table..."
+}
+set fd [open $destfile wb]
+puts $fd {/* DO NOT EDIT!
+** This file is automatically generated by the script at
+** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit
+** that script and rerun it.
+*/}
+
+# Parse the PRAGMA table above.
+#
+set name {}
+set type {}
+set if {}
+set flags {}
+set cols {}
+set cols_list {}
+set arg 0
+proc record_one {} {
+ global name type if arg allbyname typebyif flags cols all_cols
+ global cols_list colUsedBy
+ if {$name==""} return
+ if {$cols!=""} {
+ if {![info exists all_cols($cols)]} {
+ set all_cols($cols) 1
+ lappend cols_list $cols
+ }
+ set cx $cols
+ lappend colUsedBy($cols) $name
+ } else {
+ set cx 0
+ }
+ set allbyname($name) [list $type $arg $if $flags $cols]
+ set name {}
+ set type {}
+ set if {}
+ set flags {}
+ set cols {}
+ set arg 0
+}
+foreach line [split $pragma_def \n] {
+ set line [string trim $line]
+ if {$line==""} continue
+ foreach {id val} [split $line :] break
+ set val [string trim $val]
+ if {$id=="NAME"} {
+ record_one
+ set name $val
+ set type [string toupper $val]
+ } elseif {$id=="TYPE"} {
+ set type $val
+ if {$type=="FLAG"} {
+ lappend flags Result0 NoColumns1
+ }
+ } elseif {$id=="ARG"} {
+ set arg $val
+ } elseif {$id=="COLS"} {
+ set cols $val
+ } elseif {$id=="IF"} {
+ lappend if $val
+ } elseif {$id=="FLAG"} {
+ foreach term [split $val] {
+ lappend flags $term
+ set allflags($term) 1
+ }
+ } else {
+ error "bad pragma_def line: $line"
+ }
+}
+record_one
+set allnames [lsort [array names allbyname]]
+
+# Generate #defines for all pragma type names. Group the pragmas that are
+# omit in default builds (ex: defined(SQLITE_DEBUG))
+# at the end.
+#
+puts $fd "\n/* The various pragma types */"
+set pnum 0
+foreach name $allnames {
+ set type [lindex $allbyname($name) 0]
+ if {[info exists seentype($type)]} continue
+ set if [lindex $allbyname($name) 2]
+ if {[regexp SQLITE_DEBUG $if] || [regexp SQLITE_HAS_CODEC $if]} continue
+ set seentype($type) 1
+ puts $fd [format {#define %-35s %4d} PragTyp_$type $pnum]
+ incr pnum
+}
+foreach name $allnames {
+ set type [lindex $allbyname($name) 0]
+ if {[info exists seentype($type)]} continue
+ set if [lindex $allbyname($name) 2]
+ if {[regexp SQLITE_DEBUG $if]} continue
+ set seentype($type) 1
+ puts $fd [format {#define %-35s %4d} PragTyp_$type $pnum]
+ incr pnum
+}
+foreach name $allnames {
+ set type [lindex $allbyname($name) 0]
+ if {[info exists seentype($type)]} continue
+ set seentype($type) 1
+ puts $fd [format {#define %-35s %4d} PragTyp_$type $pnum]
+ incr pnum
+}
+
+# Generate #defines for flags
+#
+puts $fd "\n/* Property flags associated with various pragma. */"
+set fv 1
+foreach f [lsort [array names allflags]] {
+ puts $fd [format {#define PragFlg_%-10s 0x%02x /* %s */} \
+ $f $fv $flagMeaning($f)]
+ set fv [expr {$fv*2}]
+}
+
+# Sort the column lists so that longer column lists occur first
+#
+proc colscmp {a b} {
+ return [expr {[llength $b] - [llength $a]}]
+}
+set cols_list [lsort -command colscmp $cols_list]
+
+# Generate the array of column names used by pragmas that act like
+# queries.
+#
+puts $fd "\n/* Names of columns for pragmas that return multi-column result"
+puts $fd "** or that return single-column results where the name of the"
+puts $fd "** result column is different from the name of the pragma\n*/"
+puts $fd "static const char *const pragCName\[\] = {"
+set offset 0
+set allcollist {}
+foreach cols $cols_list {
+ set n [llength $cols]
+ set limit [expr {[llength $allcollist] - $n}]
+ for {set i 0} {$i<$limit} {incr i} {
+ set sublist [lrange $allcollist $i [expr {$i+$n-1}]]
+ if {$sublist==$cols} {
+ puts $fd [format "%27s/* $colUsedBy($cols) reuses $i */" ""]
+ set cols_offset($cols) $i
+ break
+ }
+ }
+ if {$i<$limit} continue
+ set cols_offset($cols) $offset
+ set ub " /* Used by: $colUsedBy($cols) */"
+ foreach c $cols {
+ lappend allcollist $c
+ puts $fd [format " /* %3d */ %-14s%s" $offset \"$c\", $ub]
+ set ub ""
+ incr offset
+ }
+}
+puts $fd "\175;"
+
+# Generate the lookup table
+#
+puts $fd "\n/* Definitions of all built-in pragmas */"
+puts $fd "typedef struct PragmaName \173"
+puts $fd " const char *const zName; /* Name of pragma */"
+puts $fd " u8 ePragTyp; /* PragTyp_XXX value */"
+puts $fd " u8 mPragFlg; /* Zero or more PragFlg_XXX values */"
+puts $fd { u8 iPragCName; /* Start of column names in pragCName[] */}
+puts $fd " u8 nPragCName; \
+/* Num of col names. 0 means use pragma name */"
+puts $fd " u64 iArg; /* Extra argument */"
+puts $fd "\175 PragmaName;"
+puts $fd "static const PragmaName aPragmaName\[\] = \173"
+
+set current_if {}
+set spacer [format { %26s } {}]
+foreach name $allnames {
+ foreach {type arg if flag cx} $allbyname($name) break
+ if {$cx==0 || $cx==""} {
+ set cy 0
+ set nx 0
+ } else {
+ set cy $cols_offset($cx)
+ set nx [llength $cx]
+ }
+ if {$if!=$current_if} {
+ if {$current_if!=""} {
+ foreach this_if $current_if {
+ puts $fd "#endif"
+ }
+ }
+ set current_if $if
+ if {$current_if!=""} {
+ foreach this_if $current_if {
+ puts $fd "#if $this_if"
+ }
+ }
+ }
+ set typex [format PragTyp_%-23s $type,]
+ if {$flag==""} {
+ set flagx "0"
+ } else {
+ set flagx PragFlg_[join $flag {|PragFlg_}]
+ }
+ puts $fd " \173/* zName: */ \"$name\","
+ puts $fd " /* ePragTyp: */ PragTyp_$type,"
+ puts $fd " /* ePragFlg: */ $flagx,"
+ puts $fd " /* ColNames: */ $cy, $nx,"
+ puts $fd " /* iArg: */ $arg \175,"
+}
+if {$current_if!=""} {
+ foreach this_if $current_if {
+ puts $fd "#endif"
+ }
+}
+puts $fd "\175;"
+
+# count the number of pragmas, for information purposes
+#
+set allcnt 0
+set dfltcnt 0
+foreach name $allnames {
+ incr allcnt
+ set if [lindex $allbyname($name) 2]
+ if {[regexp {^defined} $if] || [regexp {[^!]defined} $if]} continue
+ incr dfltcnt
+}
+puts $fd "/* Number of pragmas: $dfltcnt on by default, $allcnt total. */"
diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl
new file mode 100644
index 0000000..4f16a77
--- /dev/null
+++ b/tool/mkshellc.tcl
@@ -0,0 +1,75 @@
+#!/usr/bin/tclsh
+#
+# Run this script to generate the "shell.c" source file from
+# constituent parts.
+#
+# No arguments are required. This script determines the location
+# of its input files relative to the location of the script itself.
+# This script should be tool/mkshellc.tcl. If the directory holding
+# the script is $DIR, then the component parts are located in $DIR/../src
+# and $DIR/../ext/misc.
+#
+set topdir [file dir [file dir [file normal $argv0]]]
+set out stdout
+fconfigure stdout -translation binary
+puts $out {/* DO NOT EDIT!
+** This file is automatically generated by the script in the canonical
+** SQLite source tree at tool/mkshellc.tcl. That script combines source
+** code from various constituent source files of SQLite into this single
+** "shell.c" file used to implement the SQLite command-line shell.
+**
+** Most of the code found below comes from the "src/shell.c.in" file in
+** the canonical SQLite source tree. That main file contains "INCLUDE"
+** lines that specify other files in the canonical source tree that are
+** inserted to getnerate this complete program source file.
+**
+** The code from multiple files is combined into this single "shell.c"
+** source file to help make the command-line program easier to compile.
+**
+** To modify this program, get a copy of the canonical SQLite source tree,
+** edit the src/shell.c.in" and/or some of the other files that are included
+** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script.
+*/}
+set in [open $topdir/src/shell.c.in]
+fconfigure $in -translation binary
+proc omit_redundant_typedefs {line} {
+ global typedef_seen
+ if {[regexp {^typedef .*\y([a-zA-Z0-9_]+);} $line all typename]} {
+ if {[info exists typedef_seen($typename)]} {
+ return "/* [string map {/* // */ //} $line] */"
+ }
+ set typedef_seen($typename) 1
+ }
+ return $line
+}
+set iLine 0
+while {1} {
+ set lx [omit_redundant_typedefs [gets $in]]
+ if {[eof $in]} break;
+ incr iLine
+ if {[regexp {^INCLUDE } $lx]} {
+ set cfile [lindex $lx 1]
+ puts $out "/************************* Begin $cfile ******************/"
+# puts $out "#line 1 \"$cfile\""
+ set in2 [open $topdir/src/$cfile]
+ fconfigure $in2 -translation binary
+ while {![eof $in2]} {
+ set lx [omit_redundant_typedefs [gets $in2]]
+ if {[regexp {^# *include "sqlite} $lx]} {
+ set lx "/* $lx */"
+ }
+ if {[regexp {^# *include "test_windirent.h"} $lx]} {
+ set lx "/* $lx */"
+ }
+ set lx [string map [list __declspec(dllexport) {}] $lx]
+ puts $out $lx
+ }
+ close $in2
+ puts $out "/************************* End $cfile ********************/"
+# puts $out "#line [expr $iLine+1] \"shell.c.in\""
+ continue
+ }
+ puts $out $lx
+}
+close $in
+close $out
diff --git a/tool/mksourceid.c b/tool/mksourceid.c
new file mode 100644
index 0000000..dd15399
--- /dev/null
+++ b/tool/mksourceid.c
@@ -0,0 +1,835 @@
+/*
+** Run this program with a single argument which is the name of the
+** Fossil "manifest" file for a project, and this program will emit on
+** standard output the "source id" for for the program.
+**
+** (1) The "source id" is the date of check-in together with the
+** SHA3 hash of the manifest file.
+**
+** (2) All individual file hashes in the manifest are verified. If any
+** source file has changed, the SHA3 hash ends with "modified".
+**
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+/* Portable 64-bit unsigned integers */
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+ typedef unsigned __int64 u64;
+#else
+ typedef unsigned long long int u64;
+#endif
+
+
+/*
+** Macros to determine whether the machine is big or little endian,
+** and whether or not that determination is run-time or compile-time.
+**
+** For best performance, an attempt is made to guess at the byte-order
+** using C-preprocessor macros. If that is unsuccessful, or if
+** -DBYTEORDER=0 is set, then byte-order is determined
+** at run-time.
+*/
+#ifndef BYTEORDER
+# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__arm__)
+# define BYTEORDER 1234
+# elif defined(sparc) || defined(__ppc__)
+# define BYTEORDER 4321
+# else
+# define BYTEORDER 0
+# endif
+#endif
+
+
+
+/*
+** State structure for a SHA3 hash in progress
+*/
+typedef struct SHA3Context SHA3Context;
+struct SHA3Context {
+ union {
+ u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */
+ unsigned char x[1600]; /* ... or 1600 bytes */
+ } u;
+ unsigned nRate; /* Bytes of input accepted per Keccak iteration */
+ unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */
+ unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */
+};
+
+/*
+** A single step of the Keccak mixing function for a 1600-bit state
+*/
+static void KeccakF1600Step(SHA3Context *p){
+ int i;
+ u64 B0, B1, B2, B3, B4;
+ u64 C0, C1, C2, C3, C4;
+ u64 D0, D1, D2, D3, D4;
+ static const u64 RC[] = {
+ 0x0000000000000001ULL, 0x0000000000008082ULL,
+ 0x800000000000808aULL, 0x8000000080008000ULL,
+ 0x000000000000808bULL, 0x0000000080000001ULL,
+ 0x8000000080008081ULL, 0x8000000000008009ULL,
+ 0x000000000000008aULL, 0x0000000000000088ULL,
+ 0x0000000080008009ULL, 0x000000008000000aULL,
+ 0x000000008000808bULL, 0x800000000000008bULL,
+ 0x8000000000008089ULL, 0x8000000000008003ULL,
+ 0x8000000000008002ULL, 0x8000000000000080ULL,
+ 0x000000000000800aULL, 0x800000008000000aULL,
+ 0x8000000080008081ULL, 0x8000000000008080ULL,
+ 0x0000000080000001ULL, 0x8000000080008008ULL
+ };
+# define A00 (p->u.s[0])
+# define A01 (p->u.s[1])
+# define A02 (p->u.s[2])
+# define A03 (p->u.s[3])
+# define A04 (p->u.s[4])
+# define A10 (p->u.s[5])
+# define A11 (p->u.s[6])
+# define A12 (p->u.s[7])
+# define A13 (p->u.s[8])
+# define A14 (p->u.s[9])
+# define A20 (p->u.s[10])
+# define A21 (p->u.s[11])
+# define A22 (p->u.s[12])
+# define A23 (p->u.s[13])
+# define A24 (p->u.s[14])
+# define A30 (p->u.s[15])
+# define A31 (p->u.s[16])
+# define A32 (p->u.s[17])
+# define A33 (p->u.s[18])
+# define A34 (p->u.s[19])
+# define A40 (p->u.s[20])
+# define A41 (p->u.s[21])
+# define A42 (p->u.s[22])
+# define A43 (p->u.s[23])
+# define A44 (p->u.s[24])
+# define ROL64(a,x) ((a<<x)|(a>>(64-x)))
+
+ for(i=0; i<24; i+=4){
+ C0 = A00^A10^A20^A30^A40;
+ C1 = A01^A11^A21^A31^A41;
+ C2 = A02^A12^A22^A32^A42;
+ C3 = A03^A13^A23^A33^A43;
+ C4 = A04^A14^A24^A34^A44;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A11^D1), 44);
+ B2 = ROL64((A22^D2), 43);
+ B3 = ROL64((A33^D3), 21);
+ B4 = ROL64((A44^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i];
+ A11 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A20^D0), 3);
+ B3 = ROL64((A31^D1), 45);
+ B4 = ROL64((A42^D2), 61);
+ B0 = ROL64((A03^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A20 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A40^D0), 18);
+ B0 = ROL64((A01^D1), 1);
+ B1 = ROL64((A12^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A34^D4), 8);
+ A40 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A10^D0), 36);
+ B2 = ROL64((A21^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A43^D3), 56);
+ B0 = ROL64((A04^D4), 27);
+ A10 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A30^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A02^D2), 62);
+ B1 = ROL64((A13^D3), 55);
+ B2 = ROL64((A24^D4), 39);
+ A30 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A20^A40^A10^A30;
+ C1 = A11^A31^A01^A21^A41;
+ C2 = A22^A42^A12^A32^A02;
+ C3 = A33^A03^A23^A43^A13;
+ C4 = A44^A14^A34^A04^A24;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A31^D1), 44);
+ B2 = ROL64((A12^D2), 43);
+ B3 = ROL64((A43^D3), 21);
+ B4 = ROL64((A24^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+1];
+ A31 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A40^D0), 3);
+ B3 = ROL64((A21^D1), 45);
+ B4 = ROL64((A02^D2), 61);
+ B0 = ROL64((A33^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A40 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A30^D0), 18);
+ B0 = ROL64((A11^D1), 1);
+ B1 = ROL64((A42^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A04^D4), 8);
+ A30 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A20^D0), 36);
+ B2 = ROL64((A01^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A13^D3), 56);
+ B0 = ROL64((A44^D4), 27);
+ A20 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A10^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A22^D2), 62);
+ B1 = ROL64((A03^D3), 55);
+ B2 = ROL64((A34^D4), 39);
+ A10 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A40^A30^A20^A10;
+ C1 = A31^A21^A11^A01^A41;
+ C2 = A12^A02^A42^A32^A22;
+ C3 = A43^A33^A23^A13^A03;
+ C4 = A24^A14^A04^A44^A34;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A21^D1), 44);
+ B2 = ROL64((A42^D2), 43);
+ B3 = ROL64((A13^D3), 21);
+ B4 = ROL64((A34^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+2];
+ A21 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A30^D0), 3);
+ B3 = ROL64((A01^D1), 45);
+ B4 = ROL64((A22^D2), 61);
+ B0 = ROL64((A43^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A30 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A10^D0), 18);
+ B0 = ROL64((A31^D1), 1);
+ B1 = ROL64((A02^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A44^D4), 8);
+ A10 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A40^D0), 36);
+ B2 = ROL64((A11^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A03^D3), 56);
+ B0 = ROL64((A24^D4), 27);
+ A40 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A20^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A12^D2), 62);
+ B1 = ROL64((A33^D3), 55);
+ B2 = ROL64((A04^D4), 39);
+ A20 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A30^A10^A40^A20;
+ C1 = A21^A01^A31^A11^A41;
+ C2 = A42^A22^A02^A32^A12;
+ C3 = A13^A43^A23^A03^A33;
+ C4 = A34^A14^A44^A24^A04;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A01^D1), 44);
+ B2 = ROL64((A02^D2), 43);
+ B3 = ROL64((A03^D3), 21);
+ B4 = ROL64((A04^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+3];
+ A01 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A10^D0), 3);
+ B3 = ROL64((A11^D1), 45);
+ B4 = ROL64((A12^D2), 61);
+ B0 = ROL64((A13^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A10 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A20^D0), 18);
+ B0 = ROL64((A21^D1), 1);
+ B1 = ROL64((A22^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A24^D4), 8);
+ A20 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A30^D0), 36);
+ B2 = ROL64((A31^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A33^D3), 56);
+ B0 = ROL64((A34^D4), 27);
+ A30 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A40^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A42^D2), 62);
+ B1 = ROL64((A43^D3), 55);
+ B2 = ROL64((A44^D4), 39);
+ A40 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+ }
+}
+
+/*
+** Initialize a new hash. iSize determines the size of the hash
+** in bits and should be one of 224, 256, 384, or 512. Or iSize
+** can be zero to use the default hash size of 256 bits.
+*/
+static void SHA3Init(SHA3Context *p, int iSize){
+ memset(p, 0, sizeof(*p));
+ if( iSize>=128 && iSize<=512 ){
+ p->nRate = (1600 - ((iSize + 31)&~31)*2)/8;
+ }else{
+ p->nRate = (1600 - 2*256)/8;
+ }
+#if BYTEORDER==1234
+ /* Known to be little-endian at compile-time. No-op */
+#elif BYTEORDER==4321
+ p->ixMask = 7; /* Big-endian */
+#else
+ {
+ static unsigned int one = 1;
+ if( 1==*(unsigned char*)&one ){
+ /* Little endian. No byte swapping. */
+ p->ixMask = 0;
+ }else{
+ /* Big endian. Byte swap. */
+ p->ixMask = 7;
+ }
+ }
+#endif
+}
+
+/*
+** Make consecutive calls to the SHA3Update function to add new content
+** to the hash
+*/
+static void SHA3Update(
+ SHA3Context *p,
+ const unsigned char *aData,
+ unsigned int nData
+){
+ unsigned int i = 0;
+#if BYTEORDER==1234
+ if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){
+ for(; i+7<nData; i+=8){
+ p->u.s[p->nLoaded/8] ^= *(u64*)&aData[i];
+ p->nLoaded += 8;
+ if( p->nLoaded>=p->nRate ){
+ KeccakF1600Step(p);
+ p->nLoaded = 0;
+ }
+ }
+ }
+#endif
+ for(; i<nData; i++){
+#if BYTEORDER==1234
+ p->u.x[p->nLoaded] ^= aData[i];
+#elif BYTEORDER==4321
+ p->u.x[p->nLoaded^0x07] ^= aData[i];
+#else
+ p->u.x[p->nLoaded^p->ixMask] ^= aData[i];
+#endif
+ p->nLoaded++;
+ if( p->nLoaded==p->nRate ){
+ KeccakF1600Step(p);
+ p->nLoaded = 0;
+ }
+ }
+}
+
+/*
+** After all content has been added, invoke SHA3Final() to compute
+** the final hash. The function returns a pointer to the binary
+** hash value.
+*/
+static unsigned char *SHA3Final(SHA3Context *p){
+ unsigned int i;
+ if( p->nLoaded==p->nRate-1 ){
+ const unsigned char c1 = 0x86;
+ SHA3Update(p, &c1, 1);
+ }else{
+ const unsigned char c2 = 0x06;
+ const unsigned char c3 = 0x80;
+ SHA3Update(p, &c2, 1);
+ p->nLoaded = p->nRate - 1;
+ SHA3Update(p, &c3, 1);
+ }
+ for(i=0; i<p->nRate; i++){
+ p->u.x[i+p->nRate] = p->u.x[i^p->ixMask];
+ }
+ return &p->u.x[p->nRate];
+}
+
+/*
+** Convert a digest into base-16. digest should be declared as
+** "unsigned char digest[20]" in the calling function. The SHA3
+** digest is stored in the first 20 bytes. zBuf should
+** be "char zBuf[41]".
+*/
+static void DigestToBase16(unsigned char *digest, char *zBuf, int nByte){
+ static const char zEncode[] = "0123456789abcdef";
+ int ix;
+
+ for(ix=0; ix<nByte; ix++){
+ *zBuf++ = zEncode[(*digest>>4)&0xf];
+ *zBuf++ = zEncode[*digest++ & 0xf];
+ }
+ *zBuf = '\0';
+}
+
+
+/*
+** Compute the SHA3 checksum of a file on disk. Store the resulting
+** checksum in the blob pCksum. pCksum is assumed to be initialized.
+**
+** Return the number of errors.
+*/
+static int sha3sum_file(const char *zFilename, int iSize, char *pCksum){
+ FILE *in;
+ SHA3Context ctx;
+ char zBuf[10240];
+
+ in = fopen(zFilename,"rb");
+ if( in==0 ){
+ return 1;
+ }
+ SHA3Init(&ctx, iSize);
+ for(;;){
+ int n = (int)fread(zBuf, 1, sizeof(zBuf), in);
+ if( n<=0 ) break;
+ SHA3Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
+ }
+ fclose(in);
+ DigestToBase16(SHA3Final(&ctx), pCksum, iSize/8);
+ return 0;
+}
+
+/*
+** The SHA1 implementation below is adapted from:
+**
+** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $
+** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $
+**
+** SHA-1 in C
+** By Steve Reid <steve@edmweb.com>
+** 100% Public Domain
+*/
+typedef struct SHA1Context SHA1Context;
+struct SHA1Context {
+ unsigned int state[5];
+ unsigned int count[2];
+ unsigned char buffer[64];
+};
+
+/*
+ * blk0() and blk() perform the initial expand.
+ * I got the idea of expanding during the round function from SSLeay
+ *
+ * blk0le() for little-endian and blk0be() for big-endian.
+ */
+#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r))
+#define rol(x,k) SHA_ROT(x,k,32-(k))
+#define ror(x,k) SHA_ROT(x,32-(k),k)
+
+#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \
+ |(rol(block[i],8)&0x00FF00FF))
+#define blk0be(i) block[i]
+#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \
+ ^block[(i+2)&15]^block[i&15],1))
+
+/*
+ * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
+ *
+ * Rl0() for little-endian and Rb0() for big-endian. Endianness is
+ * determined at run-time.
+ */
+#define Rl0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define Rb0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R1(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R2(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2);
+#define R3(v,w,x,y,z,i) \
+ z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2);
+#define R4(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2);
+
+/*
+ * Hash a single 512-bit block. This is the core of the algorithm.
+ */
+#define a qq[0]
+#define b qq[1]
+#define c qq[2]
+#define d qq[3]
+#define e qq[4]
+
+static void SHA1Transform(
+ unsigned int state[5],
+ const unsigned char buffer[64]
+){
+ unsigned int qq[5]; /* a, b, c, d, e; */
+ static int one = 1;
+ unsigned int block[16];
+ memcpy(block, buffer, 64);
+ memcpy(qq,state,5*sizeof(unsigned int));
+
+ /* Copy context->state[] to working vars */
+ /*
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ */
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ if( 1 == *(unsigned char*)&one ){
+ Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3);
+ Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7);
+ Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11);
+ Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15);
+ }else{
+ Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3);
+ Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7);
+ Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11);
+ Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15);
+ }
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+}
+
+
+/*
+ * SHA1Init - Initialize new context
+ */
+static void SHA1Init(SHA1Context *context){
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/*
+ * Run your data through this.
+ */
+static void SHA1Update(
+ SHA1Context *context,
+ const unsigned char *data,
+ unsigned int len
+){
+ unsigned int i, j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1] += (len>>29)+1;
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ (void)memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64)
+ SHA1Transform(context->state, &data[i]);
+ j = 0;
+ } else {
+ i = 0;
+ }
+ (void)memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/*
+ * Add padding and return the message digest.
+ */
+static void SHA1Final(unsigned char *digest, SHA1Context *context){
+ unsigned int i;
+ unsigned char finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (const unsigned char *)"\200", 1);
+ while ((context->count[0] & 504) != 448)
+ SHA1Update(context, (const unsigned char *)"\0", 1);
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+
+ if (digest) {
+ for (i = 0; i < 20; i++)
+ digest[i] = (unsigned char)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+}
+
+
+/*
+** Compute the SHA1 checksum of a file on disk. Store the resulting
+** checksum in the blob pCksum. pCksum is assumed to be initialized.
+**
+** Return the number of errors.
+*/
+static int sha1sum_file(const char *zFilename, char *pCksum){
+ FILE *in;
+ SHA1Context ctx;
+ unsigned char zResult[20];
+ char zBuf[10240];
+
+ in = fopen(zFilename,"rb");
+ if( in==0 ){
+ return 1;
+ }
+ SHA1Init(&ctx);
+ for(;;){
+ int n = (int)fread(zBuf, 1, sizeof(zBuf), in);
+ if( n<=0 ) break;
+ SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
+ }
+ fclose(in);
+ SHA1Final(zResult, &ctx);
+ DigestToBase16(zResult, pCksum, 20);
+ return 0;
+}
+
+/*
+** Print a usage comment and quit.
+*/
+static void usage(const char *argv0){
+ fprintf(stderr,
+ "Usage: %s manifest\n"
+ "Options:\n"
+ " -v Diagnostic output\n"
+ , argv0);
+ exit(1);
+}
+
+/*
+** Find the first whitespace character in a string. Set that whitespace
+** to a \000 terminator and return a pointer to the next character.
+*/
+static char *nextToken(char *z){
+ while( *z && !isspace(*z) ) z++;
+ if( *z==0 ) return z;
+ *z = 0;
+ return &z[1];
+}
+
+
+int main(int argc, char **argv){
+ const char *zManifest = 0;
+ int i;
+ int bVerbose = 0;
+ FILE *in;
+ int allValid = 1;
+ int rc;
+ SHA3Context ctx;
+ char zDate[50];
+ char zHash[100];
+ char zLine[20000];
+
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( strcmp(z, "-v")==0 ){
+ bVerbose = 1;
+ }else
+ {
+ fprintf(stderr, "unknown option \"%s\"", argv[i]);
+ exit(1);
+ }
+ }else if( zManifest!=0 ){
+ usage(argv[0]);
+ }else{
+ zManifest = z;
+ }
+ }
+ if( zManifest==0 ) usage(argv[0]);
+ zDate[0] = 0;
+ in = fopen(zManifest, "rb");
+ if( in==0 ){
+ fprintf(stderr, "cannot open \"%s\" for reading\n", zManifest);
+ exit(1);
+ }
+ SHA3Init(&ctx, 256);
+ while( fgets(zLine, sizeof(zLine), in) ){
+ if( strncmp(zLine,"# Remove this line", 18)!=0 ){
+ SHA3Update(&ctx, (unsigned char*)zLine, (unsigned)strlen(zLine));
+ }
+ if( strncmp(zLine, "D 20", 4)==0 ){
+ memcpy(zDate, &zLine[2], 10);
+ zDate[10] = ' ';
+ memcpy(&zDate[11], &zLine[13], 8);
+ zDate[19] = 0;
+ continue;
+ }
+ if( strncmp(zLine, "F ", 2)==0 ){
+ char *zFilename = &zLine[2];
+ char *zMHash = nextToken(zFilename);
+ nextToken(zMHash);
+ if( strlen(zMHash)==40 ){
+ rc = sha1sum_file(zFilename, zHash);
+ }else{
+ rc = sha3sum_file(zFilename, 256, zHash);
+ }
+ if( rc ){
+ allValid = 0;
+ if( bVerbose ){
+ printf("hash failed: %s\n", zFilename);
+ }
+ }else if( strcmp(zHash, zMHash)!=0 ){
+ allValid = 0;
+ if( bVerbose ){
+ printf("wrong hash: %s\n", zFilename);
+ printf("... expected: %s\n", zMHash);
+ printf("... got: %s\n", zHash);
+ }
+ }
+ }
+ }
+ fclose(in);
+ DigestToBase16(SHA3Final(&ctx), zHash, 256/8);
+ if( !allValid ){
+ printf("%s %.60salt1\n", zDate, zHash);
+ }else{
+ printf("%s %s\n", zDate, zHash);
+ }
+ return 0;
+}
diff --git a/tool/mkspeedsql.tcl b/tool/mkspeedsql.tcl
new file mode 100644
index 0000000..04bafc0
--- /dev/null
+++ b/tool/mkspeedsql.tcl
@@ -0,0 +1,237 @@
+# 2008 October 9
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file generates SQL text used for performance testing.
+#
+# $Id: mkspeedsql.tcl,v 1.1 2008/10/09 17:57:34 drh Exp $
+#
+
+# Set a uniform random seed
+expr srand(0)
+
+# The number_name procedure below converts its argment (an integer)
+# into a string which is the English-language name for that number.
+#
+# Example:
+#
+# puts [number_name 123] -> "one hundred twenty three"
+#
+set ones {zero one two three four five six seven eight nine
+ ten eleven twelve thirteen fourteen fifteen sixteen seventeen
+ eighteen nineteen}
+set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
+proc number_name {n} {
+ if {$n>=1000} {
+ set txt "[number_name [expr {$n/1000}]] thousand"
+ set n [expr {$n%1000}]
+ } else {
+ set txt {}
+ }
+ if {$n>=100} {
+ append txt " [lindex $::ones [expr {$n/100}]] hundred"
+ set n [expr {$n%100}]
+ }
+ if {$n>=20} {
+ append txt " [lindex $::tens [expr {$n/10}]]"
+ set n [expr {$n%10}]
+ }
+ if {$n>0} {
+ append txt " [lindex $::ones $n]"
+ }
+ set txt [string trim $txt]
+ if {$txt==""} {set txt zero}
+ return $txt
+}
+
+# Create a database schema.
+#
+puts {
+ PRAGMA page_size=1024;
+ PRAGMA cache_size=8192;
+ PRAGMA locking_mode=EXCLUSIVE;
+ CREATE TABLE t1(a INTEGER, b INTEGER, c TEXT);
+ CREATE TABLE t2(a INTEGER, b INTEGER, c TEXT);
+ CREATE INDEX i2a ON t2(a);
+ CREATE INDEX i2b ON t2(b);
+ SELECT name FROM sqlite_master ORDER BY 1;
+}
+
+
+# 50000 INSERTs on an unindexed table
+#
+set t1c_list {}
+puts {BEGIN;}
+for {set i 1} {$i<=50000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ set x [number_name $r]
+ lappend t1c_list $x
+ puts "INSERT INTO t1 VALUES($i,$r,'$x');"
+}
+puts {COMMIT;}
+
+# 50000 INSERTs on an indexed table
+#
+puts {BEGIN;}
+for {set i 1} {$i<=50000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts "INSERT INTO t2 VALUES($i,$r,'[number_name $r]');"
+}
+puts {COMMIT;}
+
+
+# 50 SELECTs on an integer comparison. There is no index so
+# a full table scan is required.
+#
+for {set i 0} {$i<50} {incr i} {
+ set lwr [expr {$i*100}]
+ set upr [expr {($i+10)*100}]
+ puts "SELECT count(*), avg(b) FROM t1 WHERE b>=$lwr AND b<$upr;"
+}
+
+# 50 SELECTs on an LIKE comparison. There is no index so a full
+# table scan is required.
+#
+for {set i 0} {$i<50} {incr i} {
+ puts "SELECT count(*), avg(b) FROM t1 WHERE c LIKE '%[number_name $i]%';"
+}
+
+# Create indices
+#
+puts {BEGIN;}
+puts {
+ CREATE INDEX i1a ON t1(a);
+ CREATE INDEX i1b ON t1(b);
+ CREATE INDEX i1c ON t1(c);
+}
+puts {COMMIT;}
+
+# 5000 SELECTs on an integer comparison where the integer is
+# indexed.
+#
+set sql {}
+for {set i 0} {$i<5000} {incr i} {
+ set lwr [expr {$i*100}]
+ set upr [expr {($i+10)*100}]
+ puts "SELECT count(*), avg(b) FROM t1 WHERE b>=$lwr AND b<$upr;"
+}
+
+# 100000 random SELECTs against rowid.
+#
+for {set i 1} {$i<=100000} {incr i} {
+ set id [expr {int(rand()*50000)+1}]
+ puts "SELECT c FROM t1 WHERE rowid=$id;"
+}
+
+# 100000 random SELECTs against a unique indexed column.
+#
+for {set i 1} {$i<=100000} {incr i} {
+ set id [expr {int(rand()*50000)+1}]
+ puts "SELECT c FROM t1 WHERE a=$id;"
+}
+
+# 50000 random SELECTs against an indexed column text column
+#
+set nt1c [llength $t1c_list]
+for {set i 0} {$i<50000} {incr i} {
+ set r [expr {int(rand()*$nt1c)}]
+ set c [lindex $t1c_list $i]
+ puts "SELECT c FROM t1 WHERE c='$c';"
+}
+
+
+# Vacuum
+puts {VACUUM;}
+
+# 5000 updates of ranges where the field being compared is indexed.
+#
+puts {BEGIN;}
+for {set i 0} {$i<5000} {incr i} {
+ set lwr [expr {$i*2}]
+ set upr [expr {($i+1)*2}]
+ puts "UPDATE t1 SET b=b*2 WHERE a>=$lwr AND a<$upr;"
+}
+puts {COMMIT;}
+
+# 50000 single-row updates. An index is used to find the row quickly.
+#
+puts {BEGIN;}
+for {set i 0} {$i<50000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts "UPDATE t1 SET b=$r WHERE a=$i;"
+}
+puts {COMMIT;}
+
+# 1 big text update that touches every row in the table.
+#
+puts {
+ UPDATE t1 SET c=a;
+}
+
+# Many individual text updates. Each row in the table is
+# touched through an index.
+#
+puts {BEGIN;}
+for {set i 1} {$i<=50000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts "UPDATE t1 SET c='[number_name $r]' WHERE a=$i;"
+}
+puts {COMMIT;}
+
+# Delete all content in a table.
+#
+puts {DELETE FROM t1;}
+
+# Copy one table into another
+#
+puts {INSERT INTO t1 SELECT * FROM t2;}
+
+# Delete all content in a table, one row at a time.
+#
+puts {DELETE FROM t1 WHERE 1;}
+
+# Refill the table yet again
+#
+puts {INSERT INTO t1 SELECT * FROM t2;}
+
+# Drop the table and recreate it without its indices.
+#
+puts {BEGIN;}
+puts {
+ DROP TABLE t1;
+ CREATE TABLE t1(a INTEGER, b INTEGER, c TEXT);
+}
+puts {COMMIT;}
+
+# Refill the table yet again. This copy should be faster because
+# there are no indices to deal with.
+#
+puts {INSERT INTO t1 SELECT * FROM t2;}
+
+# Select 20000 rows from the table at random.
+#
+puts {
+ SELECT rowid FROM t1 ORDER BY random() LIMIT 20000;
+}
+
+# Delete 20000 random rows from the table.
+#
+puts {
+ DELETE FROM t1 WHERE rowid IN
+ (SELECT rowid FROM t1 ORDER BY random() LIMIT 20000);
+}
+puts {SELECT count(*) FROM t1;}
+
+# Delete 20000 more rows at random from the table.
+#
+puts {
+ DELETE FROM t1 WHERE rowid IN
+ (SELECT rowid FROM t1 ORDER BY random() LIMIT 20000);
+}
+puts {SELECT count(*) FROM t1;}
diff --git a/tool/mksqlite3c-noext.tcl b/tool/mksqlite3c-noext.tcl
new file mode 100644
index 0000000..8452072
--- /dev/null
+++ b/tool/mksqlite3c-noext.tcl
@@ -0,0 +1,367 @@
+#!/usr/bin/tclsh
+#
+# To build a single huge source file holding all of SQLite (or at
+# least the core components - the test harness, shell, and TCL
+# interface are omitted.) first do
+#
+# make target_source
+#
+# The make target above moves all of the source code files into
+# a subdirectory named "tsrc". (This script expects to find the files
+# there and will not work if they are not found.) There are a few
+# generated C code files that are also added to the tsrc directory.
+# For example, the "parse.c" and "parse.h" files to implement the
+# the parser are derived from "parse.y" using lemon. And the
+# "keywordhash.h" files is generated by a program named "mkkeywordhash".
+#
+# After the "tsrc" directory has been created and populated, run
+# this script:
+#
+# tclsh mksqlite3c-noext.tcl
+#
+# The amalgamated SQLite code will be written into sqlite3.c
+#
+
+# Begin by reading the "sqlite3.h" header file. Extract the version number
+# from in this file. The version number is needed to generate the header
+# comment of the amalgamation.
+#
+set addstatic 1
+set linemacros 0
+set useapicall 0
+for {set i 0} {$i<[llength $argv]} {incr i} {
+ set x [lindex $argv $i]
+ if {[regexp {^-+nostatic$} $x]} {
+ set addstatic 0
+ } elseif {[regexp {^-+linemacros} $x]} {
+ set linemacros 1
+ } elseif {[regexp {^-+useapicall} $x]} {
+ set useapicall 1
+ } else {
+ error "unknown command-line option: $x"
+ }
+}
+set in [open tsrc/sqlite3.h]
+set cnt 0
+set VERSION ?????
+while {![eof $in]} {
+ set line [gets $in]
+ if {$line=="" && [eof $in]} break
+ incr cnt
+ regexp {#define\s+SQLITE_VERSION\s+"(.*)"} $line all VERSION
+}
+close $in
+
+# Open the output file and write a header comment at the beginning
+# of the file.
+#
+set out [open sqlite3.c w]
+# Force the output to use unix line endings, even on Windows.
+fconfigure $out -translation lf
+set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
+puts $out [subst \
+{/******************************************************************************
+** This file is an amalgamation of many separate C source files from SQLite
+** version $VERSION. By combining all the individual C code files into this
+** single large file, the entire code can be compiled as a single translation
+** unit. This allows many compilers to do optimizations that would not be
+** possible if the files were compiled separately. Performance improvements
+** of 5% or more are commonly seen when SQLite is compiled as a single
+** translation unit.
+**
+** This file is all you need to compile SQLite. To use SQLite in other
+** programs, you need this file and the "sqlite3.h" header file that defines
+** the programming interface to the SQLite library. (If you do not have
+** the "sqlite3.h" header file at hand, you will find a copy embedded within
+** the text of this file. Search for "Begin file sqlite3.h" to find the start
+** of the embedded sqlite3.h header file.) Additional code files may be needed
+** if you want a wrapper to interface SQLite with your choice of programming
+** language. The code for the "sqlite3" command-line shell is also in a
+** separate file. This file contains only code for the core SQLite library.
+*/
+#define SQLITE_CORE 1
+#define SQLITE_AMALGAMATION 1}]
+if {$addstatic} {
+ puts $out \
+{#ifndef SQLITE_PRIVATE
+# define SQLITE_PRIVATE static
+#endif}
+}
+
+# These are the header files used by SQLite. The first time any of these
+# files are seen in a #include statement in the C code, include the complete
+# text of the file in-line. The file only needs to be included once.
+#
+foreach hdr {
+ btree.h
+ btreeInt.h
+ hash.h
+ hwtime.h
+ keywordhash.h
+ msvc.h
+ mutex.h
+ opcodes.h
+ os_common.h
+ os_setup.h
+ os_win.h
+ os.h
+ pager.h
+ parse.h
+ pcache.h
+ pragma.h
+ sqlite3.h
+ sqlite3ext.h
+ sqliteicu.h
+ sqliteInt.h
+ sqliteLimit.h
+ vdbe.h
+ vdbeInt.h
+ vxworks.h
+ wal.h
+ whereInt.h
+} {
+ set available_hdr($hdr) 1
+}
+set available_hdr(sqliteInt.h) 0
+
+# These headers should be copied into the amalgamation without modifying any
+# of their function declarations or definitions.
+set varonly_hdr(sqlite3.h) 1
+
+# These are the functions that accept a variable number of arguments. They
+# always need to use the "cdecl" calling convention even when another calling
+# convention (e.g. "stcall") is being used for the rest of the library.
+set cdecllist {
+ sqlite3_config
+ sqlite3_db_config
+ sqlite3_log
+ sqlite3_mprintf
+ sqlite3_snprintf
+ sqlite3_test_control
+ sqlite3_vtab_config
+}
+
+# 78 stars used for comment formatting.
+set s78 \
+{*****************************************************************************}
+
+# Insert a comment into the code
+#
+proc section_comment {text} {
+ global out s78
+ set n [string length $text]
+ set nstar [expr {60 - $n}]
+ set stars [string range $s78 0 $nstar]
+ puts $out "/************** $text $stars/"
+}
+
+# Read the source file named $filename and write it into the
+# sqlite3.c output file. If any #include statements are seen,
+# process them appropriately.
+#
+proc copy_file {filename} {
+ global seen_hdr available_hdr varonly_hdr cdecllist out
+ global addstatic linemacros useapicall
+ set ln 0
+ set tail [file tail $filename]
+ section_comment "Begin file $tail"
+ if {$linemacros} {puts $out "#line 1 \"$filename\""}
+ set in [open $filename r]
+ set varpattern {^[a-zA-Z][a-zA-Z_0-9 *]+(sqlite3[_a-zA-Z0-9]+)(\[|;| =)}
+ set declpattern {([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3[_a-zA-Z0-9]+)(\(.*)}
+ if {[file extension $filename]==".h"} {
+ set declpattern " *$declpattern"
+ }
+ set declpattern ^$declpattern\$
+ while {![eof $in]} {
+ set line [gets $in]
+ incr ln
+ if {[regexp {^\s*#\s*include\s+["<]([^">]+)[">]} $line all hdr]} {
+ if {[info exists available_hdr($hdr)]} {
+ if {$available_hdr($hdr)} {
+ if {$hdr!="os_common.h" && $hdr!="hwtime.h"} {
+ set available_hdr($hdr) 0
+ }
+ section_comment "Include $hdr in the middle of $tail"
+ copy_file tsrc/$hdr
+ section_comment "Continuing where we left off in $tail"
+ if {$linemacros} {puts $out "#line [expr {$ln+1}] \"$filename\""}
+ } else {
+ # Comment out the entire line, replacing any nested comment
+ # begin/end markers with the harmless substring "**".
+ puts $out "/* [string map [list /* ** */ **] $line] */"
+ }
+ } elseif {![info exists seen_hdr($hdr)]} {
+ if {![regexp {/\*\s+amalgamator:\s+dontcache\s+\*/} $line]} {
+ set seen_hdr($hdr) 1
+ }
+ puts $out $line
+ } elseif {[regexp {/\*\s+amalgamator:\s+keep\s+\*/} $line]} {
+ # This include file must be kept because there was a "keep"
+ # directive inside of a line comment.
+ puts $out $line
+ } else {
+ # Comment out the entire line, replacing any nested comment
+ # begin/end markers with the harmless substring "**".
+ puts $out "/* [string map [list /* ** */ **] $line] */"
+ }
+ } elseif {[regexp {^#ifdef __cplusplus} $line]} {
+ puts $out "#if 0"
+ } elseif {!$linemacros && [regexp {^#line} $line]} {
+ # Skip #line directives.
+ } elseif {$addstatic
+ && ![regexp {^(static|typedef|SQLITE_PRIVATE)} $line]} {
+ # Skip adding the SQLITE_PRIVATE or SQLITE_API keyword before
+ # functions if this header file does not need it.
+ if {![info exists varonly_hdr($tail)]
+ && [regexp $declpattern $line all rettype funcname rest]} {
+ regsub {^SQLITE_API } $line {} line
+ # Add the SQLITE_PRIVATE or SQLITE_API keyword before functions.
+ # so that linkage can be modified at compile-time.
+ if {[regexp {^sqlite3[a-z]*_} $funcname]} {
+ set line SQLITE_API
+ append line " " [string trim $rettype]
+ if {[string index $rettype end] ne "*"} {
+ append line " "
+ }
+ if {$useapicall} {
+ if {[lsearch -exact $cdecllist $funcname] >= 0} {
+ append line SQLITE_CDECL " "
+ } else {
+ append line SQLITE_APICALL " "
+ }
+ }
+ append line $funcname $rest
+ puts $out $line
+ } else {
+ puts $out "SQLITE_PRIVATE $line"
+ }
+ } elseif {[regexp $varpattern $line all varname]} {
+ # Add the SQLITE_PRIVATE before variable declarations or
+ # definitions for internal use
+ regsub {^SQLITE_API } $line {} line
+ if {![regexp {^sqlite3_} $varname]} {
+ regsub {^extern } $line {} line
+ puts $out "SQLITE_PRIVATE $line"
+ } else {
+ if {[regexp {const char sqlite3_version\[\];} $line]} {
+ set line {const char sqlite3_version[] = SQLITE_VERSION;}
+ }
+ regsub {^SQLITE_EXTERN } $line {} line
+ puts $out "SQLITE_API $line"
+ }
+ } elseif {[regexp {^(SQLITE_EXTERN )?void \(\*sqlite3IoTrace\)} $line]} {
+ regsub {^SQLITE_API } $line {} line
+ regsub {^SQLITE_EXTERN } $line {} line
+ puts $out $line
+ } elseif {[regexp {^void \(\*sqlite3Os} $line]} {
+ regsub {^SQLITE_API } $line {} line
+ puts $out "SQLITE_PRIVATE $line"
+ } else {
+ puts $out $line
+ }
+ } else {
+ puts $out $line
+ }
+ }
+ close $in
+ section_comment "End of $tail"
+}
+
+
+# Process the source files. Process files containing commonly
+# used subroutines first in order to help the compiler find
+# inlining opportunities.
+#
+foreach file {
+ sqliteInt.h
+
+ global.c
+ ctime.c
+ status.c
+ date.c
+ os.c
+
+ fault.c
+ mem0.c
+ mem1.c
+ mem2.c
+ mem3.c
+ mem5.c
+ mutex.c
+ mutex_noop.c
+ mutex_unix.c
+ mutex_w32.c
+ malloc.c
+ printf.c
+ treeview.c
+ random.c
+ threads.c
+ utf.c
+ util.c
+ hash.c
+ opcodes.c
+
+ os_unix.c
+ os_win.c
+
+ bitvec.c
+ pcache.c
+ pcache1.c
+ rowset.c
+ pager.c
+ wal.c
+
+ btmutex.c
+ btree.c
+ backup.c
+
+ vdbemem.c
+ vdbeaux.c
+ vdbeapi.c
+ vdbetrace.c
+ vdbe.c
+ vdbeblob.c
+ vdbesort.c
+ memjournal.c
+
+ walker.c
+ resolve.c
+ expr.c
+ alter.c
+ analyze.c
+ attach.c
+ auth.c
+ build.c
+ callback.c
+ delete.c
+ func.c
+ fkey.c
+ insert.c
+ legacy.c
+ loadext.c
+ pragma.c
+ prepare.c
+ select.c
+ table.c
+ trigger.c
+ update.c
+ vacuum.c
+ vtab.c
+ wherecode.c
+ whereexpr.c
+ where.c
+ window.c
+
+ parse.c
+
+ tokenize.c
+ complete.c
+
+ main.c
+ notify.c
+} {
+ copy_file tsrc/$file
+}
+
+close $out
diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl
new file mode 100644
index 0000000..53fa59a
--- /dev/null
+++ b/tool/mksqlite3c.tcl
@@ -0,0 +1,488 @@
+#!/usr/bin/tclsh
+#
+# To build a single huge source file holding all of SQLite (or at
+# least the core components - the test harness, shell, and TCL
+# interface are omitted.) first do
+#
+# make target_source
+#
+# The make target above moves all of the source code files into
+# a subdirectory named "tsrc". (This script expects to find the files
+# there and will not work if they are not found.) There are a few
+# generated C code files that are also added to the tsrc directory.
+# For example, the "parse.c" and "parse.h" files to implement the
+# the parser are derived from "parse.y" using lemon. And the
+# "keywordhash.h" files is generated by a program named "mkkeywordhash".
+#
+# After the "tsrc" directory has been created and populated, run
+# this script:
+#
+# tclsh mksqlite3c.tcl
+#
+# The amalgamated SQLite code will be written into sqlite3.c
+#
+
+set help {Usage: tclsh mksqlite3c.tcl <options>
+ where <options> is zero or more of the following with these effects:
+ --nostatic => Do not generate with compile-time modifiable linkage.
+ --linemacros=? => Emit #line directives into output or not. (? = 1 or 0)
+ --useapicall => Prepend functions with SQLITE_APICALL or SQLITE_CDECL.
+ --srcdir $SRC => Specify the directory containing constituent sources.
+ --help => See this.
+ The value setting options default to --linemacros=1 and '--srcdir tsrc' .
+}
+
+# Begin by reading the "sqlite3.h" header file. Extract the version number
+# from in this file. The version number is needed to generate the header
+# comment of the amalgamation.
+#
+
+set addstatic 1
+set linemacros 0
+set useapicall 0
+set enable_recover 0
+set srcdir tsrc
+
+for {set i 0} {$i<[llength $argv]} {incr i} {
+ set x [lindex $argv $i]
+ if {[regexp {^-?-enable-recover$} $x]} {
+ set enable_recover 1
+ } elseif {[regexp {^-?-nostatic$} $x]} {
+ set addstatic 0
+ } elseif {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} {
+ if {$ulm == ""} {set ulm 1}
+ set linemacros $ulm
+ } elseif {[regexp {^-?-useapicall$} $x]} {
+ set useapicall 1
+ } elseif {[regexp {^-?-srcdir$} $x]} {
+ incr i
+ if {$i==[llength $argv]} {
+ error "No argument following $x"
+ }
+ set srcdir [lindex $argv $i]
+ } elseif {[regexp {^-?-((help)|\?)$} $x]} {
+ puts $help
+ exit 0
+ } else {
+ error "unknown command-line option: $x"
+ }
+}
+set in [open $srcdir/sqlite3.h]
+set cnt 0
+set VERSION ?????
+while {![eof $in]} {
+ set line [gets $in]
+ if {$line=="" && [eof $in]} break
+ incr cnt
+ regexp {#define\s+SQLITE_VERSION\s+"(.*)"} $line all VERSION
+}
+close $in
+
+# Open the output file and write a header comment at the beginning
+# of the file.
+#
+set fname sqlite3.c
+if {$enable_recover} { set fname sqlite3r.c }
+set out [open $fname w]
+# Force the output to use unix line endings, even on Windows.
+fconfigure $out -translation lf
+set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
+puts $out [subst \
+{/******************************************************************************
+** This file is an amalgamation of many separate C source files from SQLite
+** version $VERSION. By combining all the individual C code files into this
+** single large file, the entire code can be compiled as a single translation
+** unit. This allows many compilers to do optimizations that would not be
+** possible if the files were compiled separately. Performance improvements
+** of 5% or more are commonly seen when SQLite is compiled as a single
+** translation unit.
+**
+** This file is all you need to compile SQLite. To use SQLite in other
+** programs, you need this file and the "sqlite3.h" header file that defines
+** the programming interface to the SQLite library. (If you do not have
+** the "sqlite3.h" header file at hand, you will find a copy embedded within
+** the text of this file. Search for "Begin file sqlite3.h" to find the start
+** of the embedded sqlite3.h header file.) Additional code files may be needed
+** if you want a wrapper to interface SQLite with your choice of programming
+** language. The code for the "sqlite3" command-line shell is also in a
+** separate file. This file contains only code for the core SQLite library.
+**}]
+set srcroot [file dirname [file dirname [info script]]]
+if {$tcl_platform(platform)=="windows"} {
+ set vsrcprog src-verify.exe
+} else {
+ set vsrcprog ./src-verify
+}
+if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} {
+ set res [string trim [split [exec $vsrcprog -x $srcroot]] \n]
+ puts $out "** The content in this amalgamation comes from Fossil check-in"
+ puts -nonewline $out "** [string range [lindex $res 0] 0 35]"
+ if {[llength $res]==1} {
+ puts $out "."
+ } else {
+ puts $out " with changes in files:\n**"
+ foreach f [lrange $res 1 end] {
+ puts $out "** $f"
+ }
+ }
+} else {
+ puts $out "** The origin of the sources used to build this amalgamation"
+ puts $out "** is unknown."
+}
+puts $out [subst {*/
+#define SQLITE_CORE 1
+#define SQLITE_AMALGAMATION 1}]
+if {$addstatic} {
+ puts $out \
+{#ifndef SQLITE_PRIVATE
+# define SQLITE_PRIVATE static
+#endif}
+}
+
+# Examine the parse.c file. If it contains lines of the form:
+#
+# "#ifndef SQLITE_ENABLE_UPDATE_LIMIT
+#
+# then set the SQLITE_UDL_CAPABLE_PARSER flag in the amalgamation.
+#
+set in [open $srcdir/parse.c]
+if {[regexp {ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT} [read $in]]} {
+ puts $out "#define SQLITE_UDL_CAPABLE_PARSER 1"
+}
+close $in
+
+# These are the header files used by SQLite. The first time any of these
+# files are seen in a #include statement in the C code, include the complete
+# text of the file in-line. The file only needs to be included once.
+#
+foreach hdr {
+ btree.h
+ btreeInt.h
+ fts3.h
+ fts3Int.h
+ fts3_hash.h
+ fts3_tokenizer.h
+ geopoly.c
+ hash.h
+ hwtime.h
+ keywordhash.h
+ msvc.h
+ mutex.h
+ opcodes.h
+ os_common.h
+ os_setup.h
+ os_win.h
+ os.h
+ pager.h
+ parse.h
+ pcache.h
+ pragma.h
+ rtree.h
+ sqlite3session.h
+ sqlite3.h
+ sqlite3ext.h
+ sqlite3rbu.h
+ sqliteicu.h
+ sqliteInt.h
+ sqliteLimit.h
+ vdbe.h
+ vdbeInt.h
+ vxworks.h
+ wal.h
+ whereInt.h
+ sqlite3recover.h
+} {
+ set available_hdr($hdr) 1
+}
+set available_hdr(sqliteInt.h) 0
+set available_hdr(os_common.h) 0
+set available_hdr(sqlite3session.h) 0
+
+# These headers should be copied into the amalgamation without modifying any
+# of their function declarations or definitions.
+set varonly_hdr(sqlite3.h) 1
+
+# These are the functions that accept a variable number of arguments. They
+# always need to use the "cdecl" calling convention even when another calling
+# convention (e.g. "stcall") is being used for the rest of the library.
+set cdecllist {
+ sqlite3_config
+ sqlite3_db_config
+ sqlite3_log
+ sqlite3_mprintf
+ sqlite3_snprintf
+ sqlite3_test_control
+ sqlite3_vtab_config
+}
+
+# 78 stars used for comment formatting.
+set s78 \
+{*****************************************************************************}
+
+# Insert a comment into the code
+#
+proc section_comment {text} {
+ global out s78
+ set n [string length $text]
+ set nstar [expr {60 - $n}]
+ set stars [string range $s78 0 $nstar]
+ puts $out "/************** $text $stars/"
+}
+
+# Read the source file named $filename and write it into the
+# sqlite3.c output file. If any #include statements are seen,
+# process them appropriately.
+#
+proc copy_file {filename} {
+ global seen_hdr available_hdr varonly_hdr cdecllist out
+ global addstatic linemacros useapicall srcdir
+ set ln 0
+ set tail [file tail $filename]
+ section_comment "Begin file $tail"
+ if {$linemacros} {puts $out "#line 1 \"$filename\""}
+ set in [open $filename r]
+ set varpattern {^[a-zA-Z][a-zA-Z_0-9 *]+(sqlite3[_a-zA-Z0-9]+)(\[|;| =)}
+ set declpattern {([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3[_a-zA-Z0-9]+)(\(.*)}
+ if {[file extension $filename]==".h"} {
+ set declpattern " *$declpattern"
+ }
+ set declpattern ^$declpattern\$
+ while {![eof $in]} {
+ set line [string trimright [gets $in]]
+ incr ln
+ if {[regexp {^\s*#\s*include\s+["<]([^">]+)[">]} $line all hdr]} {
+ if {[info exists available_hdr($hdr)]} {
+ if {$available_hdr($hdr)} {
+ set available_hdr($hdr) 0
+ section_comment "Include $hdr in the middle of $tail"
+ copy_file $srcdir/$hdr
+ section_comment "Continuing where we left off in $tail"
+ if {$linemacros} {puts $out "#line [expr {$ln+1}] \"$filename\""}
+ } else {
+ # Comment out the entire line, replacing any nested comment
+ # begin/end markers with the harmless substring "**".
+ puts $out "/* [string map [list /* ** */ **] $line] */"
+ }
+ } elseif {![info exists seen_hdr($hdr)]} {
+ if {![regexp {/\*\s+amalgamator:\s+dontcache\s+\*/} $line]} {
+ set seen_hdr($hdr) 1
+ }
+ puts $out $line
+ } elseif {[regexp {/\*\s+amalgamator:\s+keep\s+\*/} $line]} {
+ # This include file must be kept because there was a "keep"
+ # directive inside of a line comment.
+ puts $out $line
+ } else {
+ # Comment out the entire line, replacing any nested comment
+ # begin/end markers with the harmless substring "**".
+ puts $out "/* [string map [list /* ** */ **] $line] */"
+ }
+ } elseif {[regexp {^#ifdef __cplusplus} $line]} {
+ puts $out "#if 0"
+ } elseif {!$linemacros && [regexp {^#line} $line]} {
+ # Skip #line directives.
+ } elseif {$addstatic
+ && ![regexp {^(static|typedef|SQLITE_PRIVATE)} $line]} {
+ # Skip adding the SQLITE_PRIVATE or SQLITE_API keyword before
+ # functions if this header file does not need it.
+ if {![info exists varonly_hdr($tail)]
+ && [regexp $declpattern $line all rettype funcname rest]} {
+ regsub {^SQLITE_API } $line {} line
+ regsub {^SQLITE_API } $rettype {} rettype
+
+ # Add the SQLITE_PRIVATE or SQLITE_API keyword before functions.
+ # so that linkage can be modified at compile-time.
+ if {[regexp {^sqlite3[a-z]*_} $funcname]} {
+ set line SQLITE_API
+ append line " " [string trim $rettype]
+ if {[string index $rettype end] ne "*"} {
+ append line " "
+ }
+ if {$useapicall} {
+ if {[lsearch -exact $cdecllist $funcname] >= 0} {
+ append line SQLITE_CDECL " "
+ } else {
+ append line SQLITE_APICALL " "
+ }
+ }
+ append line $funcname $rest
+ if {$funcname=="sqlite3_sourceid"} {
+ # The sqlite3_sourceid() routine is synthesized at the end of
+ # the amalgamation
+ puts $out "/* $line */"
+ } else {
+ puts $out $line
+ }
+ } else {
+ puts $out "SQLITE_PRIVATE $line"
+ }
+ } elseif {[regexp $varpattern $line all varname]} {
+ # Add the SQLITE_PRIVATE before variable declarations or
+ # definitions for internal use
+ regsub {^SQLITE_API } $line {} line
+ if {![regexp {^sqlite3_} $varname]
+ && ![regexp {^sqlite3Show[A-Z]} $varname]} {
+ regsub {^extern } $line {} line
+ puts $out "SQLITE_PRIVATE $line"
+ } else {
+ if {[regexp {const char sqlite3_version\[\];} $line]} {
+ set line {const char sqlite3_version[] = SQLITE_VERSION;}
+ }
+ regsub {^SQLITE_EXTERN } $line {} line
+ puts $out "SQLITE_API $line"
+ }
+ } elseif {[regexp {^(SQLITE_EXTERN )?void \(\*sqlite3IoTrace\)} $line]} {
+ regsub {^SQLITE_API } $line {} line
+ regsub {^SQLITE_EXTERN } $line {} line
+ puts $out $line
+ } elseif {[regexp {^void \(\*sqlite3Os} $line]} {
+ regsub {^SQLITE_API } $line {} line
+ puts $out "SQLITE_PRIVATE $line"
+ } else {
+ puts $out $line
+ }
+ } else {
+ puts $out $line
+ }
+ }
+ close $in
+ section_comment "End of $tail"
+}
+
+
+# Process the source files. Process files containing commonly
+# used subroutines first in order to help the compiler find
+# inlining opportunities.
+#
+set flist {
+ sqliteInt.h
+ os_common.h
+ ctime.c
+
+ global.c
+ status.c
+ date.c
+ os.c
+
+ fault.c
+ mem0.c
+ mem1.c
+ mem2.c
+ mem3.c
+ mem5.c
+ mutex.c
+ mutex_noop.c
+ mutex_unix.c
+ mutex_w32.c
+ malloc.c
+ printf.c
+ treeview.c
+ random.c
+ threads.c
+ utf.c
+ util.c
+ hash.c
+ opcodes.c
+
+ os_kv.c
+ os_unix.c
+ os_win.c
+ memdb.c
+
+ bitvec.c
+ pcache.c
+ pcache1.c
+ rowset.c
+ pager.c
+ wal.c
+
+ btmutex.c
+ btree.c
+ backup.c
+
+ vdbemem.c
+ vdbeaux.c
+ vdbeapi.c
+ vdbetrace.c
+ vdbe.c
+ vdbeblob.c
+ vdbesort.c
+ vdbevtab.c
+ memjournal.c
+
+ walker.c
+ resolve.c
+ expr.c
+ alter.c
+ analyze.c
+ attach.c
+ auth.c
+ build.c
+ callback.c
+ delete.c
+ func.c
+ fkey.c
+ insert.c
+ legacy.c
+ loadext.c
+ pragma.c
+ prepare.c
+ select.c
+ table.c
+ trigger.c
+ update.c
+ upsert.c
+ vacuum.c
+ vtab.c
+ wherecode.c
+ whereexpr.c
+ where.c
+ window.c
+
+ parse.c
+
+ tokenize.c
+ complete.c
+
+ main.c
+ notify.c
+
+ fts3.c
+ fts3_aux.c
+ fts3_expr.c
+ fts3_hash.c
+ fts3_porter.c
+ fts3_tokenizer.c
+ fts3_tokenizer1.c
+ fts3_tokenize_vtab.c
+ fts3_write.c
+ fts3_snippet.c
+ fts3_unicode.c
+ fts3_unicode2.c
+
+ json.c
+ rtree.c
+ icu.c
+ fts3_icu.c
+ sqlite3rbu.c
+ dbstat.c
+ dbpage.c
+ sqlite3session.c
+ fts5.c
+ stmt.c
+}
+if {$enable_recover} {
+ lappend flist sqlite3recover.c dbdata.c
+}
+foreach file $flist {
+ copy_file $srcdir/$file
+}
+
+puts $out \
+"/* Return the source-id for this library */
+SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }"
+
+puts $out \
+"/************************** End of sqlite3.c ******************************/"
+
+close $out
diff --git a/tool/mksqlite3h.tcl b/tool/mksqlite3h.tcl
new file mode 100644
index 0000000..bd579c2
--- /dev/null
+++ b/tool/mksqlite3h.tcl
@@ -0,0 +1,165 @@
+#!/usr/bin/tclsh
+#
+# This script constructs the "sqlite3.h" header file from the following
+# sources:
+#
+# 1) The src/sqlite.h.in source file. This is the template for sqlite3.h.
+# 2) The VERSION file containing the current SQLite version number.
+# 3) The manifest file from the fossil SCM. This gives use the date.
+# 4) The manifest.uuid file from the fossil SCM. This gives the SHA1 hash.
+#
+# Run this script by specifying the root directory of the source tree
+# on the command-line.
+#
+# This script performs processing on src/sqlite.h.in. It:
+#
+# 1) Adds SQLITE_EXTERN in front of the declaration of global variables,
+# 2) Adds SQLITE_API in front of the declaration of API functions,
+# 3) Replaces the string --VERS-- with the current library version,
+# formatted as a string (e.g. "3.6.17"), and
+# 4) Replaces the string --VERSION-NUMBER-- with current library version,
+# formatted as an integer (e.g. "3006017").
+# 5) Replaces the string --SOURCE-ID-- with the date and time and sha1
+# hash of the fossil-scm manifest for the source tree.
+# 6) Adds the SQLITE_CALLBACK calling convention macro in front of all
+# callback declarations.
+#
+# This script outputs to stdout.
+#
+# Example usage:
+#
+# tclsh mksqlite3h.tcl ../sqlite >sqlite3.h
+#
+
+
+# Get the source tree root directory from the command-line
+#
+set TOP [lindex $argv 0]
+
+# Enable use of SQLITE_APICALL macros at the right points?
+#
+set useapicall 0
+
+# Include sqlite3recover.h?
+#
+set enable_recover 0
+
+if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} {
+ set useapicall 1
+}
+if {[lsearch -regexp [lrange $argv 1 end] {^-+enable-recover}] != -1} {
+ set enable_recover 1
+}
+
+# Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file.
+#
+set in [open $TOP/VERSION]
+set zVersion [string trim [read $in]]
+close $in
+set nVersion [eval format "%d%03d%03d" [split $zVersion .]]
+
+# Get the source-id
+#
+set PWD [pwd]
+cd $TOP
+set zSourceId [exec $PWD/mksourceid manifest]
+cd $PWD
+
+# Set up patterns for recognizing API declarations.
+#
+set varpattern {^[a-zA-Z][a-zA-Z_0-9 *]+sqlite3_[_a-zA-Z0-9]+(\[|;| =)}
+set declpattern1 {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3_[_a-zA-Z0-9]+)(\(.*)$}
+
+set declpattern2 \
+ {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3session_[_a-zA-Z0-9]+)(\(.*)$}
+
+set declpattern3 \
+ {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3changeset_[_a-zA-Z0-9]+)(\(.*)$}
+
+set declpattern4 \
+ {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3changegroup_[_a-zA-Z0-9]+)(\(.*)$}
+
+set declpattern5 \
+ {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3rebaser_[_a-zA-Z0-9]+)(\(.*)$}
+
+# Force the output to use unix line endings, even on Windows.
+fconfigure stdout -translation lf
+
+set filelist [subst {
+ $TOP/src/sqlite.h.in
+ $TOP/ext/rtree/sqlite3rtree.h
+ $TOP/ext/session/sqlite3session.h
+ $TOP/ext/fts5/fts5.h
+}]
+if {$enable_recover} {
+ lappend filelist "$TOP/ext/recover/sqlite3recover.h"
+}
+
+# These are the functions that accept a variable number of arguments. They
+# always need to use the "cdecl" calling convention even when another calling
+# convention (e.g. "stcall") is being used for the rest of the library.
+set cdecllist {
+ sqlite3_config
+ sqlite3_db_config
+ sqlite3_log
+ sqlite3_mprintf
+ sqlite3_snprintf
+ sqlite3_test_control
+ sqlite3_vtab_config
+}
+
+# Process the source files.
+#
+foreach file $filelist {
+ set in [open $file]
+ if {![regexp {sqlite\.h\.in} $file]} {
+ puts "/******** Begin file [file tail $file] *********/"
+ }
+ while {![eof $in]} {
+
+ set line [string trimright [gets $in]]
+
+ # File sqlite3rtree.h contains a line "#include <sqlite3.h>". Omit this
+ # line when copying sqlite3rtree.h into sqlite3.h.
+ #
+ if {[string match {*#include*[<"]sqlite3.h[>"]*} $line]} continue
+
+ regsub -- --VERS-- $line $zVersion line
+ regsub -- --VERSION-NUMBER-- $line $nVersion line
+ regsub -- --SOURCE-ID-- $line "$zSourceId" line
+
+ if {[regexp $varpattern $line] && ![regexp {^ *typedef} $line]} {
+ set line "SQLITE_API $line"
+ } else {
+ if {[regexp $declpattern1 $line all rettype funcname rest] || \
+ [regexp $declpattern2 $line all rettype funcname rest] || \
+ [regexp $declpattern3 $line all rettype funcname rest] || \
+ [regexp $declpattern4 $line all rettype funcname rest] || \
+ [regexp $declpattern5 $line all rettype funcname rest]} {
+ set line SQLITE_API
+ append line " " [string trim $rettype]
+ if {[string index $rettype end] ne "*"} {
+ append line " "
+ }
+ if {$useapicall} {
+ if {[lsearch -exact $cdecllist $funcname] >= 0} {
+ append line SQLITE_CDECL " "
+ } else {
+ append line SQLITE_APICALL " "
+ }
+ }
+ append line $funcname $rest
+ }
+ }
+ if {$useapicall} {
+ set line [string map [list (*sqlite3_syscall_ptr) \
+ "(SQLITE_SYSAPI *sqlite3_syscall_ptr)"] $line]
+ regsub {\(\*} $line {(SQLITE_CALLBACK *} line
+ }
+ puts $line
+ }
+ close $in
+ if {![regexp {sqlite\.h\.in} $file]} {
+ puts "/******** End of [file tail $file] *********/"
+ }
+}
diff --git a/tool/mksqlite3internalh.tcl b/tool/mksqlite3internalh.tcl
new file mode 100644
index 0000000..8db593f
--- /dev/null
+++ b/tool/mksqlite3internalh.tcl
@@ -0,0 +1,148 @@
+#!/usr/bin/tclsh
+#
+# To build a single huge source file holding all of SQLite (or at
+# least the core components - the test harness, shell, and TCL
+# interface are omitted.) first do
+#
+# make target_source
+#
+# The make target above moves all of the source code files into
+# a subdirectory named "tsrc". (This script expects to find the files
+# there and will not work if they are not found.) There are a few
+# generated C code files that are also added to the tsrc directory.
+# For example, the "parse.c" and "parse.h" files to implement the
+# the parser are derived from "parse.y" using lemon. And the
+# "keywordhash.h" files is generated by a program named "mkkeywordhash".
+#
+# After the "tsrc" directory has been created and populated, run
+# this script:
+#
+# tclsh mksqlite3c.tcl
+#
+# The amalgamated SQLite code will be written into sqlite3.c
+#
+
+# Begin by reading the "sqlite3.h" header file. Count the number of lines
+# in this file and extract the version number. That information will be
+# needed in order to generate the header of the amalgamation.
+#
+set in [open tsrc/sqlite3.h]
+set cnt 0
+set VERSION ?????
+while {![eof $in]} {
+ set line [gets $in]
+ if {$line=="" && [eof $in]} break
+ incr cnt
+ regexp {#define\s+SQLITE_VERSION\s+"(.*)"} $line all VERSION
+}
+close $in
+
+# Open the output file and write a header comment at the beginning
+# of the file.
+#
+set out [open sqlite3internal.h w]
+set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
+puts $out [subst \
+{/******************************************************************************
+** This file is an amalgamation of many private header files from SQLite
+** version $VERSION.
+*/}]
+
+# These are the header files used by SQLite. The first time any of these
+# files are seen in a #include statement in the C code, include the complete
+# text of the file in-line. The file only needs to be included once.
+#
+foreach hdr {
+ btree.h
+ btreeInt.h
+ hash.h
+ hwtime.h
+ keywordhash.h
+ msvc.h
+ opcodes.h
+ os_common.h
+ os_setup.h
+ os_win.h
+ os.h
+ pager.h
+ parse.h
+ sqlite3ext.h
+ sqlite3.h
+ sqliteInt.h
+ sqliteLimit.h
+ vdbe.h
+ vdbeInt.h
+} {
+ set available_hdr($hdr) 1
+}
+
+# 78 stars used for comment formatting.
+set s78 \
+{*****************************************************************************}
+
+# Insert a comment into the code
+#
+proc section_comment {text} {
+ global out s78
+ set n [string length $text]
+ set nstar [expr {60 - $n}]
+ set stars [string range $s78 0 $nstar]
+ puts $out "/************** $text $stars/"
+}
+
+# Read the source file named $filename and write it into the
+# sqlite3.c output file. If any #include statements are seen,
+# process them approprately.
+#
+proc copy_file {filename} {
+ global seen_hdr available_hdr out
+ set tail [file tail $filename]
+ section_comment "Begin file $tail"
+ set in [open $filename r]
+ while {![eof $in]} {
+ set line [gets $in]
+ if {[regexp {^#\s*include\s+["<]([^">]+)[">]} $line all hdr]} {
+ if {[info exists available_hdr($hdr)]} {
+ if {$available_hdr($hdr)} {
+ section_comment "Include $hdr in the middle of $tail"
+ copy_file tsrc/$hdr
+ section_comment "Continuing where we left off in $tail"
+ }
+ } elseif {![info exists seen_hdr($hdr)]} {
+ set seen_hdr($hdr) 1
+ puts $out $line
+ }
+ } elseif {[regexp {^#ifdef __cplusplus} $line]} {
+ puts $out "#if 0"
+ } elseif {[regexp {^#line} $line]} {
+ # Skip #line directives.
+ } else {
+ puts $out $line
+ }
+ }
+ close $in
+ section_comment "End of $tail"
+}
+
+
+# Process the source files. Process files containing commonly
+# used subroutines first in order to help the compiler find
+# inlining opportunities.
+#
+foreach file {
+ sqliteInt.h
+ sqlite3.h
+ btree.h
+ hash.h
+ os.h
+ pager.h
+ parse.h
+ sqlite3ext.h
+ vdbe.h
+} {
+ if {$available_hdr($file)} {
+ copy_file tsrc/$file
+ }
+}
+
+close $out
diff --git a/tool/mktoolzip.tcl b/tool/mktoolzip.tcl
new file mode 100644
index 0000000..885bae9
--- /dev/null
+++ b/tool/mktoolzip.tcl
@@ -0,0 +1,69 @@
+#!/usr/bin/tclsh
+#
+# Run this script in order to generate a ZIP archive containing various
+# command-line tools.
+#
+# The makefile that invokes this script must first build the following
+# binaries:
+#
+# testfixture -- used to run this script
+# sqlite3 -- the SQLite CLI
+# sqldiff -- Program to diff two databases
+# sqlite3_analyzer -- Space analyzer
+#
+switch $tcl_platform(os) {
+ {Windows NT} {
+ set OS win32
+ set EXE .exe
+ }
+ Linux {
+ set OS linux
+ set EXE {}
+ }
+ Darwin {
+ set OS osx
+ set EXE {}
+ }
+ default {
+ set OS unknown
+ set EXE {}
+ }
+}
+switch $tcl_platform(machine) {
+ arm64 {
+ set ARCH arm64
+ }
+ x86_64 {
+ set ARCH x64
+ }
+ amd64 -
+ intel {
+ if {$tcl_platform(pointerSize)==4} {
+ set ARCH x86
+ } else {
+ set ARCH x64
+ }
+ }
+ default {
+ set ARCH unk
+ }
+}
+set in [open [file join [file dirname [file dirname [info script]]] VERSION]]
+set vers [read $in]
+close $in
+scan $vers %d.%d.%d v1 v2 v3
+set v2 [format 3%02d%02d00 $v2 $v3]
+set name sqlite-tools-$OS-$ARCH-$v2.zip
+
+if {$OS=="win32"} {
+ # The win32 tar.exe supports the -a ("auto-compress") option. This causes
+ # tar to create an archive type based on the extension of the output file.
+ # In this case, a zip file.
+ puts "tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
+ puts [exec tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
+ puts "$name: [file size $name] bytes"
+} else {
+ puts "zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
+ puts [exec zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
+ puts [exec ls -l $name]
+}
diff --git a/tool/mkvsix.tcl b/tool/mkvsix.tcl
new file mode 100644
index 0000000..c874d53
--- /dev/null
+++ b/tool/mkvsix.tcl
@@ -0,0 +1,840 @@
+#!/usr/bin/tclsh
+#
+# This script is used to generate a VSIX (Visual Studio Extension) file for
+# SQLite usable by Visual Studio.
+#
+# PREREQUISITES
+#
+# 1. Tcl 8.4 and later are supported, earlier versions have not been tested.
+#
+# 2. The "sqlite3.h" file is assumed to exist in the parent directory of the
+# directory containing this script. The [optional] second command line
+# argument to this script may be used to specify an alternate location.
+# This script also assumes that the "sqlite3.h" file corresponds with the
+# version of the binaries to be packaged. This assumption is not verified
+# by this script.
+#
+# 3. The temporary directory specified in the TEMP or TMP environment variables
+# must refer to an existing directory writable by the current user.
+#
+# 4. The "zip" and "unzip" command line tools must be located either in a
+# directory contained in the PATH environment variable or specified as the
+# exact file names to execute in the "ZipTool" and "UnZipTool" environment
+# variables, respectively.
+#
+# 5. The template VSIX file (which is basically a zip file) must be located in
+# a "win" directory inside the directory containing this script. It should
+# not contain any executable binaries. It should only contain dynamic
+# textual content files to be processed using [subst] and/or static content
+# files to be copied verbatim.
+#
+# 6. The executable and other compiled binary files to be packaged into the
+# final VSIX file (e.g. DLLs, LIBs, and PDBs) must be located in a single
+# directory tree. The top-level directory of the tree must be specified as
+# the first command line argument to this script. The second level
+# sub-directory names must match those of the build configuration (e.g.
+# "Debug" or "Retail"). The third level sub-directory names must match
+# those of the platform (e.g. "x86", "x64", and "ARM"). For example, the
+# binary files to be packaged would need to be organized as follows when
+# packaging the "Debug" and "Retail" build configurations for the "x86" and
+# "x64" platforms (in this example, "C:\temp" is the top-level directory as
+# specified in the first command line argument):
+#
+# C:\Temp\Debug\x86\sqlite3.lib
+# C:\Temp\Debug\x86\sqlite3.dll
+# C:\Temp\Debug\x86\sqlite3.pdb
+# C:\Temp\Debug\x64\sqlite3.lib
+# C:\Temp\Debug\x64\sqlite3.dll
+# C:\Temp\Debug\x64\sqlite3.pdb
+# C:\Temp\Retail\x86\sqlite3.lib
+# C:\Temp\Retail\x86\sqlite3.dll
+# C:\Temp\Retail\x86\sqlite3.pdb
+# C:\Temp\Retail\x64\sqlite3.lib
+# C:\Temp\Retail\x64\sqlite3.dll
+# C:\Temp\Retail\x64\sqlite3.pdb
+#
+# The above directory tree organization is performed automatically if the
+# "tool\build-all-msvc.bat" batch script is used to build the binary files
+# to be packaged.
+#
+# USAGE
+#
+# The first argument to this script is required and must be the name of the
+# top-level directory containing the directories and files organized into a
+# tree as described in item 6 of the PREREQUISITES section, above. The second
+# argument is optional and if present must contain the name of the directory
+# containing the root of the source tree for SQLite. The third argument is
+# optional and if present must contain the flavor the VSIX package to build.
+# Currently, the only supported package flavors are "WinRT", "WinRT81", "WP80",
+# "WP81", and "Win32". The fourth argument is optional and if present must be
+# a string containing a list of platforms to include in the VSIX package. The
+# platform list is "platform1,platform2,platform3". The fifth argument is
+# optional and if present must contain the version of Visual Studio required by
+# the package. Currently, the only supported versions are "2012" and "2013".
+# The package flavors "WinRT81" and "WP81" are only supported when the Visual
+# Studio version is "2013". Typically, when on Windows, this script is
+# executed using commands similar to the following from a normal Windows
+# command prompt:
+#
+# CD /D C:\dev\sqlite\core
+# tclsh tool\mkvsix.tcl C:\Temp
+#
+# In the example above, "C:\dev\sqlite\core" represents the root of the source
+# tree for SQLite and "C:\Temp" represents the top-level directory containing
+# the executable and other compiled binary files, organized into a directory
+# tree as described in item 6 of the PREREQUISITES section, above.
+#
+# This script should work on non-Windows platforms as well, provided that all
+# the requirements listed in the PREREQUISITES section are met.
+#
+# NOTES
+#
+# The temporary directory is used as a staging area for the final VSIX file.
+# The template VSIX file is extracted, its contents processed, and then the
+# resulting files are packaged into the final VSIX file.
+#
+package require Tcl 8.4
+
+proc fail { {error ""} {usage false} } {
+ if {[string length $error] > 0} then {
+ puts stdout $error
+ if {!$usage} then {exit 1}
+ }
+
+ puts stdout "usage:\
+[file tail [info nameofexecutable]]\
+[file tail [info script]] <binaryDirectory> \[sourceDirectory\]\
+\[packageFlavor\] \[platformNames\] \[vsVersion\]"
+
+ exit 1
+}
+
+proc getEnvironmentVariable { name } {
+ #
+ # NOTE: Returns the value of the specified environment variable or an empty
+ # string for environment variables that do not exist in the current
+ # process environment.
+ #
+ return [expr {[info exists ::env($name)] ? $::env($name) : ""}]
+}
+
+proc getTemporaryPath {} {
+ #
+ # NOTE: Returns the normalized path to the first temporary directory found
+ # in the typical set of environment variables used for that purpose
+ # or an empty string to signal a failure to locate such a directory.
+ #
+ set names [list]
+
+ foreach name [list TEMP TMP] {
+ lappend names [string toupper $name] [string tolower $name] \
+ [string totitle $name]
+ }
+
+ foreach name $names {
+ set value [getEnvironmentVariable $name]
+
+ if {[string length $value] > 0} then {
+ return [file normalize $value]
+ }
+ }
+
+ return ""
+}
+
+proc appendArgs { args } {
+ #
+ # NOTE: Returns all passed arguments joined together as a single string with
+ # no intervening spaces between arguments.
+ #
+ eval append result $args
+}
+
+proc readFile { fileName } {
+ #
+ # NOTE: Reads and returns the entire contents of the specified file, which
+ # may contain binary data.
+ #
+ set file_id [open $fileName RDONLY]
+ fconfigure $file_id -encoding binary -translation binary
+ set result [read $file_id]
+ close $file_id
+ return $result
+}
+
+proc writeFile { fileName data } {
+ #
+ # NOTE: Writes the entire contents of the specified file, which may contain
+ # binary data.
+ #
+ set file_id [open $fileName {WRONLY CREAT TRUNC}]
+ fconfigure $file_id -encoding binary -translation binary
+ puts -nonewline $file_id $data
+ close $file_id
+ return ""
+}
+
+#
+# TODO: Modify this procedure when a new version of Visual Studio is released.
+#
+proc getMinVsVersionXmlChunk { vsVersion } {
+ switch -exact $vsVersion {
+ 2012 {
+ return [appendArgs \
+ "\r\n " {MinVSVersion="11.0"}]
+ }
+ 2013 {
+ return [appendArgs \
+ "\r\n " {MinVSVersion="12.0"}]
+ }
+ 2015 {
+ return [appendArgs \
+ "\r\n " {MinVSVersion="14.0"}]
+ }
+ default {
+ return ""
+ }
+ }
+}
+
+#
+# TODO: Modify this procedure when a new version of Visual Studio is released.
+#
+proc getMaxPlatformVersionXmlChunk { packageFlavor vsVersion } {
+ #
+ # NOTE: Only Visual Studio 2013 and later support this attribute within the
+ # SDK manifest.
+ #
+ if {![string equal $vsVersion 2013] && \
+ ![string equal $vsVersion 2015]} then {
+ return ""
+ }
+
+ switch -exact $packageFlavor {
+ WinRT {
+ return [appendArgs \
+ "\r\n " {MaxPlatformVersion="8.0"}]
+ }
+ WinRT81 {
+ return [appendArgs \
+ "\r\n " {MaxPlatformVersion="8.1"}]
+ }
+ WP80 {
+ return [appendArgs \
+ "\r\n " {MaxPlatformVersion="8.0"}]
+ }
+ WP81 {
+ return [appendArgs \
+ "\r\n " {MaxPlatformVersion="8.1"}]
+ }
+ default {
+ return ""
+ }
+ }
+}
+
+#
+# TODO: Modify this procedure when a new version of Visual Studio is released.
+#
+proc getExtraFileListXmlChunk { packageFlavor vsVersion } {
+ #
+ # NOTE: Windows Phone 8.0 does not require any extra attributes in its VSIX
+ # package SDK manifests; however, it appears that Windows Phone 8.1
+ # does.
+ #
+ if {[string equal $packageFlavor WP80]} then {
+ return ""
+ }
+
+ set appliesTo [expr {[string equal $packageFlavor Win32] ? \
+ "VisualC" : "WindowsAppContainer"}]
+
+ switch -exact $vsVersion {
+ 2012 {
+ return [appendArgs \
+ "\r\n " AppliesTo=\" $appliesTo \" \
+ "\r\n " {DependsOn="Microsoft.VCLibs, version=11.0"}]
+ }
+ 2013 {
+ return [appendArgs \
+ "\r\n " AppliesTo=\" $appliesTo \" \
+ "\r\n " {DependsOn="Microsoft.VCLibs, version=12.0"}]
+ }
+ 2015 {
+ return [appendArgs \
+ "\r\n " AppliesTo=\" $appliesTo \" \
+ "\r\n " {DependsOn="Microsoft.VCLibs, version=14.0"}]
+ }
+ default {
+ return ""
+ }
+ }
+}
+
+proc replaceFileNameTokens { fileName name buildName platformName } {
+ #
+ # NOTE: Returns the specified file name containing the platform name instead
+ # of platform placeholder tokens.
+ #
+ return [string map [list <build> $buildName <platform> $platformName \
+ <name> $name] $fileName]
+}
+
+proc substFile { fileName } {
+ #
+ # NOTE: Performs all Tcl command, variable, and backslash substitutions in
+ # the specified file and then rewrites the contents of that same file
+ # with the substituted data.
+ #
+ return [writeFile $fileName [uplevel 1 [list subst [readFile $fileName]]]]
+}
+
+#
+# NOTE: This is the entry point for this script.
+#
+set script [file normalize [info script]]
+
+if {[string length $script] == 0} then {
+ fail "script file currently being evaluated is unknown" true
+}
+
+set path [file dirname $script]
+set rootName [file rootname [file tail $script]]
+
+###############################################################################
+
+#
+# NOTE: Process and verify all the command line arguments.
+#
+set argc [llength $argv]
+if {$argc < 1 || $argc > 5} then {fail}
+
+set binaryDirectory [lindex $argv 0]
+
+if {[string length $binaryDirectory] == 0} then {
+ fail "invalid binary directory"
+}
+
+if {![file exists $binaryDirectory] || \
+ ![file isdirectory $binaryDirectory]} then {
+ fail "binary directory does not exist"
+}
+
+if {$argc >= 2} then {
+ set sourceDirectory [lindex $argv 1]
+} else {
+ #
+ # NOTE: Assume that the source directory is the parent directory of the one
+ # that contains this script file.
+ #
+ set sourceDirectory [file dirname $path]
+}
+
+if {[string length $sourceDirectory] == 0} then {
+ fail "invalid source directory"
+}
+
+if {![file exists $sourceDirectory] || \
+ ![file isdirectory $sourceDirectory]} then {
+ fail "source directory does not exist"
+}
+
+if {$argc >= 3} then {
+ set packageFlavor [lindex $argv 2]
+} else {
+ #
+ # NOTE: Assume the package flavor is WinRT.
+ #
+ set packageFlavor WinRT
+}
+
+if {[string length $packageFlavor] == 0} then {
+ fail "invalid package flavor"
+}
+
+if {$argc >= 4} then {
+ set platformNames [list]
+
+ foreach platformName [split [lindex $argv 3] ", "] {
+ set platformName [string trim $platformName]
+
+ if {[string length $platformName] > 0} then {
+ lappend platformNames $platformName
+ }
+ }
+}
+
+if {$argc >= 5} then {
+ set vsVersion [lindex $argv 4]
+} else {
+ set vsVersion 2012
+}
+
+if {[string length $vsVersion] == 0} then {
+ fail "invalid Visual Studio version"
+}
+
+if {![string equal $vsVersion 2012] && ![string equal $vsVersion 2013] && \
+ ![string equal $vsVersion 2015]} then {
+ fail [appendArgs \
+ "unsupported Visual Studio version, must be one of: " \
+ [list 2012 2013 2015]]
+}
+
+set shortNames(WinRT,2012) SQLite.WinRT
+set shortNames(WinRT,2013) SQLite.WinRT.2013
+set shortNames(WinRT81,2013) SQLite.WinRT81
+set shortNames(WP80,2012) SQLite.WP80
+set shortNames(WP80,2013) SQLite.WP80.2013
+set shortNames(WP81,2013) SQLite.WP81
+set shortNames(Win32,2012) SQLite.Win32
+set shortNames(Win32,2013) SQLite.Win32.2013
+set shortNames(UWP,2015) SQLite.UWP.2015
+
+set displayNames(WinRT,2012) "SQLite for Windows Runtime"
+set displayNames(WinRT,2013) "SQLite for Windows Runtime"
+set displayNames(WinRT81,2013) "SQLite for Windows Runtime (Windows 8.1)"
+set displayNames(WP80,2012) "SQLite for Windows Phone"
+set displayNames(WP80,2013) "SQLite for Windows Phone"
+set displayNames(WP81,2013) "SQLite for Windows Phone 8.1"
+set displayNames(Win32,2012) "SQLite for Windows"
+set displayNames(Win32,2013) "SQLite for Windows"
+set displayNames(UWP,2015) "SQLite for Universal Windows Platform"
+
+if {[string equal $packageFlavor WinRT]} then {
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier Windows
+ set targetPlatformVersion v8.0
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath ""
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} elseif {[string equal $packageFlavor WinRT81]} then {
+ if {$vsVersion ne "2013"} then {
+ fail [appendArgs \
+ "unsupported combination, package flavor " $packageFlavor \
+ " is only supported with Visual Studio 2013"]
+ }
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier Windows
+ set targetPlatformVersion v8.1
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath ""
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} elseif {[string equal $packageFlavor WP80]} then {
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier "Windows Phone"
+ set targetPlatformVersion v8.0
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath "\\..\\$targetPlatformIdentifier"
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} elseif {[string equal $packageFlavor WP81]} then {
+ if {$vsVersion ne "2013"} then {
+ fail [appendArgs \
+ "unsupported combination, package flavor " $packageFlavor \
+ " is only supported with Visual Studio 2013"]
+ }
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier WindowsPhoneApp
+ set targetPlatformVersion v8.1
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath "\\..\\$targetPlatformIdentifier"
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} elseif {[string equal $packageFlavor UWP]} then {
+ if {$vsVersion ne "2015"} then {
+ fail [appendArgs \
+ "unsupported combination, package flavor " $packageFlavor \
+ " is only supported with Visual Studio 2015"]
+ }
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier UAP; # NOTE: Not "UWP".
+ set targetPlatformVersion v0.8.0.0
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath "\\..\\$targetPlatformIdentifier"
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} elseif {[string equal $packageFlavor Win32]} then {
+ set shortName $shortNames($packageFlavor,$vsVersion)
+ set displayName $displayNames($packageFlavor,$vsVersion)
+ set targetPlatformIdentifier Windows
+ set targetPlatformVersion v8.0
+ set minVsVersion [getMinVsVersionXmlChunk $vsVersion]
+ set maxPlatformVersion \
+ [getMaxPlatformVersionXmlChunk $packageFlavor $vsVersion]
+ set extraSdkPath ""
+ set extraFileListAttributes \
+ [getExtraFileListXmlChunk $packageFlavor $vsVersion]
+} else {
+ fail [appendArgs \
+ "unsupported package flavor, must be one of: " \
+ [list WinRT WinRT81 WP80 WP81 UWP Win32]]
+}
+
+###############################################################################
+
+#
+# NOTE: Evaluate the user-specific customizations file, if it exists.
+#
+set userFile [file join $path [appendArgs \
+ $rootName . $tcl_platform(user) .tcl]]
+
+if {[file exists $userFile] && \
+ [file isfile $userFile]} then {
+ source $userFile
+}
+
+###############################################################################
+
+set templateFile [file join $path win sqlite.vsix]
+
+if {![file exists $templateFile] || \
+ ![file isfile $templateFile]} then {
+ fail [appendArgs "template file \"" $templateFile "\" does not exist"]
+}
+
+set currentDirectory [pwd]
+set outputFile [file join $currentDirectory [appendArgs sqlite- \
+ $packageFlavor -output.vsix]]
+
+if {[file exists $outputFile]} then {
+ fail [appendArgs "output file \"" $outputFile "\" already exists"]
+}
+
+###############################################################################
+
+#
+# NOTE: Make sure that a valid temporary directory exists.
+#
+set temporaryDirectory [getTemporaryPath]
+
+if {[string length $temporaryDirectory] == 0 || \
+ ![file exists $temporaryDirectory] || \
+ ![file isdirectory $temporaryDirectory]} then {
+ fail "cannot locate a usable temporary directory"
+}
+
+#
+# NOTE: Setup the staging directory to have a unique name inside of the
+# configured temporary directory.
+#
+set stagingDirectory [file normalize [file join $temporaryDirectory \
+ [appendArgs $rootName . [pid]]]]
+
+###############################################################################
+
+#
+# NOTE: Configure the external zipping tool. First, see if it has already
+# been pre-configured. If not, try to query it from the environment.
+# Finally, fallback on the default of simply "zip", which will then
+# be assumed to exist somewhere along the PATH.
+#
+if {![info exists zip]} then {
+ if {[info exists env(ZipTool)]} then {
+ set zip $env(ZipTool)
+ }
+ if {![info exists zip] || ![file exists $zip]} then {
+ set zip zip
+ }
+}
+
+#
+# NOTE: Configure the external unzipping tool. First, see if it has already
+# been pre-configured. If not, try to query it from the environment.
+# Finally, fallback on the default of simply "unzip", which will then
+# be assumed to exist somewhere along the PATH.
+#
+if {![info exists unzip]} then {
+ if {[info exists env(UnZipTool)]} then {
+ set unzip $env(UnZipTool)
+ }
+ if {![info exists unzip] || ![file exists $unzip]} then {
+ set unzip unzip
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Attempt to extract the SQLite version from the "sqlite3.h" header file
+# in the source directory. This script assumes that the header file has
+# already been generated by the build process.
+#
+set pattern {^#define\s+SQLITE_VERSION\s+"(.*)"$}
+set data [readFile [file join $sourceDirectory sqlite3.h]]
+
+if {![regexp -line -- $pattern $data dummy version]} then {
+ fail [appendArgs "cannot locate SQLITE_VERSION value in \"" \
+ [file join $sourceDirectory sqlite3.h] \"]
+}
+
+###############################################################################
+
+#
+# NOTE: Setup all the master file list data. This includes the source file
+# names, the destination file names, and the file processing flags. The
+# possible file processing flags are:
+#
+# "buildNeutral" -- This flag indicates the file location and content do
+# not depend on the build configuration.
+#
+# "platformNeutral" -- This flag indicates the file location and content
+# do not depend on the build platform.
+#
+# "subst" -- This flag indicates that the file contains dynamic textual
+# content that needs to be processed using [subst] prior to
+# packaging the file into the final VSIX package. The primary
+# use of this flag is to insert the name of the VSIX package,
+# some package flavor-specific value, or the SQLite version
+# into a file.
+#
+# "noDebug" -- This flag indicates that the file should be skipped when
+# processing the debug build.
+#
+# "noRetail" -- This flag indicates that the file should be skipped when
+# processing the retail build.
+#
+# "move" -- This flag indicates that the file should be moved from the
+# source to the destination instead of being copied.
+#
+# This file metadata may be overridden, either in whole or in part, via
+# the user-specific customizations file.
+#
+if {![info exists fileNames(source)]} then {
+ set fileNames(source) [list "" "" \
+ [file join $stagingDirectory DesignTime <build> <platform> sqlite3.props] \
+ [file join $sourceDirectory sqlite3.h] \
+ [file join $binaryDirectory <build> <platform> sqlite3.lib] \
+ [file join $binaryDirectory <build> <platform> sqlite3.dll]]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(source) \
+ [file join $binaryDirectory <build> <platform> sqlite3.pdb]
+ }
+}
+
+if {![info exists fileNames(destination)]} then {
+ set fileNames(destination) [list \
+ [file join $stagingDirectory extension.vsixmanifest] \
+ [file join $stagingDirectory SDKManifest.xml] \
+ [file join $stagingDirectory DesignTime <build> <platform> <name>.props] \
+ [file join $stagingDirectory DesignTime <build> <platform> sqlite3.h] \
+ [file join $stagingDirectory DesignTime <build> <platform> sqlite3.lib] \
+ [file join $stagingDirectory Redist <build> <platform> sqlite3.dll]]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(destination) \
+ [file join $stagingDirectory Redist <build> <platform> sqlite3.pdb]
+ }
+}
+
+if {![info exists fileNames(flags)]} then {
+ set fileNames(flags) [list \
+ [list buildNeutral platformNeutral subst] \
+ [list buildNeutral platformNeutral subst] \
+ [list buildNeutral platformNeutral subst move] \
+ [list buildNeutral platformNeutral] \
+ [list] [list] [list noRetail]]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(flags) [list noRetail]
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Setup the list of builds supported by this script. These may be
+# overridden via the user-specific customizations file.
+#
+if {![info exists buildNames]} then {
+ set buildNames [list Debug Retail]
+}
+
+###############################################################################
+
+#
+# NOTE: Setup the list of platforms supported by this script. These may be
+# overridden via the command line or the user-specific customizations
+# file.
+#
+if {![info exists platformNames] || [llength $platformNames] == 0} then {
+ set platformNames [list x86 x64 ARM]
+}
+
+###############################################################################
+
+#
+# NOTE: Make sure the staging directory exists, creating it if necessary.
+#
+file mkdir $stagingDirectory
+
+#
+# NOTE: Build the Tcl command used to extract the template VSIX package to
+# the staging directory.
+#
+set extractCommand [list exec -- $unzip $templateFile -d $stagingDirectory]
+
+#
+# NOTE: Extract the template VSIX package to the staging directory.
+#
+eval $extractCommand
+
+###############################################################################
+
+#
+# NOTE: Process each file in the master file list. There are actually three
+# parallel lists that contain the source file names, the destination file
+# names, and the file processing flags. If the "buildNeutral" flag is
+# present, the file location and content do not depend on the build
+# configuration and "CommonConfiguration" will be used in place of the
+# build configuration name. If the "platformNeutral" flag is present,
+# the file location and content do not depend on the build platform and
+# "neutral" will be used in place of the build platform name. If the
+# "subst" flag is present, the file is assumed to be a text file that may
+# contain Tcl variable, command, and backslash replacements, to be
+# dynamically replaced during processing using the Tcl [subst] command.
+# If the "noDebug" flag is present, the file will be skipped when
+# processing for the debug build. If the "noRetail" flag is present, the
+# file will be skipped when processing for the retail build. If the
+# "move" flag is present, the source file will be deleted after it is
+# copied to the destination file. If the source file name is an empty
+# string, the destination file name will be assumed to already exist in
+# the staging directory and will not be copied; however, Tcl variable,
+# command, and backslash replacements may still be performed on the
+# destination file prior to the final VSIX package being built if the
+# "subst" flag is present.
+#
+foreach sourceFileName $fileNames(source) \
+ destinationFileName $fileNames(destination) \
+ fileFlags $fileNames(flags) {
+ #
+ # NOTE: Process the file flags into separate boolean variables that may be
+ # used within the loop.
+ #
+ set isBuildNeutral [expr {[lsearch $fileFlags buildNeutral] != -1}]
+ set isPlatformNeutral [expr {[lsearch $fileFlags platformNeutral] != -1}]
+ set isMove [expr {[lsearch $fileFlags move] != -1}]
+ set useSubst [expr {[lsearch $fileFlags subst] != -1}]
+
+ #
+ # NOTE: If the current file is build-neutral, then only one build will
+ # be processed for it, namely "CommonConfiguration"; otherwise, each
+ # supported build will be processed for it individually.
+ #
+ foreach buildName \
+ [expr {$isBuildNeutral ? [list CommonConfiguration] : $buildNames}] {
+ #
+ # NOTE: Should the current file be skipped for this build?
+ #
+ if {[lsearch $fileFlags no${buildName}] != -1} then {
+ continue
+ }
+
+ #
+ # NOTE: If the current file is platform-neutral, then only one platform
+ # will be processed for it, namely "neutral"; otherwise, each
+ # supported platform will be processed for it individually.
+ #
+ foreach platformName \
+ [expr {$isPlatformNeutral ? [list neutral] : $platformNames}] {
+ #
+ # NOTE: Use the actual platform name in the destination file name.
+ #
+ set newDestinationFileName [replaceFileNameTokens $destinationFileName \
+ $shortName $buildName $platformName]
+
+ #
+ # NOTE: Does the source file need to be copied to the destination file?
+ #
+ if {[string length $sourceFileName] > 0} then {
+ #
+ # NOTE: First, make sure the destination directory exists.
+ #
+ file mkdir [file dirname $newDestinationFileName]
+
+ #
+ # NOTE: Then, copy the source file to the destination file verbatim.
+ #
+ set newSourceFileName [replaceFileNameTokens $sourceFileName \
+ $shortName $buildName $platformName]
+
+ file copy $newSourceFileName $newDestinationFileName
+
+ #
+ # NOTE: If this is a move instead of a copy, delete the source file
+ # now.
+ #
+ if {$isMove} then {
+ file delete $newSourceFileName
+ }
+ }
+
+ #
+ # NOTE: Does the destination file contain dynamic replacements that must
+ # be processed now?
+ #
+ if {$useSubst} then {
+ #
+ # NOTE: Perform any dynamic replacements contained in the destination
+ # file and then re-write it in-place.
+ #
+ substFile $newDestinationFileName
+ }
+ }
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Change the current directory to the staging directory so that the
+# external archive building tool can pickup the necessary files using
+# relative paths.
+#
+cd $stagingDirectory
+
+#
+# NOTE: Build the Tcl command used to archive the final VSIX package in the
+# output directory.
+#
+set archiveCommand [list exec -- $zip -r $outputFile *]
+
+#
+# NOTE: Build the final VSIX package archive in the output directory.
+#
+eval $archiveCommand
+
+#
+# NOTE: Change back to the previously saved current directory.
+#
+cd $currentDirectory
+
+#
+# NOTE: Cleanup the temporary staging directory.
+#
+file delete -force $stagingDirectory
+
+###############################################################################
+
+#
+# NOTE: Success, emit the fully qualified path of the generated VSIX file.
+#
+puts stdout $outputFile
diff --git a/tool/offsets.c b/tool/offsets.c
new file mode 100644
index 0000000..26ee9fc
--- /dev/null
+++ b/tool/offsets.c
@@ -0,0 +1,329 @@
+/*
+** This program searches an SQLite database file for the lengths and
+** offsets for all TEXT or BLOB entries for a particular column of a
+** particular table. The rowid, size and offset for the column are
+** written to standard output. There are three arguments, which are the
+** name of the database file, the table, and the column.
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+typedef unsigned char u8;
+typedef struct GState GState;
+
+#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** Global state information for this program.
+*/
+struct GState {
+ char *zErr; /* Error message text */
+ FILE *f; /* Open database file */
+ int szPg; /* Page size for the database file */
+ int iRoot; /* Root page of the table */
+ int iCol; /* Column number for the column */
+ int pgno; /* Current page number */
+ u8 *aPage; /* Current page content */
+ u8 *aStack[20]; /* Page stack */
+ int aPgno[20]; /* Page number stack */
+ int nStack; /* Depth of stack */
+ int bTrace; /* True for tracing output */
+};
+
+/*
+** Write an error.
+*/
+static void ofstError(GState *p, const char *zFormat, ...){
+ va_list ap;
+ sqlite3_free(p->zErr);
+ va_start(ap, zFormat);
+ p->zErr = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+}
+
+/*
+** Write a trace message
+*/
+static void ofstTrace(GState *p, const char *zFormat, ...){
+ va_list ap;
+ if( p->bTrace ){
+ va_start(ap, zFormat);
+ vprintf(zFormat, ap);
+ va_end(ap);
+ }
+}
+
+/*
+** Find the root page of the table and the column number of the column.
+*/
+static void ofstRootAndColumn(
+ GState *p, /* Global state */
+ const char *zFile, /* Name of the database file */
+ const char *zTable, /* Name of the table */
+ const char *zColumn /* Name of the column */
+){
+ sqlite3 *db = 0;
+ sqlite3_stmt *pStmt = 0;
+ char *zSql = 0;
+ int rc;
+ if( p->zErr ) return;
+ rc = sqlite3_open(zFile, &db);
+ if( rc ){
+ ofstError(p, "cannot open database file \"%s\"", zFile);
+ goto rootAndColumn_exit;
+ }
+ zSql = sqlite3_mprintf("SELECT rootpage FROM sqlite_schema WHERE name=%Q",
+ zTable);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc ) ofstError(p, "%s: [%s]", sqlite3_errmsg(db), zSql);
+ sqlite3_free(zSql);
+ if( p->zErr ) goto rootAndColumn_exit;
+ if( sqlite3_step(pStmt)!=SQLITE_ROW ){
+ ofstError(p, "cannot find table [%s]\n", zTable);
+ sqlite3_finalize(pStmt);
+ goto rootAndColumn_exit;
+ }
+ p->iRoot = sqlite3_column_int(pStmt , 0);
+ sqlite3_finalize(pStmt);
+
+ p->iCol = -1;
+ zSql = sqlite3_mprintf("PRAGMA table_info(%Q)", zTable);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc ) ofstError(p, "%s: [%s}", sqlite3_errmsg(db), zSql);
+ sqlite3_free(zSql);
+ if( p->zErr ) goto rootAndColumn_exit;
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zCol = sqlite3_column_text(pStmt, 1);
+ if( strlen(zCol)==strlen(zColumn)
+ && sqlite3_strnicmp(zCol, zColumn, strlen(zCol))==0
+ ){
+ p->iCol = sqlite3_column_int(pStmt, 0);
+ break;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( p->iCol<0 ){
+ ofstError(p, "no such column: %s.%s", zTable, zColumn);
+ goto rootAndColumn_exit;
+ }
+
+ zSql = sqlite3_mprintf("PRAGMA page_size");
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc ) ofstError(p, "%s: [%s]", sqlite3_errmsg(db), zSql);
+ sqlite3_free(zSql);
+ if( p->zErr ) goto rootAndColumn_exit;
+ if( sqlite3_step(pStmt)!=SQLITE_ROW ){
+ ofstError(p, "cannot find page size");
+ }else{
+ p->szPg = sqlite3_column_int(pStmt, 0);
+ }
+ sqlite3_finalize(pStmt);
+
+rootAndColumn_exit:
+ sqlite3_close(db);
+ return;
+}
+
+/*
+** Pop a page from the stack
+*/
+static void ofstPopPage(GState *p){
+ if( p->nStack<=0 ) return;
+ p->nStack--;
+ sqlite3_free(p->aStack[p->nStack]);
+ p->pgno = p->aPgno[p->nStack-1];
+ p->aPage = p->aStack[p->nStack-1];
+}
+
+
+/*
+** Push a new page onto the stack.
+*/
+static void ofstPushPage(GState *p, int pgno){
+ u8 *pPage;
+ size_t got;
+ if( p->zErr ) return;
+ if( p->nStack >= ArraySize(p->aStack) ){
+ ofstError(p, "page stack overflow");
+ return;
+ }
+ p->aPgno[p->nStack] = pgno;
+ p->aStack[p->nStack] = pPage = sqlite3_malloc( p->szPg );
+ if( pPage==0 ){
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ p->nStack++;
+ p->aPage = pPage;
+ p->pgno = pgno;
+ fseek(p->f, (pgno-1)*p->szPg, SEEK_SET);
+ got = fread(pPage, 1, p->szPg, p->f);
+ if( got!=p->szPg ){
+ ofstError(p, "unable to read page %d", pgno);
+ ofstPopPage(p);
+ }
+}
+
+/* Read a two-byte integer at the given offset into the current page */
+static int ofst2byte(GState *p, int ofst){
+ int x = p->aPage[ofst];
+ return (x<<8) + p->aPage[ofst+1];
+}
+
+/* Read a four-byte integer at the given offset into the current page */
+static int ofst4byte(GState *p, int ofst){
+ int x = p->aPage[ofst];
+ x = (x<<8) + p->aPage[ofst+1];
+ x = (x<<8) + p->aPage[ofst+2];
+ x = (x<<8) + p->aPage[ofst+3];
+ return x;
+}
+
+/* Read a variable-length integer. Update the offset */
+static sqlite3_int64 ofstVarint(GState *p, int *pOfst){
+ sqlite3_int64 x = 0;
+ u8 *a = &p->aPage[*pOfst];
+ int n = 0;
+ while( n<8 && (a[0] & 0x80)!=0 ){
+ x = (x<<7) + (a[0] & 0x7f);
+ n++;
+ a++;
+ }
+ if( n==8 ){
+ x = (x<<8) + a[0];
+ }else{
+ x = (x<<7) + a[0];
+ }
+ *pOfst += (n+1);
+ return x;
+}
+
+/* Return the absolute offset into a file for the given offset
+** into the current page */
+static int ofstInFile(GState *p, int ofst){
+ return p->szPg*(p->pgno-1) + ofst;
+}
+
+/* Return the size (in bytes) of the data corresponding to the
+** given serial code */
+static int ofstSerialSize(int scode){
+ if( scode<5 ) return scode;
+ if( scode==5 ) return 6;
+ if( scode<8 ) return 8;
+ if( scode<12 ) return 0;
+ return (scode-12)/2;
+}
+
+/* Forward reference */
+static void ofstWalkPage(GState*, int);
+
+/* Walk an interior btree page */
+static void ofstWalkInteriorPage(GState *p){
+ int nCell;
+ int i;
+ int ofst;
+ int iChild;
+
+ nCell = ofst2byte(p, 3);
+ for(i=0; i<nCell; i++){
+ ofst = ofst2byte(p, 12+i*2);
+ iChild = ofst4byte(p, ofst);
+ ofstWalkPage(p, iChild);
+ if( p->zErr ) return;
+ }
+ ofstWalkPage(p, ofst4byte(p, 8));
+}
+
+/* Walk a leaf btree page */
+static void ofstWalkLeafPage(GState *p){
+ int nCell;
+ int i;
+ int ofst;
+ int nPayload;
+ sqlite3_int64 rowid;
+ int nHdr;
+ int j;
+ int scode;
+ int sz;
+ int dataOfst;
+ char zMsg[200];
+
+ nCell = ofst2byte(p, 3);
+ for(i=0; i<nCell; i++){
+ ofst = ofst2byte(p, 8+i*2);
+ nPayload = ofstVarint(p, &ofst);
+ rowid = ofstVarint(p, &ofst);
+ if( nPayload > p->szPg-35 ){
+ sqlite3_snprintf(sizeof(zMsg), zMsg,
+ "# overflow rowid %lld", rowid);
+ printf("%s\n", zMsg);
+ continue;
+ }
+ dataOfst = ofst;
+ nHdr = ofstVarint(p, &ofst);
+ dataOfst += nHdr;
+ for(j=0; j<p->iCol; j++){
+ scode = ofstVarint(p, &ofst);
+ dataOfst += ofstSerialSize(scode);
+ }
+ scode = ofstVarint(p, &ofst);
+ sz = ofstSerialSize(scode);
+ sqlite3_snprintf(sizeof(zMsg), zMsg,
+ "rowid %12lld size %5d offset %8d",
+ rowid, sz, ofstInFile(p, dataOfst));
+ printf("%s\n", zMsg);
+ }
+}
+
+/*
+** Output results from a single page.
+*/
+static void ofstWalkPage(GState *p, int pgno){
+ if( p->zErr ) return;
+ ofstPushPage(p, pgno);
+ if( p->zErr ) return;
+ if( p->aPage[0]==5 ){
+ ofstWalkInteriorPage(p);
+ }else if( p->aPage[0]==13 ){
+ ofstWalkLeafPage(p);
+ }else{
+ ofstError(p, "page %d has a faulty type byte: %d", pgno, p->aPage[0]);
+ }
+ ofstPopPage(p);
+}
+
+int main(int argc, char **argv){
+ GState g;
+ memset(&g, 0, sizeof(g));
+ if( argc>2 && strcmp(argv[1],"--trace")==0 ){
+ g.bTrace = 1;
+ argc--;
+ argv++;
+ }
+ if( argc!=4 ){
+ fprintf(stderr, "Usage: %s DATABASE TABLE COLUMN\n", *argv);
+ exit(1);
+ }
+ ofstRootAndColumn(&g, argv[1], argv[2], argv[3]);
+ if( g.zErr ){
+ fprintf(stderr, "%s\n", g.zErr);
+ exit(1);
+ }
+ ofstTrace(&g, "# szPg = %d\n", g.szPg);
+ ofstTrace(&g, "# iRoot = %d\n", g.iRoot);
+ ofstTrace(&g, "# iCol = %d\n", g.iCol);
+ g.f = fopen(argv[1], "rb");
+ if( g.f==0 ){
+ fprintf(stderr, "cannot open \"%s\"\n", argv[1]);
+ exit(1);
+ }
+ ofstWalkPage(&g, g.iRoot);
+ if( g.zErr ){
+ fprintf(stderr, "%s\n", g.zErr);
+ exit(1);
+ }
+ return 0;
+}
diff --git a/tool/omittest-msvc.tcl b/tool/omittest-msvc.tcl
new file mode 100644
index 0000000..28c4b98
--- /dev/null
+++ b/tool/omittest-msvc.tcl
@@ -0,0 +1,99 @@
+# Run this TCL script in order to build using MSVC multiple times
+# with various compile-time options. Use this to verify that the various
+# compile-time options all work with MSVC.
+#
+set OPTIONS [list \
+ SQLITE_ALLOW_ROWID_IN_VIEW \
+ SQLITE_ENABLE_COLUMN_METADATA \
+ SQLITE_ENABLE_EXPENSIVE_ASSERT \
+ SQLITE_ENABLE_IOTRACE \
+ SQLITE_ENABLE_MEMORY_MANAGEMENT \
+ SQLITE_ENABLE_MEMSYS3 \
+ SQLITE_ENABLE_MEMSYS5 \
+ SQLITE_ENABLE_OVERSIZE_CELL_CHECK \
+ SQLITE_ENABLE_UNLOCK_NOTIFY \
+ SQLITE_ENABLE_UPDATE_DELETE_LIMIT \
+ SQLITE_OMIT_ALTERTABLE-x \
+ SQLITE_OMIT_ATTACH-x \
+ SQLITE_OMIT_AUTHORIZATION \
+ SQLITE_OMIT_AUTOINCREMENT \
+ SQLITE_OMIT_AUTOINIT \
+ SQLITE_OMIT_AUTOMATIC_INDEX \
+ SQLITE_OMIT_AUTORESET \
+ SQLITE_OMIT_AUTOVACUUM \
+ SQLITE_OMIT_BETWEEN_OPTIMIZATION \
+ SQLITE_OMIT_BLOB_LITERAL \
+ SQLITE_OMIT_BTREECOUNT \
+ SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA \
+ SQLITE_OMIT_CAST \
+ SQLITE_OMIT_CHECK \
+ SQLITE_OMIT_COMPILEOPTION_DIAGS \
+ SQLITE_OMIT_COMPLETE \
+ SQLITE_OMIT_COMPOUND_SELECT \
+ SQLITE_OMIT_CONFLICT_CLAUSE \
+ SQLITE_OMIT_CTE \
+ SQLITE_OMIT_DATETIME_FUNCS \
+ SQLITE_OMIT_DECLTYPE \
+ SQLITE_OMIT_DEPRECATED \
+ SQLITE_OMIT_DESERIALIZE \
+ SQLITE_OMIT_DISKIO-x \
+ SQLITE_OMIT_EXPLAIN-x \
+ SQLITE_OMIT_FLAG_PRAGMAS \
+ SQLITE_OMIT_FLOATING_POINT \
+ SQLITE_OMIT_FOREIGN_KEY \
+ SQLITE_OMIT_GENERATED_COLUMNS \
+ SQLITE_OMIT_GET_TABLE \
+ SQLITE_OMIT_HEX_INTEGER \
+ SQLITE_OMIT_INCRBLOB-x \
+ SQLITE_OMIT_INTEGRITY_CHECK \
+ SQLITE_OMIT_INTROSPECTION_PRAGMAS \
+ SQLITE_OMIT_JSON \
+ SQLITE_OMIT_LIKE_OPTIMIZATION \
+ SQLITE_OMIT_LOAD_EXTENSION \
+ SQLITE_OMIT_LOCALTIME \
+ SQLITE_OMIT_LOOKASIDE \
+ SQLITE_OMIT_MEMORYDB \
+ SQLITE_OMIT_OR_OPTIMIZATION \
+ SQLITE_OMIT_PAGER_PRAGMAS-x \
+ SQLITE_OMIT_PARSER_TRACE \
+ SQLITE_OMIT_POPEN \
+ SQLITE_OMIT_PRAGMA-x \
+ SQLITE_OMIT_PROGRESS_CALLBACK \
+ SQLITE_OMIT_QUICKBALANCE \
+ SQLITE_OMIT_RANDOMNESS \
+ SQLITE_OMIT_REINDEX-x \
+ SQLITE_OMIT_SCHEMA_PRAGMAS \
+ SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS \
+ SQLITE_OMIT_SHARED_CACHE \
+ SQLITE_OMIT_SHUTDOWN_DIRECTORIES \
+ SQLITE_OMIT_SUBQUERY-x \
+ SQLITE_OMIT_TCL_VARIABLE \
+ SQLITE_OMIT_TEMPDB \
+ SQLITE_OMIT_TEST_CONTROL \
+ SQLITE_OMIT_TRACE \
+ SQLITE_OMIT_TRIGGER \
+ SQLITE_OMIT_TRUNCATE_OPTIMIZATION \
+ SQLITE_OMIT_UPSERT \
+ SQLITE_OMIT_UTF16 \
+ SQLITE_OMIT_VACUUM-x \
+ SQLITE_OMIT_VIEW-x \
+ SQLITE_OMIT_VIRTUALTABLE-x \
+ SQLITE_OMIT_WAL \
+ SQLITE_OMIT_WINDOWFUNC-x \
+ SQLITE_OMIT_WSD \
+ SQLITE_OMIT_XFER_OPT \
+]
+set start [lindex $argv 0]
+foreach opt $OPTIONS {
+ if {[regexp x $opt]} continue
+ if {[string compare $opt $start]<0} continue
+ exec fossil clean -x
+ set cmd "nmake /f Makefile.msc USE_FULLWARN=0 OPTIMIZATIONS=0"
+ append cmd " CCOPTS=-D$opt"
+ puts $cmd
+ set res [catch {eval exec $cmd} outtxt]
+ if {[regexp {sqlite3.c.\d+.: error} $outtxt]} {
+ puts "FAILED:\n$outtxt"
+ exit
+ }
+}
diff --git a/tool/omittest.tcl b/tool/omittest.tcl
new file mode 100644
index 0000000..8862c68
--- /dev/null
+++ b/tool/omittest.tcl
@@ -0,0 +1,370 @@
+# Documentation for this script. This may be output to stderr
+# if the script is invoked incorrectly.
+set ::USAGE_MESSAGE {
+This Tcl script is used to test the various compile time options
+available for omitting code (the SQLITE_OMIT_xxx options). It
+should be invoked as follows:
+
+ <script> ?test-symbol? ?-makefile PATH-TO-MAKEFILE? ?-skip_run?
+
+The default value for ::MAKEFILE is "../Makefile.linux.gcc".
+
+If -skip_run option is given then only the compile part is attempted.
+
+This script builds the testfixture program and runs the SQLite test suite
+once with each SQLITE_OMIT_ option defined and then once with all options
+defined together. Each run is performed in a seperate directory created
+as a sub-directory of the current directory by the script. The output
+of the build is saved in <sub-directory>/build.log. The output of the
+test-suite is saved in <sub-directory>/test.log.
+
+Almost any SQLite makefile (except those generated by configure - see below)
+should work. The following properties are required:
+
+ * The makefile should support the "testfixture" target.
+ * The makefile should support the "test" target.
+ * The makefile should support the variable "OPTS" as a way to pass
+ options from the make command line to lemon and the C compiler.
+
+More precisely, the following two invocations must be supported:
+
+ $::MAKEBIN -f $::MAKEFILE testfixture OPTS="-DSQLITE_OMIT_ALTERTABLE=1"
+ $::MAKEBIN -f $::MAKEFILE test
+
+Makefiles generated by the sqlite configure program cannot be used as
+they do not respect the OPTS variable.
+}
+
+
+# Build a testfixture executable and run quick.test using it. The first
+# parameter is the name of the directory to create and use to run the
+# test in. The second parameter is a list of OMIT symbols to define
+# when doing so. For example:
+#
+# run_quick_test /tmp/testdir {SQLITE_OMIT_TRIGGER SQLITE_OMIT_VIEW}
+#
+#
+proc run_quick_test {dir omit_symbol_list} {
+ # Compile the value of the OPTS Makefile variable.
+ set opts ""
+ if {$::tcl_platform(platform)=="windows"} {
+ append opts "OPTS += -DSQLITE_OS_WIN=1\n"
+ set target "testfixture.exe"
+ } else {
+ append opts "OPTS += -DSQLITE_OS_UNIX=1\n"
+ }
+ foreach sym $omit_symbol_list {
+ append opts "OPTS += -D${sym}=1\n"
+ }
+
+ # Create the directory and do the build. If an error occurs return
+ # early without attempting to run the test suite.
+ file mkdir $dir
+ puts -nonewline "Building $dir..."
+ flush stdout
+ catch {
+ file copy -force ./config.h $dir
+ file copy -force ./libtool $dir
+ }
+ set fd [open $::MAKEFILE]
+ set mkfile [read $fd]
+ close $fd
+ regsub {\ninclude} $mkfile "\n$opts\ninclude" mkfile
+ set fd [open $dir/makefile w]
+ puts $fd $mkfile
+ close $fd
+
+ set rc [catch {
+ exec $::MAKEBIN -C $dir -f makefile clean $::TARGET >& $dir/build.log
+ }]
+ if {$rc} {
+ puts "No good. See $dir/build.log."
+ return
+ } else {
+ puts "Ok"
+ }
+
+ # Create an empty file "$dir/sqlite3". This is to trick the makefile out
+ # of trying to build the sqlite shell. The sqlite shell won't build
+ # with some of the OMIT options (i.e OMIT_COMPLETE).
+ set sqlite3_dummy $dir/sqlite3
+ if {$::tcl_platform(platform)=="windows"} {
+ append sqlite3_dummy ".exe"
+ }
+ if {![file exists $sqlite3_dummy]} {
+ set wr [open $sqlite3_dummy w]
+ puts $wr "dummy"
+ close $wr
+ }
+
+ if {$::SKIP_RUN} {
+ # puts "Skip testing $dir."
+ } else {
+ # Run the test suite.
+ puts -nonewline "Testing $dir..."
+ flush stdout
+ set rc [catch {
+ exec $::MAKEBIN -C $dir -f makefile test >& $dir/test.log
+ }]
+ if {$rc} {
+ puts "No good. See $dir/test.log."
+ } else {
+ puts "Ok"
+ }
+ }
+}
+
+
+# This proc processes the command line options passed to this script.
+# Currently the only option supported is "-makefile", default
+# "../Makefile.linux-gcc". Set the ::MAKEFILE variable to the value of this
+# option.
+#
+proc process_options {argv} {
+ set ::MAKEBIN make ;# Default value
+ if {$::tcl_platform(platform)=="windows"} {
+ set ::MAKEFILE ./Makefile ;# Default value on Windows
+ } else {
+ set ::MAKEFILE ./Makefile.linux-gcc ;# Default value
+ }
+ set ::SKIP_RUN 1 ;# Default to attempt test
+ set ::TARGET testfixture ;# Default thing to build
+
+ for {set i 0} {$i < [llength $argv]} {incr i} {
+ switch -regexp -- [lindex $argv $i] {
+ -{1,2}makefile {
+ incr i
+ set ::MAKEFILE [lindex $argv $i]
+ }
+
+ -{1,2}nmake {
+ set ::MAKEBIN nmake
+ set ::MAKEFILE ./Makefile.msc
+ }
+
+ -{1,2}target {
+ incr i
+ set ::TARGET [lindex $argv $i]
+ }
+
+ -{1,2}skip_run {
+ set ::SKIP_RUN 1
+ }
+ -{1,2}run {
+ set ::SKIP_RUN 0
+ }
+
+ -{1,2}help {
+ puts $::USAGE_MESSAGE
+ exit
+ }
+
+ -.* {
+ puts stderr "Unknown option: [lindex $argv i]"
+ puts stderr $::USAGE_MESSAGE
+ exit 1
+ }
+
+ default {
+ if {[info exists ::SYMBOL]} {
+ puts stderr [string trim $::USAGE_MESSAGE]
+ exit -1
+ }
+ set ::SYMBOL [lindex $argv $i]
+ }
+ }
+ set ::MAKEFILE [file normalize $::MAKEFILE]
+ }
+}
+
+# Main routine.
+#
+
+proc main {argv} {
+ # List of SQLITE_OMIT_XXX symbols supported by SQLite.
+ set ::OMIT_SYMBOLS [list \
+ SQLITE_OMIT_ALTERTABLE \
+ SQLITE_OMIT_ANALYZE \
+ SQLITE_OMIT_ATTACH \
+ SQLITE_OMIT_AUTHORIZATION \
+ SQLITE_OMIT_AUTOINCREMENT \
+ SQLITE_OMIT_AUTOINIT \
+ SQLITE_OMIT_AUTOMATIC_INDEX \
+ SQLITE_OMIT_AUTORESET \
+ SQLITE_OMIT_AUTOVACUUM \
+ SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS \
+ SQLITE_OMIT_BETWEEN_OPTIMIZATION \
+ SQLITE_OMIT_BLOB_LITERAL \
+ SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA \
+ SQLITE_OMIT_CAST \
+ SQLITE_OMIT_CHECK \
+ SQLITE_OMIT_COMPILEOPTION_DIAGS \
+ SQLITE_OMIT_COMPLETE \
+ SQLITE_OMIT_COMPOUND_SELECT \
+ SQLITE_OMIT_CONFLICT_CLAUSE \
+ SQLITE_OMIT_CTE \
+ SQLITE_OMIT_DATETIME_FUNCS \
+ SQLITE_OMIT_DECLTYPE \
+ SQLITE_OMIT_DEPRECATED \
+ SQLITE_OMIT_DESERIALIZE \
+ SQLITE_OMIT_DISKIO \
+ SQLITE_OMIT_EXPLAIN \
+ SQLITE_OMIT_FLAG_PRAGMAS \
+ SQLITE_OMIT_FLOATING_POINT \
+ SQLITE_OMIT_FOREIGN_KEY \
+ SQLITE_OMIT_GENERATED_COLUMNS \
+ SQLITE_OMIT_GET_TABLE \
+ SQLITE_OMIT_HEX_INTEGER \
+ SQLITE_OMIT_INCRBLOB \
+ SQLITE_OMIT_INTEGRITY_CHECK \
+ SQLITE_OMIT_INTROSPECTION_PRAGMAS \
+ SQLITE_OMIT_JSON \
+ SQLITE_OMIT_LIKE_OPTIMIZATION \
+ SQLITE_OMIT_LOAD_EXTENSION \
+ SQLITE_OMIT_LOCALTIME \
+ SQLITE_OMIT_LOOKASIDE \
+ SQLITE_OMIT_MEMORYDB \
+ SQLITE_OMIT_OR_OPTIMIZATION \
+ SQLITE_OMIT_PAGER_PRAGMAS \
+ SQLITE_OMIT_PARSER_TRACE \
+ SQLITE_OMIT_POPEN \
+ SQLITE_OMIT_PRAGMA \
+ SQLITE_OMIT_PROGRESS_CALLBACK \
+ SQLITE_OMIT_QUICKBALANCE \
+ SQLITE_OMIT_RANDOMNESS \
+ SQLITE_OMIT_REINDEX \
+ SQLITE_OMIT_SCHEMA_PRAGMAS \
+ SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS \
+ SQLITE_OMIT_SHARED_CACHE \
+ SQLITE_OMIT_SHUTDOWN_DIRECTORIES \
+ SQLITE_OMIT_SUBQUERY \
+ SQLITE_OMIT_TCL_VARIABLE \
+ SQLITE_OMIT_TEMPDB \
+ SQLITE_OMIT_TEST_CONTROL \
+ SQLITE_OMIT_TRACE \
+ SQLITE_OMIT_TRIGGER \
+ SQLITE_OMIT_TRUNCATE_OPTIMIZATION \
+ SQLITE_OMIT_TWOSIZE_LOOKASIDE \
+ SQLITE_OMIT_UPSERT \
+ SQLITE_OMIT_UTF \
+ SQLITE_OMIT_VACUUM \
+ SQLITE_OMIT_VIEW \
+ SQLITE_OMIT_VIRTUALTABLE \
+ SQLITE_OMIT_WAL \
+ SQLITE_OMIT_WINDOWFUNC \
+ SQLITE_OMIT_WSD \
+ SQLITE_OMIT_XFER_OPT \
+ ]
+
+ set ::ENABLE_SYMBOLS [list \
+ SQLITE_ALLOW_ROWID_IN_VIEW \
+ SQLITE_DISABLE_DIRSYNC \
+ SQLITE_DISABLE_FTS \
+ SQLITE_DISABLE_INTRINSIC \
+ SQLITE_DISABLE_LFS \
+ SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS \
+ SQLITE_DISABLE_SKIPAHEAD_DISTINCT \
+ SQLITE_ENABLE_API_ARMOR \
+ SQLITE_ENABLE_ATOMIC_WRITE \
+ SQLITE_ENABLE_BATCH_ATOMIC_WRITE \
+ SQLITE_ENABLE_BYTECODE_VTAB \
+ SQLITE_ENABLE_CEROD \
+ SQLITE_ENABLE_COLUMN_METADATA \
+ SQLITE_ENABLE_COLUMN_USED_MASK \
+ SQLITE_ENABLE_COMMENTS \
+ SQLITE_ENABLE_CORRUPT_PGNO \
+ SQLITE_ENABLE_COSTMULT \
+ SQLITE_ENABLE_CURSOR_HINTS \
+ SQLITE_ENABLE_DBPAGE_VTAB \
+ SQLITE_ENABLE_DBSTAT_VTAB \
+ SQLITE_ENABLE_EXPENSIVE_ASSERT \
+ SQLITE_ENABLE_EXPLAIN_COMMENTS \
+ SQLITE_ENABLE_FTS \
+ SQLITE_ENABLE_GEOPOLY \
+ SQLITE_ENABLE_HIDDEN_COLUMNS \
+ SQLITE_ENABLE_ICU \
+ SQLITE_ENABLE_ICU_COLLATIONS \
+ SQLITE_ENABLE_INTERNAL_FUNCTIONS \
+ SQLITE_ENABLE_IOTRACE \
+ SQLITE_ENABLE_LOAD_EXTENSION \
+ SQLITE_ENABLE_LOCKING_STYLE \
+ SQLITE_ENABLE_MATH_FUNCTIONS \
+ SQLITE_ENABLE_MEMORY_MANAGEMENT \
+ SQLITE_ENABLE_MEMSYS \
+ SQLITE_ENABLE_MODULE_COMMENTS \
+ SQLITE_ENABLE_MULTIPLEX \
+ SQLITE_ENABLE_MULTITHREADED_CHECKS \
+ SQLITE_ENABLE_NORMALIZE \
+ SQLITE_ENABLE_NULL_TRIM \
+ SQLITE_ENABLE_OFFSET_SQL_FUNC \
+ SQLITE_ENABLE_OVERSIZE_CELL_CHECK \
+ SQLITE_ENABLE_PREUPDATE_HOOK \
+ SQLITE_ENABLE_QPSG \
+ SQLITE_ENABLE_RBU \
+ SQLITE_ENABLE_RTREE \
+ SQLITE_ENABLE_SELECTTRACE \
+ SQLITE_ENABLE_SESSION \
+ SQLITE_ENABLE_SETLK_TIMEOUT \
+ SQLITE_ENABLE_SNAPSHOT \
+ SQLITE_ENABLE_SORTER_MMAP\
+ SQLITE_ENABLE_SORTER_REFERENCE \
+ SQLITE_ENABLE_SORTER_REFERENCES \
+ SQLITE_ENABLE_SQLLOG\
+ SQLITE_ENABLE_STAT \
+ SQLITE_ENABLE_STMT_SCANSTATUS \
+ SQLITE_ENABLE_STMTVTAB \
+ SQLITE_ENABLE_TREETRACE \
+ SQLITE_ENABLE_UNKNOWN_FUNCTION \
+ SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
+ SQLITE_ENABLE_UNLOCK_NOTIFY \
+ SQLITE_ENABLE_UPDATE_DELETE_LIMIT \
+ SQLITE_ENABLE_URI_00_ERROR \
+ SQLITE_ENABLE_VFSTRACE \
+ SQLITE_ENABLE_WHERETRACE \
+ SQLITE_ENABLE_ZIPVFS \
+ ]
+
+ # Process any command line options.
+ process_options $argv
+
+ if {[info exists ::SYMBOL] } {
+ set sym $::SYMBOL
+
+ if {[lsearch $::OMIT_SYMBOLS $sym]<0 && [lsearch $::ENABLE_SYMBOLS $sym]<0} {
+ puts stderr "No such symbol: $sym"
+ exit -1
+ }
+
+ set dirname "test_[regsub -nocase {^x*SQLITE_} $sym {}]"
+ run_quick_test $dirname $sym
+ } else {
+ # First try a test with all OMIT symbols except SQLITE_OMIT_FLOATING_POINT
+ # and SQLITE_OMIT_PRAGMA defined. The former doesn't work (causes segfaults)
+ # and the latter is currently incompatible with the test suite (this should
+ # be fixed, but it will be a lot of work).
+ set allsyms [list]
+ foreach s $::OMIT_SYMBOLS {
+ if {$s!="SQLITE_OMIT_FLOATING_POINT" && $s!="SQLITE_OMIT_PRAGMA"} {
+ lappend allsyms $s
+ }
+ }
+ run_quick_test test_OMIT_EVERYTHING $allsyms
+
+ # Now try one quick.test with each of the OMIT symbols defined. Included
+ # are the OMIT_FLOATING_POINT and OMIT_PRAGMA symbols, even though we
+ # know they will fail. It's good to be reminded of this from time to time.
+ foreach sym $::OMIT_SYMBOLS {
+ set dirname "test_[regsub -nocase {^x*SQLITE_} $sym {}]"
+ run_quick_test $dirname $sym
+ }
+
+ # Try the ENABLE/DISABLE symbols one at a time.
+ # We don't do them all at once since some are conflicting.
+ foreach sym $::ENABLE_SYMBOLS {
+ set dirname "test_[regsub -nocase {^x*SQLITE_} $sym {}]"
+ run_quick_test $dirname $sym
+ }
+ }
+}
+
+main $argv
diff --git a/tool/opcodesum.tcl b/tool/opcodesum.tcl
new file mode 100644
index 0000000..47dff32
--- /dev/null
+++ b/tool/opcodesum.tcl
@@ -0,0 +1,34 @@
+#!/usr/bin/tclsh
+#
+# Run this script, redirecting input from cachegrind output, to compute the
+# number of CPU cycles used by each VDBE opcode.
+#
+# The cachegrind output should be configured so that it reports a single
+# column of Ir at the left margin. Ex:
+#
+# cg_annotation --show=Ir --auto=yes cachegrind.out.* | tclsh opcodesum.tcl
+#
+set currentop x
+set ncycle(x) 0
+while {![eof stdin]} {
+ set line [string map {\173 x \175 x \042 x} [gets stdin]]
+ if {[regexp { \. case OP_.*:} $line]} {
+ regexp {OP_(.+):} $line all currentop
+ set ncycle($currentop) 0
+ } elseif {[lindex $line 1]=="default:"
+ && [regexp {really OP_Noop and OP_Explain} $line]} {
+ break
+ } elseif {[lindex $line 0]!="."} {
+ regsub -all {[^0-9]} [lindex $line 0] {} n
+ if {$n!=""} {incr ncycle($currentop) $n}
+ }
+}
+unset ncycle(x)
+set results {}
+foreach op [lsort [array names ncycle]] {
+ if {$ncycle($op)==0} continue
+ lappend results [list $ncycle($op) $op]
+}
+foreach entry [lsort -index 0 -int -decr $results] {
+ puts [format {%-16s %10d} [lindex $entry 1] [lindex $entry 0]]
+}
diff --git a/tool/pagesig.c b/tool/pagesig.c
new file mode 100644
index 0000000..540c9d7
--- /dev/null
+++ b/tool/pagesig.c
@@ -0,0 +1,92 @@
+/*
+** 2013-10-01
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** Compute hash signatures for every page of a database file. This utility
+** program is useful for analyzing the output logs generated by the
+** ext/misc/vfslog.c extension.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+/*
+** Compute signature for a block of content.
+**
+** For blocks of 16 or fewer bytes, the signature is just a hex dump of
+** the entire block.
+**
+** For blocks of more than 16 bytes, the signature is a hex dump of the
+** first 8 bytes followed by a 64-bit has of the entire block.
+*/
+static void vlogSignature(unsigned char *p, int n, char *zCksum){
+ unsigned int s0 = 0, s1 = 0;
+ unsigned int *pI;
+ int i;
+ if( n<=16 ){
+ for(i=0; i<n; i++) sprintf(zCksum+i*2, "%02x", p[i]);
+ }else{
+ pI = (unsigned int*)p;
+ for(i=0; i<n-7; i+=8){
+ s0 += pI[0] + s1;
+ s1 += pI[1] + s0;
+ pI += 2;
+ }
+ for(i=0; i<8; i++) sprintf(zCksum+i*2, "%02x", p[i]);
+ sprintf(zCksum+i*2, "-%08x%08x", s0, s1);
+ }
+}
+
+/*
+** Open a file. Find its page size. Read each page, and compute and
+** display the page signature.
+*/
+static void computeSigs(const char *zFilename){
+ FILE *in = fopen(zFilename, "rb");
+ unsigned pgsz;
+ size_t got;
+ unsigned n;
+ unsigned char aBuf[50];
+ unsigned char aPage[65536];
+
+ if( in==0 ){
+ fprintf(stderr, "cannot open \"%s\"\n", zFilename);
+ return;
+ }
+ got = fread(aBuf, 1, sizeof(aBuf), in);
+ if( got!=sizeof(aBuf) ){
+ goto endComputeSigs;
+ }
+ pgsz = aBuf[16]*256 + aBuf[17];
+ if( pgsz==1 ) pgsz = 65536;
+ if( (pgsz & (pgsz-1))!=0 ){
+ fprintf(stderr, "invalid page size: %02x%02x\n", aBuf[16], aBuf[17]);
+ goto endComputeSigs;
+ }
+ rewind(in);
+ for(n=1; (got=fread(aPage, 1, pgsz, in))==pgsz; n++){
+ vlogSignature(aPage, pgsz, aBuf);
+ printf("%4d: %s\n", n, aBuf);
+ }
+
+endComputeSigs:
+ fclose(in);
+}
+
+/*
+** Find page signatures for all named files.
+*/
+int main(int argc, char **argv){
+ int i;
+ for(i=1; i<argc; i++) computeSigs(argv[i]);
+ return 0;
+}
diff --git a/tool/replace.tcl b/tool/replace.tcl
new file mode 100644
index 0000000..e87cb92
--- /dev/null
+++ b/tool/replace.tcl
@@ -0,0 +1,23 @@
+#!/usr/bin/tcl
+#
+# Replace string with another string -OR- include
+# only lines successfully modified with a regular
+# expression.
+#
+fconfigure stdout -translation binary -encoding binary
+fconfigure stderr -translation binary -encoding binary
+set mode [string tolower [lindex $argv 0]]
+set from [lindex $argv 1]
+set to [lindex $argv 2]
+if {-1 == [lsearch -exact [list exact regsub include] $mode]} {exit 1}
+if {[string length $from]==0} {exit 2}
+while {![eof stdin]} {
+ set line [gets stdin]
+ if {[eof stdin]} break
+ switch -exact $mode {
+ exact {set line [string map [list $from $to] $line]}
+ regsub {regsub -all -- $from $line $to line}
+ include {if {[regsub -all -- $from $line $to line]==0} continue}
+ }
+ puts stdout $line
+}
diff --git a/tool/restore_jrnl.tcl b/tool/restore_jrnl.tcl
new file mode 100644
index 0000000..05af4f9
--- /dev/null
+++ b/tool/restore_jrnl.tcl
@@ -0,0 +1,233 @@
+# 2010 January 7
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements utility functions for SQLite library.
+#
+# This file attempts to restore the header of a journal.
+# This may be useful for rolling-back the last committed
+# transaction from a recovered journal.
+#
+
+package require sqlite3
+
+set parm_error 0
+set fix_chksums 0
+set dump_pages 0
+set db_name ""
+
+for {set i 0} {$i<$argc} {incr i} {
+ if {[lindex $argv $i] == "-fix_chksums"} {
+ set fix_chksums -1
+ } elseif {[lindex $argv $i] == "-dump_pages"} {
+ set dump_pages -1
+ } elseif {$db_name == ""} {
+ set db_name [lindex $argv $i]
+ set jrnl_name $db_name-journal
+ } else {
+ set parm_error -1
+ }
+}
+if {$parm_error || $db_name == ""} {
+ puts "USAGE: restore_jrnl.tcl \[-fix_chksums\] \[-dump_pages\] db_name"
+ puts "Example: restore_jrnl.tcl foo.sqlite"
+ return
+}
+
+# is there a way to determine this?
+set sectsz 512
+
+# Copy file $from into $to
+#
+proc copy_file {from to} {
+ file copy -force $from $to
+}
+
+# Execute some SQL
+#
+proc catchsql {sql} {
+ set rc [catch {uplevel [list db eval $sql]} msg]
+ list $rc $msg
+}
+
+# Perform a test
+#
+proc do_test {name cmd expected} {
+ puts -nonewline "$name ..."
+ set res [uplevel $cmd]
+ if {$res eq $expected} {
+ puts Ok
+ } else {
+ puts Error
+ puts " Got: $res"
+ puts " Expected: $expected"
+ }
+}
+
+# Calc checksum nonce from journal page data.
+#
+proc calc_nonce {jrnl_pgno} {
+ global sectsz
+ global db_pgsz
+ global jrnl_name
+ set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
+ set nonce [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
+ for {set i [expr $db_pgsz-200]} {$i>0} {set i [expr $i-200]} {
+ set byte [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
+ set nonce [expr $nonce-$byte]
+ }
+ return $nonce
+}
+
+# Calc checksum from journal page data.
+#
+proc calc_chksum {jrnl_pgno} {
+ global sectsz
+ global db_pgsz
+ global jrnl_name
+ global nonce
+ set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
+ set chksum $nonce
+ for {set i [expr $db_pgsz-200]} {$i>0} {set i [expr $i-200]} {
+ set byte [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
+ set chksum [expr $chksum+$byte]
+ }
+ return $chksum
+}
+
+# Print journal page data in hex dump form
+#
+proc dump_jrnl_page {jrnl_pgno} {
+ global sectsz
+ global db_pgsz
+ global jrnl_name
+
+ # print a header block for the page
+ puts [string repeat "-" 79]
+ set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
+ set db_pgno [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset] 4]]
+ set chksum [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
+ set nonce [calc_nonce $jrnl_pgno]
+ puts [ format {jrnl_pg_offset: %08x (%d) jrnl_pgno: %d db_pgno: %d} \
+ $jrnl_pg_offset $jrnl_pg_offset \
+ $jrnl_pgno $db_pgno]
+ puts [ format {nonce: %08x chksum: %08x} \
+ $nonce $chksum]
+
+ # now hex dump the data
+ # This is derived from the Tcler's WIKI
+ set fid [open $jrnl_name r]
+ fconfigure $fid -translation binary -encoding binary
+ seek $fid [expr $jrnl_pg_offset+4]
+ set data [read $fid $db_pgsz]
+ close $fid
+ for {set addr 0} {$addr<$db_pgsz} {set addr [expr $addr+16]} {
+ # get 16 bytes of data
+ set s [string range $data $addr [expr $addr+16]]
+
+ # Convert the data to hex and to characters.
+ binary scan $s H*@0a* hex ascii
+
+ # Replace non-printing characters in the data.
+ regsub -all -- {[^[:graph:] ]} $ascii {.} ascii
+
+ # Split the 16 bytes into two 8-byte chunks
+ regexp -- {(.{16})(.{0,16})} $hex -> hex1 hex2
+
+ # Convert the hex to pairs of hex digits
+ regsub -all -- {..} $hex1 {& } hex1
+ regsub -all -- {..} $hex2 {& } hex2
+
+ # Print the hex and ascii data
+ puts [ format {%08x %-24s %-24s %-16s} \
+ $addr $hex1 $hex2 $ascii ]
+ }
+}
+
+# Setup for the tests. Make a backup copy of the files.
+#
+if [file exist $db_name.org] {
+ puts "ERROR: during back-up: $db_name.org exists already."
+ return;
+}
+if [file exist $jrnl_name.org] {
+ puts "ERROR: during back-up: $jrnl_name.org exists already."
+ return
+}
+copy_file $db_name $db_name.org
+copy_file $jrnl_name $jrnl_name.org
+
+set db_fsize [file size $db_name]
+set db_pgsz [hexio_get_int [hexio_read $db_name 16 2]]
+set db_npage [expr {$db_fsize / $db_pgsz}]
+
+set jrnl_fsize [file size $jrnl_name]
+set jrnl_npage [expr {($jrnl_fsize - $sectsz) / (4 + $db_pgsz + 4)}]
+
+# calculate checksum nonce for first page
+set nonce [calc_nonce 0]
+
+# verify all the pages in the journal use the same nonce
+for {set i 1} {$i<$jrnl_npage} {incr i} {
+ set tnonce [calc_nonce $i]
+ if {$tnonce != $nonce} {
+ puts "WARNING: different nonces: 0=$nonce $i=$tnonce"
+ if {$fix_chksums } {
+ set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$i)]
+ set tchksum [calc_chksum $i]
+ hexio_write $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] [format %08x $tchksum]
+ puts "INFO: fixing chksum: $i=$tchksum"
+ }
+ }
+}
+
+# verify all the page numbers in the journal
+for {set i 0} {$i<$jrnl_npage} {incr i} {
+ set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$i)]
+ set db_pgno [hexio_get_int [hexio_read $jrnl_name $jrnl_pg_offset 4]]
+ if {$db_pgno < 1} {
+ puts "WARNING: page number < 1: $i=$db_pgno"
+ }
+ if {$db_pgno >= $db_npage} {
+ puts "WARNING: page number >= $db_npage: $i=$db_pgno"
+ }
+}
+
+# dump page data
+if {$dump_pages} {
+ for {set i 0} {$i<$jrnl_npage} {incr i} {
+ dump_jrnl_page $i
+ }
+}
+
+# write the 8 byte magic string
+hexio_write $jrnl_name 0 d9d505f920a163d7
+
+# write -1 for number of records
+hexio_write $jrnl_name 8 ffffffff
+
+# write 00 for checksum nonce
+hexio_write $jrnl_name 12 [format %08x $nonce]
+
+# write page count
+hexio_write $jrnl_name 16 [format %08x $db_npage]
+
+# write sector size
+hexio_write $jrnl_name 20 [format %08x $sectsz]
+
+# write page size
+hexio_write $jrnl_name 24 [format %08x $db_pgsz]
+
+# check the integrity of the database with the patched journal
+sqlite3 db $db_name
+do_test restore_jrnl-1.0 {
+ catchsql {PRAGMA integrity_check}
+} {0 ok}
+db close
+
diff --git a/tool/rollback-test.c b/tool/rollback-test.c
new file mode 100644
index 0000000..915d9d2
--- /dev/null
+++ b/tool/rollback-test.c
@@ -0,0 +1,155 @@
+/*
+** This program is used to generate and verify databases with hot journals.
+** Use this program to generate a hot journal on one machine and verify
+** that it rolls back correctly on another machine with a different
+** architecture.
+**
+** Usage:
+**
+** rollback-test new [-utf8] [-utf16le] [-utf16be] [-pagesize=N] DATABASE
+** rollback-test check DATABASE
+** rollback-test crash [-wal] [-rollback] DATABASE
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sqlite3.h"
+
+static void usage(char *argv0){
+ fprintf(stderr,
+ "Usage: %s new [-utf8] [-utf16le] [-utf16be] [-pagesize=N] DATABASE\n"
+ " %s check DATABASE\n"
+ " %s crash [-wal] DATABASE\n",
+ argv0, argv0, argv0
+ );
+ exit(1);
+}
+
+static sqlite3 *openDb(const char *zFilename){
+ int rc;
+ sqlite3 *db;
+ rc = sqlite3_open(zFilename, &db);
+ if( rc ){
+ fprintf(stderr, "Cannot open \"%s\": %s\n",
+ zFilename, sqlite3_errmsg(db));
+ sqlite3_close(db);
+ exit(1);
+ }
+ return db;
+}
+
+static int nReply = 0;
+static char zReply[1000];
+
+static int execCallback(void *NotUsed, int nArg, char **azArg, char **azCol){
+ int i, n;
+ char *z;
+ for(i=0; i<nArg; i++){
+ z = azArg[i];
+ if( z==0 ) z = "NULL";
+ if( nReply>0 && nReply<sizeof(zReply)-1 ) zReply[nReply++] = ' ';
+ n = strlen(z);
+ if( nReply+n>=sizeof(zReply)-1 ) n = sizeof(zReply) - nReply - 1;
+ memcpy(&zReply[nReply], z, n);
+ nReply += n;
+ zReply[nReply] = 0;
+ }
+ return 0;
+}
+
+static void runSql(sqlite3 *db, const char *zSql){
+ char *zErr = 0;
+ int rc;
+ nReply = 0;
+ rc = sqlite3_exec(db, zSql, execCallback, 0, &zErr);
+ if( zErr ){
+ fprintf(stderr, "SQL error: %s\n", zErr);
+ exit(1);
+ }
+ if( rc ){
+ fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+}
+
+int main(int argc, char **argv){
+ sqlite3 *db;
+ int i;
+
+ if( argc<3 ) usage(argv[0]);
+ if( strcmp(argv[1], "new")==0 ){
+ db = openDb(argv[argc-1]);
+ for(i=2; i<argc-1; i++){
+ if( strcmp(argv[i],"-utf8")==0 ){
+ runSql(db, "PRAGMA encoding=UTF8");
+ }else if( strcmp(argv[i], "-utf16le")==0 ){
+ runSql(db, "PRAGMA encoding=UTF16LE");
+ }else if( strcmp(argv[i], "-utf16be")==0 ){
+ runSql(db, "PRAGMA encoding=UTF16BE");
+ }else if( strncmp(argv[i], "-pagesize=", 10)==0 ){
+ int szPg = atoi(&argv[i][10]);
+ char zBuf[100];
+ sprintf(zBuf, "PRAGMA pagesize=%d", szPg);
+ runSql(db, zBuf);
+ }else{
+ fprintf(stderr, "unknown option %s\n", argv[i]);
+ usage(argv[0]);
+ }
+ }
+ runSql(db,
+ "BEGIN;"
+ "CREATE TABLE t1(x INTEGER PRIMARY KEY, y);"
+ "INSERT INTO t1(y) VALUES('abcdefghijklmnopqrstuvwxyz');"
+ "INSERT INTO t1(y) VALUES('abcdefghijklmnopqrstuvwxyz');"
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 4 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 8 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 16 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 32 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 64 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 128 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 256 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 512 */
+ "INSERT INTO t1(y) SELECT y FROM t1;" /* 1024 */
+ "UPDATE t1 SET y=(y || x);"
+ "CREATE INDEX t1y ON t1(y);"
+ "COMMIT;"
+ );
+ sqlite3_close(db);
+ }else if( strcmp(argv[1], "check")==0 ){
+ db = openDb(argv[argc-1]);
+ runSql(db, "PRAGMA integrity_check");
+ if( strcmp(zReply, "ok")!=0 ){
+ fprintf(stderr, "Integrity check: %s\n", zReply);
+ exit(1);
+ }
+ runSql(db,
+ "SELECT count(*) FROM t1 WHERE y<>('abcdefghijklmnopqrstuvwxyz' || x)"
+ );
+ if( strcmp(zReply, "0")!=0 ){
+ fprintf(stderr, "Wrong content\n");
+ exit(1);
+ }
+ printf("Ok\n");
+ }else if( strcmp(argv[1], "crash")==0 ){
+ db = openDb(argv[argc-1]);
+ for(i=2; i<argc-1; i++){
+ if( strcmp(argv[i],"-wal")==0 ){
+ runSql(db, "PRAGMA journal_mode=WAL");
+ }else if( strcmp(argv[i], "-rollback")==0 ){
+ runSql(db, "PRAGMA journal_mode=DELETE");
+ }else{
+ fprintf(stderr, "unknown option %s\n", argv[i]);
+ usage(argv[0]);
+ }
+ }
+ runSql(db,
+ "PRAGMA cache_size=10;"
+ "BEGIN;"
+ "UPDATE t1 SET y=(y || -x)"
+ );
+ exit(0);
+ }else{
+ usage(argv[0]);
+ }
+ return 0;
+}
diff --git a/tool/run-speed-test.sh b/tool/run-speed-test.sh
new file mode 100644
index 0000000..0e970ea
--- /dev/null
+++ b/tool/run-speed-test.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+#
+# This is a template for a script used for day-to-day size and
+# performance monitoring of SQLite. Typical usage:
+#
+# sh run-speed-test.sh trunk # Baseline measurement of trunk
+# sh run-speed-test.sh x1 # Measure some experimental change
+# fossil test-diff --tk cout-trunk.txt cout-x1.txt # View chanages
+#
+# There are multiple output files, all with a base name given by
+# the first argument:
+#
+# summary-$BASE.txt # Copy of standard output
+# cout-$BASE.txt # cachegrind output
+# explain-$BASE.txt # EXPLAIN listings (only with --explain)
+#
+if test "$1" = ""
+then
+ echo "Usage: $0 OUTPUTFILE [OPTIONS]"
+ exit
+fi
+NAME=$1
+shift
+CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5"
+SPEEDTEST_OPTS="--shrink-memory --reprepare --heap 10000000 64"
+SIZE=5
+doExplain=0
+while test "$1" != ""; do
+ case $1 in
+ --reprepare)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --autovacuum)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --utf16be)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --stats)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --without-rowid)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --nomemstat)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --wal)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal"
+ ;;
+ --size)
+ shift; SIZE=$1
+ ;;
+ --explain)
+ doExplain=1
+ ;;
+ --heap)
+ CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_MEMSYS5"
+ shift;
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --heap $1 64"
+ ;;
+ *)
+ CC_OPTS="$CC_OPTS $1"
+ ;;
+ esac
+ shift
+done
+SPEEDTEST_OPTS="$SPEEDTEST_OPTS --size $SIZE"
+echo "NAME = $NAME" | tee summary-$NAME.txt
+echo "SPEEDTEST_OPTS = $SPEEDTEST_OPTS" | tee -a summary-$NAME.txt
+echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt
+rm -f cachegrind.out.* speedtest1 speedtest1.db sqlite3.o
+gcc -g -Os -Wall -I. $CC_OPTS -c sqlite3.c
+size sqlite3.o | tee -a summary-$NAME.txt
+if test $doExplain -eq 1; then
+ gcc -g -Os -Wall -I. $CC_OPTS \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ ./shell.c ./sqlite3.c -o sqlite3 -ldl -lpthread
+fi
+SRC=./speedtest1.c
+gcc -g -Os -Wall -I. $CC_OPTS $SRC ./sqlite3.o -o speedtest1 -ldl -lpthread
+ls -l speedtest1 | tee -a summary-$NAME.txt
+valgrind --tool=cachegrind ./speedtest1 speedtest1.db \
+ $SPEEDTEST_OPTS 2>&1 | tee -a summary-$NAME.txt
+size sqlite3.o | tee -a summary-$NAME.txt
+wc sqlite3.c
+cg_anno.tcl cachegrind.out.* >cout-$NAME.txt
+if test $doExplain -eq 1; then
+ ./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt
+fi
diff --git a/tool/showdb.c b/tool/showdb.c
new file mode 100644
index 0000000..1b80c7f
--- /dev/null
+++ b/tool/showdb.c
@@ -0,0 +1,1251 @@
+/*
+** A utility for printing all or part of an SQLite database file.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+#define ISPRINT(X) isprint((unsigned char)(X))
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if !defined(_MSC_VER)
+#include <unistd.h>
+#else
+#include <io.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "sqlite3.h"
+
+typedef unsigned char u8; /* unsigned 8-bit */
+typedef unsigned int u32; /* unsigned 32-bit */
+typedef sqlite3_int64 i64; /* signed 64-bit */
+typedef sqlite3_uint64 u64; /* unsigned 64-bit */
+
+
+static struct GlobalData {
+ u32 pagesize; /* Size of a database page */
+ int dbfd; /* File descriptor for reading the DB */
+ u32 mxPage; /* Last page number */
+ int perLine; /* HEX elements to print per line */
+ int bRaw; /* True to access db file via OS APIs */
+ sqlite3_file *pFd; /* File descriptor for non-raw mode */
+ sqlite3 *pDb; /* Database handle that owns pFd */
+} g = {1024, -1, 0, 16, 0, 0, 0};
+
+/*
+** Convert the var-int format into i64. Return the number of bytes
+** in the var-int. Write the var-int value into *pVal.
+*/
+static int decodeVarint(const unsigned char *z, i64 *pVal){
+ i64 v = 0;
+ int i;
+ for(i=0; i<8; i++){
+ v = (v<<7) + (z[i]&0x7f);
+ if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
+ }
+ v = (v<<8) + (z[i]&0xff);
+ *pVal = v;
+ return 9;
+}
+
+/*
+** Extract a big-endian 32-bit integer
+*/
+static u32 decodeInt32(const u8 *z){
+ return (z[0]<<24) + (z[1]<<16) + (z[2]<<8) + z[3];
+}
+
+/* Report an out-of-memory error and die.
+*/
+static void out_of_memory(void){
+ fprintf(stderr,"Out of memory...\n");
+ exit(1);
+}
+
+/*
+** Open a database connection.
+*/
+static sqlite3 *openDatabase(const char *zPrg, const char *zName){
+ sqlite3 *db = 0;
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI;
+ int rc = sqlite3_open_v2(zName, &db, flags, 0);
+ if( rc!=SQLITE_OK ){
+ const char *zErr = sqlite3_errmsg(db);
+ fprintf(stderr, "%s: can't open %s (%s)\n", zPrg, zName, zErr);
+ sqlite3_close(db);
+ exit(1);
+ }
+ return db;
+}
+
+/**************************************************************************
+** Beginning of low-level file access functions.
+**
+** All low-level access to the database file read by this program is
+** performed using the following four functions:
+**
+** fileOpen() - open the db file
+** fileClose() - close the db file
+** fileRead() - read raw data from the db file
+** fileGetsize() - return the size of the db file in bytes
+*/
+
+/*
+** Open the database file.
+*/
+static void fileOpen(const char *zPrg, const char *zName){
+ assert( g.dbfd<0 );
+ if( g.bRaw==0 ){
+ int rc;
+ void *pArg = (void *)(&g.pFd);
+ g.pDb = openDatabase(zPrg, zName);
+ rc = sqlite3_file_control(g.pDb, "main", SQLITE_FCNTL_FILE_POINTER, pArg);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr,
+ "%s: failed to obtain fd for %s (SQLite too old?)\n", zPrg, zName
+ );
+ exit(1);
+ }
+ }else{
+ g.dbfd = open(zName, O_RDONLY);
+ if( g.dbfd<0 ){
+ fprintf(stderr,"%s: can't open %s\n", zPrg, zName);
+ exit(1);
+ }
+ }
+}
+
+/*
+** Close the database file opened by fileOpen()
+*/
+static void fileClose(){
+ if( g.bRaw==0 ){
+ sqlite3_close(g.pDb);
+ g.pDb = 0;
+ g.pFd = 0;
+ }else{
+ close(g.dbfd);
+ g.dbfd = -1;
+ }
+}
+
+/*
+** Read content from the file.
+**
+** Space to hold the content is obtained from sqlite3_malloc() and needs
+** to be freed by the caller.
+*/
+static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){
+ unsigned char *aData;
+ int got;
+ aData = sqlite3_malloc64(32+(i64)nByte);
+ if( aData==0 ) out_of_memory();
+ memset(aData, 0, nByte+32);
+ if( g.bRaw==0 ){
+ int rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst);
+ if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
+ fprintf(stderr, "error in xRead() - %d\n", rc);
+ exit(1);
+ }
+ }else{
+ lseek(g.dbfd, (long)ofst, SEEK_SET);
+ got = read(g.dbfd, aData, nByte);
+ if( got>0 && got<nByte ) memset(aData+got, 0, nByte-got);
+ }
+ return aData;
+}
+
+/*
+** Return the size of the file in byte.
+*/
+static i64 fileGetsize(void){
+ i64 res = 0;
+ if( g.bRaw==0 ){
+ int rc = g.pFd->pMethods->xFileSize(g.pFd, &res);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr, "error in xFileSize() - %d\n", rc);
+ exit(1);
+ }
+ }else{
+ struct stat sbuf;
+ fstat(g.dbfd, &sbuf);
+ res = (sqlite3_int64)(sbuf.st_size);
+ }
+ return res;
+}
+
+/*
+** End of low-level file access functions.
+**************************************************************************/
+
+/*
+** Print a range of bytes as hex and as ascii.
+*/
+static unsigned char *print_byte_range(
+ sqlite3_int64 ofst, /* First byte in the range of bytes to print */
+ int nByte, /* Number of bytes to print */
+ int printOfst /* Add this amount to the index on the left column */
+){
+ unsigned char *aData;
+ int i, j;
+ const char *zOfstFmt;
+
+ if( ((printOfst+nByte)&~0xfff)==0 ){
+ zOfstFmt = " %03x: ";
+ }else if( ((printOfst+nByte)&~0xffff)==0 ){
+ zOfstFmt = " %04x: ";
+ }else if( ((printOfst+nByte)&~0xfffff)==0 ){
+ zOfstFmt = " %05x: ";
+ }else if( ((printOfst+nByte)&~0xffffff)==0 ){
+ zOfstFmt = " %06x: ";
+ }else{
+ zOfstFmt = " %08x: ";
+ }
+
+ aData = fileRead(ofst, nByte);
+ for(i=0; i<nByte; i += g.perLine){
+ int go = 0;
+ for(j=0; j<g.perLine; j++){
+ if( i+j>nByte ){ break; }
+ if( aData[i+j] ){ go = 1; break; }
+ }
+ if( !go && i>0 && i+g.perLine<nByte ) continue;
+ fprintf(stdout, zOfstFmt, i+printOfst);
+ for(j=0; j<g.perLine; j++){
+ if( i+j>nByte ){
+ fprintf(stdout, " ");
+ }else{
+ fprintf(stdout,"%02x ", aData[i+j]);
+ }
+ }
+ for(j=0; j<g.perLine; j++){
+ if( i+j>nByte ){
+ fprintf(stdout, " ");
+ }else{
+ fprintf(stdout,"%c", ISPRINT(aData[i+j]) ? aData[i+j] : '.');
+ }
+ }
+ fprintf(stdout,"\n");
+ }
+ return aData;
+}
+
+/*
+** Print an entire page of content as hex
+*/
+static void print_page(u32 iPg){
+ i64 iStart;
+ unsigned char *aData;
+ iStart = ((i64)(iPg-1))*g.pagesize;
+ fprintf(stdout, "Page %u: (offsets 0x%llx..0x%llx)\n",
+ iPg, iStart, iStart+g.pagesize-1);
+ aData = print_byte_range(iStart, g.pagesize, 0);
+ sqlite3_free(aData);
+}
+
+
+/* Print a line of decoded output showing a 4-byte unsigned integer.
+*/
+static void print_decode_line(
+ unsigned char *aData, /* Content being decoded */
+ int ofst, int nByte, /* Start and size of decode */
+ const char *zMsg /* Message to append */
+){
+ int i, j;
+ u32 val = aData[ofst];
+ char zBuf[100];
+ sprintf(zBuf, " %03x: %02x", ofst, aData[ofst]);
+ i = (int)strlen(zBuf);
+ for(j=1; j<4; j++){
+ if( j>=nByte ){
+ sprintf(&zBuf[i], " ");
+ }else{
+ sprintf(&zBuf[i], " %02x", aData[ofst+j]);
+ val = val*256 + aData[ofst+j];
+ }
+ i += (int)strlen(&zBuf[i]);
+ }
+ sprintf(&zBuf[i], " %10u", val);
+ printf("%s %s\n", zBuf, zMsg);
+}
+
+/*
+** Decode the database header.
+*/
+static void print_db_header(void){
+ unsigned char *aData;
+ aData = print_byte_range(0, 100, 0);
+ printf("Decoded:\n");
+ print_decode_line(aData, 16, 2, "Database page size");
+ print_decode_line(aData, 18, 1, "File format write version");
+ print_decode_line(aData, 19, 1, "File format read version");
+ print_decode_line(aData, 20, 1, "Reserved space at end of page");
+ print_decode_line(aData, 24, 4, "File change counter");
+ print_decode_line(aData, 28, 4, "Size of database in pages");
+ print_decode_line(aData, 32, 4, "Page number of first freelist page");
+ print_decode_line(aData, 36, 4, "Number of freelist pages");
+ print_decode_line(aData, 40, 4, "Schema cookie");
+ print_decode_line(aData, 44, 4, "Schema format version");
+ print_decode_line(aData, 48, 4, "Default page cache size");
+ print_decode_line(aData, 52, 4, "Largest auto-vac root page");
+ print_decode_line(aData, 56, 4, "Text encoding");
+ print_decode_line(aData, 60, 4, "User version");
+ print_decode_line(aData, 64, 4, "Incremental-vacuum mode");
+ print_decode_line(aData, 68, 4, "Application ID");
+ print_decode_line(aData, 72, 4, "meta[8]");
+ print_decode_line(aData, 76, 4, "meta[9]");
+ print_decode_line(aData, 80, 4, "meta[10]");
+ print_decode_line(aData, 84, 4, "meta[11]");
+ print_decode_line(aData, 88, 4, "meta[12]");
+ print_decode_line(aData, 92, 4, "Change counter for version number");
+ print_decode_line(aData, 96, 4, "SQLite version number");
+ sqlite3_free(aData);
+}
+
+/*
+** Describe cell content.
+*/
+static i64 describeContent(
+ unsigned char *a, /* Cell content */
+ i64 nLocal, /* Bytes in a[] */
+ char *zDesc /* Write description here */
+){
+ i64 nDesc = 0;
+ int n, j;
+ i64 i, x, v;
+ const unsigned char *pData;
+ const unsigned char *pLimit;
+ char sep = ' ';
+
+ pLimit = &a[nLocal];
+ n = decodeVarint(a, &x);
+ pData = &a[x];
+ a += n;
+ i = x - n;
+ while( i>0 && pData<=pLimit ){
+ n = decodeVarint(a, &x);
+ a += n;
+ i -= n;
+ nLocal -= n;
+ zDesc[0] = sep;
+ sep = ',';
+ nDesc++;
+ zDesc++;
+ if( x==0 ){
+ sprintf(zDesc, "*"); /* NULL is a "*" */
+ }else if( x>=1 && x<=6 ){
+ v = (signed char)pData[0];
+ pData++;
+ switch( x ){
+ case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 4: v = (v<<8) + pData[0]; pData++;
+ case 3: v = (v<<8) + pData[0]; pData++;
+ case 2: v = (v<<8) + pData[0]; pData++;
+ }
+ sprintf(zDesc, "%lld", v);
+ }else if( x==7 ){
+ sprintf(zDesc, "real");
+ pData += 8;
+ }else if( x==8 ){
+ sprintf(zDesc, "0");
+ }else if( x==9 ){
+ sprintf(zDesc, "1");
+ }else if( x>=12 ){
+ i64 size = (x-12)/2;
+ if( (x&1)==0 ){
+ sprintf(zDesc, "blob(%lld)", size);
+ }else{
+ sprintf(zDesc, "txt(%lld)", size);
+ }
+ pData += size;
+ }
+ j = (int)strlen(zDesc);
+ zDesc += j;
+ nDesc += j;
+ }
+ return nDesc;
+}
+
+/*
+** Compute the local payload size given the total payload size and
+** the page size.
+*/
+static i64 localPayload(i64 nPayload, char cType){
+ i64 maxLocal;
+ i64 minLocal;
+ i64 surplus;
+ i64 nLocal;
+ if( cType==13 ){
+ /* Table leaf */
+ maxLocal = g.pagesize-35;
+ minLocal = (g.pagesize-12)*32/255-23;
+ }else{
+ maxLocal = (g.pagesize-12)*64/255-23;
+ minLocal = (g.pagesize-12)*32/255-23;
+ }
+ if( nPayload>maxLocal ){
+ surplus = minLocal + (nPayload-minLocal)%(g.pagesize-4);
+ if( surplus<=maxLocal ){
+ nLocal = surplus;
+ }else{
+ nLocal = minLocal;
+ }
+ }else{
+ nLocal = nPayload;
+ }
+ return nLocal;
+}
+
+
+/*
+** Create a description for a single cell.
+**
+** The return value is the local cell size.
+*/
+static i64 describeCell(
+ unsigned char cType, /* Page type */
+ unsigned char *a, /* Cell content */
+ int showCellContent, /* Show cell content if true */
+ char **pzDesc /* Store description here */
+){
+ int i;
+ i64 nDesc = 0;
+ int n = 0;
+ u32 leftChild;
+ i64 nPayload;
+ i64 rowid;
+ i64 nLocal;
+ static char zDesc[1000];
+ i = 0;
+ if( cType<=5 ){
+ leftChild = ((a[0]*256 + a[1])*256 + a[2])*256 + a[3];
+ a += 4;
+ n += 4;
+ sprintf(zDesc, "lx: %u ", leftChild);
+ nDesc = strlen(zDesc);
+ }
+ if( cType!=5 ){
+ i = decodeVarint(a, &nPayload);
+ a += i;
+ n += i;
+ sprintf(&zDesc[nDesc], "n: %lld ", nPayload);
+ nDesc += strlen(&zDesc[nDesc]);
+ nLocal = localPayload(nPayload, cType);
+ }else{
+ nPayload = nLocal = 0;
+ }
+ if( cType==5 || cType==13 ){
+ i = decodeVarint(a, &rowid);
+ a += i;
+ n += i;
+ sprintf(&zDesc[nDesc], "r: %lld ", rowid);
+ nDesc += strlen(&zDesc[nDesc]);
+ }
+ if( nLocal<nPayload ){
+ u32 ovfl;
+ unsigned char *b = &a[nLocal];
+ ovfl = ((b[0]*256 + b[1])*256 + b[2])*256 + b[3];
+ sprintf(&zDesc[nDesc], "ov: %u ", ovfl);
+ nDesc += strlen(&zDesc[nDesc]);
+ n += 4;
+ }
+ if( showCellContent && cType!=5 ){
+ nDesc += describeContent(a, nLocal, &zDesc[nDesc-1]);
+ }
+ *pzDesc = zDesc;
+ return nLocal+n;
+}
+
+/* Print an offset followed by nByte bytes. Add extra white-space
+** at the end so that subsequent text is aligned.
+*/
+static void printBytes(
+ unsigned char *aData, /* Content being decoded */
+ unsigned char *aStart, /* Start of content to be printed */
+ int nByte /* Number of bytes to print */
+){
+ int j;
+ printf(" %03x: ", (int)(aStart-aData));
+ for(j=0; j<9; j++){
+ if( j>=nByte ){
+ printf(" ");
+ }else{
+ printf("%02x ", aStart[j]);
+ }
+ }
+}
+
+
+/*
+** Write a full decode on stdout for the cell at a[ofst].
+** Assume the page contains a header of size szPgHdr bytes.
+*/
+static void decodeCell(
+ unsigned char *a, /* Page content (without the page-1 header) */
+ unsigned pgno, /* Page number */
+ int iCell, /* Cell index */
+ int szPgHdr, /* Size of the page header. 0 or 100 */
+ int ofst /* Cell begins at a[ofst] */
+){
+ int i, j = 0;
+ u32 leftChild;
+ i64 k;
+ i64 nPayload;
+ i64 rowid;
+ i64 nHdr;
+ i64 iType;
+ i64 nLocal;
+ unsigned char *x = a + ofst;
+ unsigned char *end;
+ unsigned char cType = a[0];
+ int nCol = 0;
+ int szCol[2000];
+ int ofstCol[2000];
+ int typeCol[2000];
+
+ printf("Cell[%d]:\n", iCell);
+ if( cType<=5 ){
+ leftChild = ((x[0]*256 + x[1])*256 + x[2])*256 + x[3];
+ printBytes(a, x, 4);
+ printf("left child page:: %u\n", leftChild);
+ x += 4;
+ }
+ if( cType!=5 ){
+ i = decodeVarint(x, &nPayload);
+ printBytes(a, x, i);
+ nLocal = localPayload(nPayload, cType);
+ if( nLocal==nPayload ){
+ printf("payload-size: %lld\n", nPayload);
+ }else{
+ printf("payload-size: %lld (%lld local, %lld overflow)\n",
+ nPayload, nLocal, nPayload-nLocal);
+ }
+ x += i;
+ }else{
+ nPayload = nLocal = 0;
+ }
+ end = x + nLocal;
+ if( cType==5 || cType==13 ){
+ i = decodeVarint(x, &rowid);
+ printBytes(a, x, i);
+ printf("rowid: %lld\n", rowid);
+ x += i;
+ }
+ if( nLocal>0 ){
+ i = decodeVarint(x, &nHdr);
+ printBytes(a, x, i);
+ printf("record-header-size: %d\n", (int)nHdr);
+ j = i;
+ nCol = 0;
+ k = nHdr;
+ while( x+j<=end && j<nHdr ){
+ const char *zTypeName;
+ int sz = 0;
+ char zNm[30];
+ i = decodeVarint(x+j, &iType);
+ printBytes(a, x+j, i);
+ printf("typecode[%d]: %d - ", nCol, (int)iType);
+ switch( iType ){
+ case 0: zTypeName = "NULL"; sz = 0; break;
+ case 1: zTypeName = "int8"; sz = 1; break;
+ case 2: zTypeName = "int16"; sz = 2; break;
+ case 3: zTypeName = "int24"; sz = 3; break;
+ case 4: zTypeName = "int32"; sz = 4; break;
+ case 5: zTypeName = "int48"; sz = 6; break;
+ case 6: zTypeName = "int64"; sz = 8; break;
+ case 7: zTypeName = "double"; sz = 8; break;
+ case 8: zTypeName = "zero"; sz = 0; break;
+ case 9: zTypeName = "one"; sz = 0; break;
+ case 10:
+ case 11: zTypeName = "error"; sz = 0; break;
+ default: {
+ sz = (int)(iType-12)/2;
+ sprintf(zNm, (iType&1)==0 ? "blob(%d)" : "text(%d)", sz);
+ zTypeName = zNm;
+ break;
+ }
+ }
+ printf("%s\n", zTypeName);
+ szCol[nCol] = sz;
+ ofstCol[nCol] = (int)k;
+ typeCol[nCol] = (int)iType;
+ k += sz;
+ nCol++;
+ j += i;
+ }
+ for(i=0; i<nCol && ofstCol[i]+szCol[i]<=nLocal; i++){
+ int s = ofstCol[i];
+ i64 v;
+ const unsigned char *pData;
+ if( szCol[i]==0 ) continue;
+ printBytes(a, x+s, szCol[i]);
+ printf("data[%d]: ", i);
+ pData = x+s;
+ if( typeCol[i]<=7 ){
+ v = (signed char)pData[0];
+ for(k=1; k<szCol[i]; k++){
+ v = (v<<8) + pData[k];
+ }
+ if( typeCol[i]==7 ){
+ double r;
+ memcpy(&r, &v, sizeof(r));
+ printf("%#g\n", r);
+ }else{
+ printf("%lld\n", v);
+ }
+ }else{
+ int ii, jj;
+ char zConst[32];
+ if( (typeCol[i]&1)==0 ){
+ zConst[0] = 'x';
+ zConst[1] = '\'';
+ for(ii=2, jj=0; jj<szCol[i] && ii<24; jj++, ii+=2){
+ sprintf(zConst+ii, "%02x", pData[jj]);
+ }
+ }else{
+ zConst[0] = '\'';
+ for(ii=1, jj=0; jj<szCol[i] && ii<24; jj++, ii++){
+ zConst[ii] = ISPRINT(pData[jj]) ? pData[jj] : '.';
+ }
+ zConst[ii] = 0;
+ }
+ if( jj<szCol[i] ){
+ memcpy(zConst+ii, "...'", 5);
+ }else{
+ memcpy(zConst+ii, "'", 2);
+ }
+ printf("%s\n", zConst);
+ }
+ j = ofstCol[i] + szCol[i];
+ }
+ }
+ if( j<nLocal ){
+ printBytes(a, x+j, 0);
+ printf("... %lld bytes of content ...\n", nLocal-j);
+ }
+ if( nLocal<nPayload ){
+ printBytes(a, x+nLocal, 4);
+ printf("overflow-page: %u\n", decodeInt32(x+nLocal));
+ }
+}
+
+
+/*
+** Decode a btree page
+*/
+static void decode_btree_page(
+ unsigned char *a, /* Page content */
+ int pgno, /* Page number */
+ int hdrSize, /* Size of the page header. 0 or 100 */
+ char *zArgs /* Flags to control formatting */
+){
+ const char *zType = "unknown";
+ int nCell;
+ int i, j;
+ int iCellPtr;
+ int showCellContent = 0;
+ int showMap = 0;
+ int cellToDecode = -2;
+ char *zMap = 0;
+ switch( a[0] ){
+ case 2: zType = "index interior node"; break;
+ case 5: zType = "table interior node"; break;
+ case 10: zType = "index leaf"; break;
+ case 13: zType = "table leaf"; break;
+ }
+ while( zArgs[0] ){
+ switch( zArgs[0] ){
+ case 'c': showCellContent = 1; break;
+ case 'm': showMap = 1; break;
+ case 'd': {
+ if( !ISDIGIT(zArgs[1]) ){
+ cellToDecode = -1;
+ }else{
+ cellToDecode = 0;
+ while( ISDIGIT(zArgs[1]) ){
+ zArgs++;
+ cellToDecode = cellToDecode*10 + zArgs[0] - '0';
+ }
+ }
+ break;
+ }
+ }
+ zArgs++;
+ }
+ nCell = a[3]*256 + a[4];
+ iCellPtr = (a[0]==2 || a[0]==5) ? 12 : 8;
+ if( cellToDecode>=nCell ){
+ printf("Page %d has only %d cells\n", pgno, nCell);
+ return;
+ }
+ printf("Header on btree page %d:\n", pgno);
+ print_decode_line(a, 0, 1, zType);
+ print_decode_line(a, 1, 2, "Offset to first freeblock");
+ print_decode_line(a, 3, 2, "Number of cells on this page");
+ print_decode_line(a, 5, 2, "Offset to cell content area");
+ print_decode_line(a, 7, 1, "Fragmented byte count");
+ if( a[0]==2 || a[0]==5 ){
+ print_decode_line(a, 8, 4, "Right child");
+ }
+ if( cellToDecode==(-2) && nCell>0 ){
+ printf(" key: lx=left-child n=payload-size r=rowid\n");
+ }
+ if( showMap ){
+ zMap = sqlite3_malloc(g.pagesize);
+ memset(zMap, '.', g.pagesize);
+ memset(zMap, '1', hdrSize);
+ memset(&zMap[hdrSize], 'H', iCellPtr);
+ memset(&zMap[hdrSize+iCellPtr], 'P', 2*nCell);
+ }
+ for(i=0; i<nCell; i++){
+ int cofst = iCellPtr + i*2;
+ char *zDesc;
+ i64 n;
+
+ cofst = a[cofst]*256 + a[cofst+1];
+ n = describeCell(a[0], &a[cofst-hdrSize], showCellContent, &zDesc);
+ if( showMap ){
+ char zBuf[30];
+ memset(&zMap[cofst], '*', (size_t)n);
+ zMap[cofst] = '[';
+ zMap[cofst+n-1] = ']';
+ sprintf(zBuf, "%d", i);
+ j = (int)strlen(zBuf);
+ if( j<=n-2 ) memcpy(&zMap[cofst+1], zBuf, j);
+ }
+ if( cellToDecode==(-2) ){
+ printf(" %03x: cell[%d] %s\n", cofst, i, zDesc);
+ }else if( cellToDecode==(-1) || cellToDecode==i ){
+ decodeCell(a, pgno, i, hdrSize, cofst-hdrSize);
+ }
+ }
+ if( showMap ){
+ printf("Page map: (H=header P=cell-index 1=page-1-header .=free-space)\n");
+ for(i=0; (u32)i<g.pagesize; i+=64){
+ printf(" %03x: %.64s\n", i, &zMap[i]);
+ }
+ sqlite3_free(zMap);
+ }
+}
+
+/*
+** Decode a freelist trunk page.
+*/
+static void decode_trunk_page(
+ u32 pgno, /* The page number */
+ int detail, /* Show leaf pages if true */
+ int recursive /* Follow the trunk change if true */
+){
+ u32 i;
+ u32 n;
+ unsigned char *a;
+ while( pgno>0 ){
+ a = fileRead((pgno-1)*g.pagesize, g.pagesize);
+ printf("Decode of freelist trunk page %d:\n", pgno);
+ print_decode_line(a, 0, 4, "Next freelist trunk page");
+ print_decode_line(a, 4, 4, "Number of entries on this page");
+ if( detail ){
+ n = decodeInt32(&a[4]);
+ for(i=0; i<n && i<g.pagesize/4; i++){
+ u32 x = decodeInt32(&a[8+4*i]);
+ char zIdx[13];
+ sprintf(zIdx, "[%d]", i);
+ printf(" %5s %7u", zIdx, x);
+ if( i%5==4 ) printf("\n");
+ }
+ if( i%5!=0 ) printf("\n");
+ }
+ if( !recursive ){
+ pgno = 0;
+ }else{
+ pgno = decodeInt32(&a[0]);
+ }
+ sqlite3_free(a);
+ }
+}
+
+/*
+** A short text comment on the use of each page.
+*/
+static char **zPageUse;
+
+/*
+** Add a comment on the use of a page.
+*/
+static void page_usage_msg(u32 pgno, const char *zFormat, ...){
+ va_list ap;
+ char *zMsg;
+
+ va_start(ap, zFormat);
+ zMsg = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+ if( pgno<=0 || pgno>g.mxPage ){
+ printf("ERROR: page %d out of range 1..%u: %s\n",
+ pgno, g.mxPage, zMsg);
+ sqlite3_free(zMsg);
+ return;
+ }
+ if( zPageUse[pgno]!=0 ){
+ printf("ERROR: page %d used multiple times:\n", pgno);
+ printf("ERROR: previous: %s\n", zPageUse[pgno]);
+ printf("ERROR: current: %s\n", zMsg);
+ sqlite3_free(zPageUse[pgno]);
+ }
+ zPageUse[pgno] = zMsg;
+}
+
+/*
+** Find overflow pages of a cell and describe their usage.
+*/
+static void page_usage_cell(
+ unsigned char cType, /* Page type */
+ unsigned char *a, /* Cell content */
+ u32 pgno, /* page containing the cell */
+ int cellno /* Index of the cell on the page */
+){
+ int i;
+ int n = 0;
+ i64 nPayload;
+ i64 rowid;
+ i64 nLocal;
+ i = 0;
+ if( cType<=5 ){
+ a += 4;
+ n += 4;
+ }
+ if( cType!=5 ){
+ i = decodeVarint(a, &nPayload);
+ a += i;
+ n += i;
+ nLocal = localPayload(nPayload, cType);
+ }else{
+ nPayload = nLocal = 0;
+ }
+ if( cType==5 || cType==13 ){
+ i = decodeVarint(a, &rowid);
+ a += i;
+ n += i;
+ }
+ if( nLocal<nPayload ){
+ u32 ovfl = decodeInt32(a+nLocal);
+ u32 cnt = 0;
+ while( ovfl && (cnt++)<g.mxPage ){
+ page_usage_msg(ovfl, "overflow %d from cell %d of page %u",
+ cnt, cellno, pgno);
+ a = fileRead((ovfl-1)*(sqlite3_int64)g.pagesize, 4);
+ ovfl = decodeInt32(a);
+ sqlite3_free(a);
+ }
+ }
+}
+
+/*
+** True if the memory is all zeros
+*/
+static int allZero(unsigned char *a, int n){
+ while( n && (a++)[0]==0 ){ n--; }
+ return n==0;
+}
+
+
+/*
+** Describe the usages of a b-tree page.
+**
+** If parent==0, then this is the root of a btree. If parent<0 then
+** this is an orphan page.
+*/
+static void page_usage_btree(
+ u32 pgno, /* Page to describe */
+ int parent, /* Parent of this page. 0 for root pages */
+ int idx, /* Which child of the parent */
+ const char *zName /* Name of the table */
+){
+ unsigned char *a;
+ const char *zType = "corrupt node";
+ int nCell;
+ int i;
+ int hdr = pgno==1 ? 100 : 0;
+ char zEntry[30];
+
+ if( pgno<=0 || pgno>g.mxPage ) return;
+ a = fileRead((pgno-1)*g.pagesize, g.pagesize);
+ switch( a[hdr] ){
+ case 0: {
+ if( allZero(a, g.pagesize) ){
+ zType = "zeroed page";
+ }else if( parent<0 ){
+ return;
+ }else{
+ zType = "corrupt node";
+ }
+ break;
+ }
+ case 2: zType = "interior node of index"; break;
+ case 5: zType = "interior node of table"; break;
+ case 10: zType = "leaf of index"; break;
+ case 13: zType = "leaf of table"; break;
+ default: {
+ if( parent<0 ) return;
+ zType = "corrupt node";
+ }
+ }
+ nCell = a[hdr+3]*256 + a[hdr+4];
+ if( nCell==1 ){
+ sqlite3_snprintf(sizeof(zEntry),zEntry,"1 row");
+ }else{
+ sqlite3_snprintf(sizeof(zEntry),zEntry,"%d rows", nCell);
+ }
+ if( parent>0 ){
+ page_usage_msg(pgno, "%s [%s], child %d of page %d, %s",
+ zType, zName, idx, parent, zEntry);
+ }else if( parent==0 ){
+ page_usage_msg(pgno, "root %s [%s], %s", zType, zName, zEntry);
+ }else{
+ page_usage_msg(pgno, "orphaned %s, %s", zType, zEntry);
+ }
+ if( a[hdr]==2 || a[hdr]==5 ){
+ int cellstart = hdr+12;
+ u32 child;
+ for(i=0; i<nCell; i++){
+ u32 cellidx;
+ u32 ofst;
+
+ cellidx = cellstart + i*2;
+ if( cellidx+1 >= g.pagesize ){
+ printf("ERROR: page %d too many cells (%d)\n", pgno, nCell);
+ break;
+ }
+ ofst = a[cellidx]*256 + a[cellidx+1];
+ if( ofst<cellidx+2 || ofst+4>=g.pagesize ){
+ printf("ERROR: page %d cell %d out of bounds\n", pgno, i);
+ continue;
+ }
+ child = decodeInt32(a+ofst);
+ page_usage_btree(child, pgno, i, zName);
+ }
+ child = decodeInt32(a+cellstart-4);
+ page_usage_btree(child, pgno, i, zName);
+ }
+ if( a[hdr]==2 || a[hdr]==10 || a[hdr]==13 ){
+ int cellstart = hdr + 8 + 4*(a[hdr]<=5);
+ for(i=0; i<nCell; i++){
+ int ofst;
+ ofst = cellstart + i*2;
+ ofst = a[ofst]*256 + a[ofst+1];
+ page_usage_cell(a[hdr], a+ofst, pgno, i);
+ }
+ }
+ sqlite3_free(a);
+}
+
+/*
+** Determine page usage by the freelist
+*/
+static void page_usage_freelist(u32 pgno){
+ unsigned char *a;
+ int cnt = 0;
+ int i;
+ int n;
+ int iNext;
+ int parent = 1;
+
+ while( pgno>0 && pgno<=g.mxPage && (u32)(cnt++)<g.mxPage ){
+ page_usage_msg(pgno, "freelist trunk #%d child of %d", cnt, parent);
+ a = fileRead((pgno-1)*g.pagesize, g.pagesize);
+ iNext = decodeInt32(a);
+ n = decodeInt32(a+4);
+ if( n>(g.pagesize - 8)/4 ){
+ printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n);
+ n = (g.pagesize - 8)/4;
+ }
+ for(i=0; i<n; i++){
+ int child = decodeInt32(a + (i*4+8));
+ page_usage_msg(child, "freelist leaf, child %d of trunk page %d",
+ i, pgno);
+ }
+ sqlite3_free(a);
+ parent = pgno;
+ pgno = iNext;
+ }
+}
+
+/*
+** Determine pages used as PTRMAP pages
+*/
+static void page_usage_ptrmap(u8 *a){
+ if( decodeInt32(a+52) ){
+ int usable = g.pagesize - a[20];
+ u64 pgno = 2;
+ int perPage = usable/5;
+ while( pgno<=g.mxPage ){
+ page_usage_msg((u32)pgno, "PTRMAP page covering %llu..%llu",
+ pgno+1, pgno+perPage);
+ pgno += perPage + 1;
+ }
+ }
+}
+
+/*
+** Try to figure out how every page in the database file is being used.
+*/
+static void page_usage_report(const char *zPrg, const char *zDbName){
+ u32 i, j;
+ int rc;
+ sqlite3 *db;
+ sqlite3_stmt *pStmt;
+ unsigned char *a;
+ char zQuery[200];
+
+ /* Avoid the pathological case */
+ if( g.mxPage<1 ){
+ printf("empty database\n");
+ return;
+ }
+
+ /* Open the database file */
+ db = openDatabase(zPrg, zDbName);
+
+ /* Set up global variables zPageUse[] and g.mxPage to record page
+ ** usages */
+ zPageUse = sqlite3_malloc64( sizeof(zPageUse[0])*(g.mxPage+1) );
+ if( zPageUse==0 ) out_of_memory();
+ memset(zPageUse, 0, sizeof(zPageUse[0])*(g.mxPage+1));
+
+ /* Discover the usage of each page */
+ a = fileRead(0, 100);
+ page_usage_freelist(decodeInt32(a+32));
+ page_usage_ptrmap(a);
+ sqlite3_free(a);
+ page_usage_btree(1, 0, 0, "sqlite_schema");
+ sqlite3_exec(db, "PRAGMA writable_schema=ON", 0, 0, 0);
+ for(j=0; j<2; j++){
+ sqlite3_snprintf(sizeof(zQuery), zQuery,
+ "SELECT type, name, rootpage FROM SQLITE_MASTER WHERE rootpage"
+ " ORDER BY rowid %s", j?"DESC":"");
+ rc = sqlite3_prepare_v2(db, zQuery, -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ u32 pgno = (u32)sqlite3_column_int64(pStmt, 2);
+ page_usage_btree(pgno, 0, 0, (const char*)sqlite3_column_text(pStmt,1));
+ }
+ }else{
+ printf("ERROR: cannot query database: %s\n", sqlite3_errmsg(db));
+ }
+ rc = sqlite3_finalize(pStmt);
+ if( rc==SQLITE_OK ) break;
+ }
+ sqlite3_close(db);
+
+ /* Print the report and free memory used */
+ for(i=1; i<=g.mxPage; i++){
+ if( zPageUse[i]==0 ) page_usage_btree(i, -1, 0, 0);
+ printf("%5u: %s\n", i, zPageUse[i] ? zPageUse[i] : "???");
+ }
+ for(i=1; i<=g.mxPage; i++){
+ sqlite3_free(zPageUse[i]);
+ }
+ sqlite3_free(zPageUse);
+ zPageUse = 0;
+}
+
+/*
+** Try to figure out how every page in the database file is being used.
+*/
+static void ptrmap_coverage_report(const char *zDbName){
+ u64 pgno;
+ unsigned char *aHdr;
+ unsigned char *a;
+ int usable;
+ int perPage;
+ int i;
+
+ /* Avoid the pathological case */
+ if( g.mxPage<1 ){
+ printf("empty database\n");
+ return;
+ }
+
+ /* Make sure PTRMAPs are used in this database */
+ aHdr = fileRead(0, 100);
+ if( aHdr[55]==0 ){
+ printf("database does not use PTRMAP pages\n");
+ return;
+ }
+ usable = g.pagesize - aHdr[20];
+ perPage = usable/5;
+ sqlite3_free(aHdr);
+ printf("%5d: root of sqlite_schema\n", 1);
+ for(pgno=2; pgno<=g.mxPage; pgno += perPage+1){
+ printf("%5llu: PTRMAP page covering %llu..%llu\n", pgno,
+ pgno+1, pgno+perPage);
+ a = fileRead((pgno-1)*g.pagesize, usable);
+ for(i=0; i+5<=usable && pgno+1+i/5<=g.mxPage; i+=5){
+ const char *zType = "???";
+ u32 iFrom = decodeInt32(&a[i+1]);
+ switch( a[i] ){
+ case 1: zType = "b-tree root page"; break;
+ case 2: zType = "freelist page"; break;
+ case 3: zType = "first page of overflow"; break;
+ case 4: zType = "later page of overflow"; break;
+ case 5: zType = "b-tree non-root page"; break;
+ }
+ printf("%5llu: %s, parent=%u\n", pgno+1+i/5, zType, iFrom);
+ }
+ sqlite3_free(a);
+ }
+}
+
+/*
+** Check the range validity for a page number. Print an error and
+** exit if the page is out of range.
+*/
+static void checkPageValidity(int iPage){
+ if( iPage<1 || iPage>g.mxPage ){
+ fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n",
+ iPage, g.mxPage);
+ exit(1);
+ }
+}
+
+/*
+** Print a usage comment
+*/
+static void usage(const char *argv0){
+ fprintf(stderr, "Usage %s ?--uri? FILENAME ?args...?\n\n", argv0);
+ fprintf(stderr,
+ "switches:\n"
+ " --raw Read db file directly, bypassing SQLite VFS\n"
+ "args:\n"
+ " dbheader Show database header\n"
+ " pgidx Index of how each page is used\n"
+ " ptrmap Show all PTRMAP page content\n"
+ " NNN..MMM Show hex of pages NNN through MMM\n"
+ " NNN..end Show hex of pages NNN through end of file\n"
+ " NNNb Decode btree page NNN\n"
+ " NNNbc Decode btree page NNN and show content\n"
+ " NNNbm Decode btree page NNN and show a layout map\n"
+ " NNNbdCCC Decode cell CCC on btree page NNN\n"
+ " NNNt Decode freelist trunk page NNN\n"
+ " NNNtd Show leaf freelist pages on the decode\n"
+ " NNNtr Recursively decode freelist starting at NNN\n"
+ );
+}
+
+int main(int argc, char **argv){
+ sqlite3_int64 szFile;
+ unsigned char *zPgSz;
+ const char *zPrg = argv[0]; /* Name of this executable */
+ char **azArg = argv;
+ int nArg = argc;
+
+ /* Check for the "--uri" or "-uri" switch. */
+ if( nArg>1 ){
+ if( sqlite3_stricmp("-raw", azArg[1])==0
+ || sqlite3_stricmp("--raw", azArg[1])==0
+ ){
+ g.bRaw = 1;
+ azArg++;
+ nArg--;
+ }
+ }
+
+ if( nArg<2 ){
+ usage(zPrg);
+ exit(1);
+ }
+
+ fileOpen(zPrg, azArg[1]);
+ szFile = fileGetsize();
+
+ zPgSz = fileRead(16, 2);
+ g.pagesize = zPgSz[0]*256 + zPgSz[1]*65536;
+ if( g.pagesize==0 ) g.pagesize = 1024;
+ sqlite3_free(zPgSz);
+
+ printf("Pagesize: %d\n", g.pagesize);
+ g.mxPage = (u32)((szFile+g.pagesize-1)/g.pagesize);
+
+ printf("Available pages: 1..%u\n", g.mxPage);
+ if( nArg==2 ){
+ u32 i;
+ for(i=1; i<=g.mxPage; i++) print_page(i);
+ }else{
+ int i;
+ for(i=2; i<nArg; i++){
+ u32 iStart, iEnd;
+ char *zLeft;
+ if( strcmp(azArg[i], "dbheader")==0 ){
+ print_db_header();
+ continue;
+ }
+ if( strcmp(azArg[i], "pgidx")==0 ){
+ page_usage_report(zPrg, azArg[1]);
+ continue;
+ }
+ if( strcmp(azArg[i], "ptrmap")==0 ){
+ ptrmap_coverage_report(azArg[1]);
+ continue;
+ }
+ if( strcmp(azArg[i], "help")==0 ){
+ usage(zPrg);
+ continue;
+ }
+ if( !ISDIGIT(azArg[i][0]) ){
+ fprintf(stderr, "%s: unknown option: [%s]\n", zPrg, azArg[i]);
+ continue;
+ }
+ iStart = strtoul(azArg[i], &zLeft, 0);
+ checkPageValidity(iStart);
+ if( zLeft && strcmp(zLeft,"..end")==0 ){
+ iEnd = g.mxPage;
+ }else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){
+ iEnd = strtol(&zLeft[2], 0, 0);
+ checkPageValidity(iEnd);
+ }else if( zLeft && zLeft[0]=='b' ){
+ int ofst, nByte, hdrSize;
+ unsigned char *a;
+ if( iStart==1 ){
+ ofst = hdrSize = 100;
+ nByte = g.pagesize-100;
+ }else{
+ hdrSize = 0;
+ ofst = (iStart-1)*g.pagesize;
+ nByte = g.pagesize;
+ }
+ a = fileRead(ofst, nByte);
+ decode_btree_page(a, iStart, hdrSize, &zLeft[1]);
+ sqlite3_free(a);
+ continue;
+ }else if( zLeft && zLeft[0]=='t' ){
+ int detail = 0;
+ int recursive = 0;
+ int j;
+ for(j=1; zLeft[j]; j++){
+ if( zLeft[j]=='r' ) recursive = 1;
+ if( zLeft[j]=='d' ) detail = 1;
+ }
+ decode_trunk_page(iStart, detail, recursive);
+ continue;
+ }else{
+ iEnd = iStart;
+ }
+ if( iStart<1 || iEnd<iStart || iEnd>g.mxPage ){
+ fprintf(stderr,
+ "Page argument should be LOWER?..UPPER?. Range 1 to %d\n",
+ g.mxPage);
+ exit(1);
+ }
+ while( iStart<=iEnd ){
+ print_page(iStart);
+ iStart++;
+ }
+ }
+ }
+ fileClose();
+ return 0;
+}
diff --git a/tool/showjournal.c b/tool/showjournal.c
new file mode 100644
index 0000000..51c89da
--- /dev/null
+++ b/tool/showjournal.c
@@ -0,0 +1,138 @@
+/*
+** A utility for printing an SQLite database journal.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** state information
+*/
+static int pageSize = 1024;
+static int sectorSize = 512;
+static FILE *db = 0;
+static int fileSize = 0;
+static unsigned cksumNonce = 0;
+
+/* Report a memory allocation error */
+static void out_of_memory(void){
+ fprintf(stderr,"Out of memory...\n");
+ exit(1);
+}
+
+/*
+** Read N bytes of memory starting at iOfst into space obtained
+** from malloc().
+*/
+static unsigned char *read_content(int N, int iOfst){
+ int got;
+ unsigned char *pBuf = malloc(N);
+ if( pBuf==0 ) out_of_memory();
+ fseek(db, iOfst, SEEK_SET);
+ got = (int)fread(pBuf, 1, N, db);
+ if( got<0 ){
+ fprintf(stderr, "I/O error reading %d bytes from %d\n", N, iOfst);
+ memset(pBuf, 0, N);
+ }else if( got<N ){
+ fprintf(stderr, "Short read: got only %d of %d bytes from %d\n",
+ got, N, iOfst);
+ memset(&pBuf[got], 0, N-got);
+ }
+ return pBuf;
+}
+
+/* Print a line of decode output showing a 4-byte integer.
+*/
+static unsigned print_decode_line(
+ const unsigned char *aData, /* Content being decoded */
+ int ofst, int nByte, /* Start and size of decode */
+ const char *zMsg /* Message to append */
+){
+ int i, j;
+ unsigned val = aData[ofst];
+ char zBuf[100];
+ sprintf(zBuf, " %05x: %02x", ofst, aData[ofst]);
+ i = (int)strlen(zBuf);
+ for(j=1; j<4; j++){
+ if( j>=nByte ){
+ sprintf(&zBuf[i], " ");
+ }else{
+ sprintf(&zBuf[i], " %02x", aData[ofst+j]);
+ val = val*256 + aData[ofst+j];
+ }
+ i += (int)strlen(&zBuf[i]);
+ }
+ sprintf(&zBuf[i], " %10u", val);
+ printf("%s %s\n", zBuf, zMsg);
+ return val;
+}
+
+/*
+** Read and print a journal header. Store key information (page size, etc)
+** in global variables.
+*/
+static unsigned decode_journal_header(int iOfst){
+ unsigned char *pHdr = read_content(64, iOfst);
+ unsigned nPage;
+ printf("Header at offset %d:\n", iOfst);
+ print_decode_line(pHdr, 0, 4, "Header part 1 (3654616569)");
+ print_decode_line(pHdr, 4, 4, "Header part 2 (547447767)");
+ nPage =
+ print_decode_line(pHdr, 8, 4, "page count");
+ cksumNonce =
+ print_decode_line(pHdr, 12, 4, "chksum nonce");
+ print_decode_line(pHdr, 16, 4, "initial database size in pages");
+ sectorSize =
+ print_decode_line(pHdr, 20, 4, "sector size");
+ pageSize =
+ print_decode_line(pHdr, 24, 4, "page size");
+ print_decode_line(pHdr, 28, 4, "zero");
+ print_decode_line(pHdr, 32, 4, "zero");
+ print_decode_line(pHdr, 36, 4, "zero");
+ print_decode_line(pHdr, 40, 4, "zero");
+ free(pHdr);
+ return nPage;
+}
+
+static void print_page(int iOfst){
+ unsigned char *aData;
+ char zTitle[50];
+ aData = read_content(pageSize+8, iOfst);
+ sprintf(zTitle, "page number for page at offset %d", iOfst);
+ print_decode_line(aData-iOfst, iOfst, 4, zTitle);
+ free(aData);
+}
+
+int main(int argc, char **argv){
+ int nPage, cnt;
+ int iOfst;
+ if( argc!=2 ){
+ fprintf(stderr,"Usage: %s FILENAME\n", argv[0]);
+ exit(1);
+ }
+ db = fopen(argv[1], "rb");
+ if( db==0 ){
+ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
+ exit(1);
+ }
+ fseek(db, 0, SEEK_END);
+ fileSize = ftell(db);
+ printf("journal file size: %d bytes\n", fileSize);
+ fseek(db, 0, SEEK_SET);
+ iOfst = 0;
+ while( iOfst<fileSize ){
+ cnt = nPage = (int)decode_journal_header(iOfst);
+ if( cnt==0 ){
+ cnt = (fileSize - sectorSize)/(pageSize+8);
+ }
+ iOfst += sectorSize;
+ while( cnt && iOfst<fileSize ){
+ print_page(iOfst);
+ iOfst += pageSize+8;
+ }
+ iOfst = (iOfst/sectorSize + 1)*sectorSize;
+ }
+ fclose(db);
+ return 0;
+}
diff --git a/tool/showlocks.c b/tool/showlocks.c
new file mode 100644
index 0000000..4159a71
--- /dev/null
+++ b/tool/showlocks.c
@@ -0,0 +1,99 @@
+/*
+** This file implements a simple command-line utility that shows all of the
+** Posix Advisory Locks on a file.
+**
+** Usage:
+**
+** showlocks FILENAME
+**
+** To compile: gcc -o showlocks showlocks.c
+*/
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* This utility only looks for locks in the first 2 billion bytes */
+#define MX_LCK 2147483647
+
+/*
+** Print all locks on the inode of "fd" that occur in between
+** lwr and upr, inclusive.
+*/
+static int showLocksInRange(int fd, off_t lwr, off_t upr){
+ int cnt = 0;
+ struct flock x;
+ struct lockRange {
+ off_t lwr;
+ off_t upr;
+ } *aPending = 0;
+ int nAlloc = 1;
+ int nPending = 0;
+ int nDone = 0;
+
+ nPending = 1;
+ aPending = malloc( sizeof(aPending[0]) );
+ if( aPending==0 ){
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ aPending[0].lwr = lwr;
+ aPending[0].upr = upr;
+
+ for(nDone=0; nDone<nPending; nDone++){
+ lwr = aPending[nDone].lwr;
+ upr = aPending[nDone].upr;
+ if( lwr>=upr ) continue;
+ x.l_type = F_WRLCK;
+ x.l_whence = SEEK_SET;
+ x.l_start = lwr;
+ x.l_len = upr - lwr;
+ fcntl(fd, F_GETLK, &x);
+ if( x.l_type==F_UNLCK ) continue;
+ printf("start: %-12d len: %-5d pid: %-5d type: %s\n",
+ (int)x.l_start, (int)x.l_len,
+ x.l_pid, x.l_type==F_WRLCK ? "WRLCK" : "RDLCK");
+ cnt++;
+ if( nPending+2 > nAlloc ){
+ nAlloc = nAlloc*2 + 2;
+ aPending = realloc(aPending, sizeof(aPending[0])*nAlloc );
+ }
+ if( aPending==0 ){
+ fprintf(stderr, "unable to realloc for %d bytes\n",
+ (int)sizeof(aPending[0])*(nPending+2));
+ exit(1);
+ }
+ if( lwr<x.l_start ){
+ aPending[nPending].lwr = lwr;
+ aPending[nPending].upr = x.l_start;
+ nPending++;
+ }
+ if( x.l_start+x.l_len<=upr ){
+ aPending[nPending].lwr = x.l_start + x.l_len;
+ aPending[nPending].upr = upr;
+ nPending++;
+ }
+ }
+ free(aPending);
+ return cnt;
+}
+
+int main(int argc, char **argv){
+ int fd;
+ int cnt;
+
+ if( argc!=2 ){
+ fprintf(stderr, "Usage: %s FILENAME\n", argv[0]);
+ return 1;
+ }
+ fd = open(argv[1], O_RDWR, 0);
+ if( fd<0 ){
+ fprintf(stderr, "%s: cannot open %s\n", argv[0], argv[1]);
+ return 1;
+ }
+ cnt = showLocksInRange(fd, 0, MX_LCK);
+ if( cnt==0 ) printf("no locks\n");
+ close(fd);
+ return 0;
+}
diff --git a/tool/showshm.c b/tool/showshm.c
new file mode 100644
index 0000000..03e0fc4
--- /dev/null
+++ b/tool/showshm.c
@@ -0,0 +1,158 @@
+/*
+** A utility for printing content from the wal-index or "shm" file.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+#define ISPRINT(X) isprint((unsigned char)(X))
+
+#if !defined(_MSC_VER)
+#include <unistd.h>
+#include <sys/types.h>
+#else
+#include <io.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+static int fd = -1; /* The open SHM file */
+
+/* Report an out-of-memory error and die.
+*/
+static void out_of_memory(void){
+ fprintf(stderr,"Out of memory...\n");
+ exit(1);
+}
+
+/*
+** Read content from the file.
+**
+** Space to hold the content is obtained from malloc() and needs to be
+** freed by the caller.
+*/
+static unsigned char *getContent(int ofst, int nByte){
+ unsigned char *aData;
+ aData = malloc(nByte);
+ if( aData==0 ) out_of_memory();
+ lseek(fd, ofst, SEEK_SET);
+ read(fd, aData, nByte);
+ return aData;
+}
+
+/*
+** Flags values
+*/
+#define FG_HEX 1 /* Show as hex */
+#define FG_NBO 2 /* Native byte order */
+#define FG_PGSZ 4 /* Show as page-size */
+
+/* Print a line of decode output showing a 4-byte integer.
+*/
+static void print_decode_line(
+ unsigned char *aData, /* Content being decoded */
+ int ofst, int nByte, /* Start and size of decode */
+ unsigned flg, /* Display flags */
+ const char *zMsg /* Message to append */
+){
+ int i, j;
+ int val = aData[ofst];
+ char zBuf[100];
+ sprintf(zBuf, " %03x: %02x", ofst, aData[ofst]);
+ i = (int)strlen(zBuf);
+ for(j=1; j<4; j++){
+ if( j>=nByte ){
+ sprintf(&zBuf[i], " ");
+ }else{
+ sprintf(&zBuf[i], " %02x", aData[ofst+j]);
+ val = val*256 + aData[ofst+j];
+ }
+ i += (int)strlen(&zBuf[i]);
+ }
+ if( nByte==8 ){
+ for(j=4; j<8; j++){
+ sprintf(&zBuf[i], " %02x", aData[ofst+j]);
+ i += (int)strlen(&zBuf[i]);
+ }
+ }
+ if( flg & FG_NBO ){
+ assert( nByte==4 );
+ memcpy(&val, aData+ofst, 4);
+ }
+ sprintf(&zBuf[i], " ");
+ i += 12;
+ if( flg & FG_PGSZ ){
+ unsigned short sz;
+ memcpy(&sz, aData+ofst, 2);
+ sprintf(&zBuf[i], " %9d", sz==1 ? 65536 : sz);
+ }else if( flg & FG_HEX ){
+ sprintf(&zBuf[i], " 0x%08x", val);
+ }else if( nByte<8 ){
+ sprintf(&zBuf[i], " %9d", val);
+ }
+ printf("%s %s\n", zBuf, zMsg);
+}
+
+/*
+** Print an instance of the WalIndexHdr object. ix is either 0 or 1
+** to select which header to print.
+*/
+static void print_index_hdr(unsigned char *aData, int ix){
+ int i;
+ assert( ix==0 || ix==1 );
+ i = ix ? 48 : 0;
+ print_decode_line(aData, 0+i, 4, FG_NBO, "Wal-index version");
+ print_decode_line(aData, 4+i, 4, 0, "unused padding");
+ print_decode_line(aData, 8+i, 4, FG_NBO, "transaction counter");
+ print_decode_line(aData,12+i, 1, 0, "1 when initialized");
+ print_decode_line(aData,13+i, 1, 0, "true if WAL cksums are bigendian");
+ print_decode_line(aData,14+i, 2, FG_PGSZ, "database page size");
+ print_decode_line(aData,16+i, 4, FG_NBO, "mxFrame");
+ print_decode_line(aData,20+i, 4, FG_NBO, "Size of database in pages");
+ print_decode_line(aData,24+i, 8, 0, "Cksum of last frame in -wal");
+ print_decode_line(aData,32+i, 8, 0, "Salt values from the -wal");
+ print_decode_line(aData,40+i, 8, 0, "Cksum over all prior fields");
+}
+
+/*
+** Print the WalCkptInfo object
+*/
+static void print_ckpt_info(unsigned char *aData){
+ const int i = 96;
+ int j;
+ print_decode_line(aData, 0+i, 4, FG_NBO, "nBackfill");
+ for(j=0; j<5; j++){
+ char zLabel[100];
+ sprintf(zLabel, "aReadMark[%d]", j);
+ print_decode_line(aData, 4*j+4+i, 4, FG_NBO, zLabel);
+ }
+ print_decode_line(aData,24+i, 8, 0, "aLock");
+ print_decode_line(aData,32+i, 4, FG_NBO, "nBackfillAttempted");
+ print_decode_line(aData,36+i, 4, FG_NBO, "notUsed0");
+}
+
+
+int main(int argc, char **argv){
+ unsigned char *aData;
+ if( argc<2 ){
+ fprintf(stderr,"Usage: %s FILENAME\n", argv[0]);
+ exit(1);
+ }
+ fd = open(argv[1], O_RDONLY);
+ if( fd<0 ){
+ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
+ exit(1);
+ }
+ aData = getContent(0, 136);
+ print_index_hdr(aData, 0);
+ print_index_hdr(aData, 1);
+ print_ckpt_info(aData);
+ free(aData);
+ close(fd);
+ return 0;
+}
diff --git a/tool/showstat4.c b/tool/showstat4.c
new file mode 100644
index 0000000..b8a12ad
--- /dev/null
+++ b/tool/showstat4.c
@@ -0,0 +1,163 @@
+/*
+** This utility program decodes and displays the content of the
+** sqlite_stat4 table in the database file named on the command
+** line.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "sqlite3.h"
+
+#define ISPRINT(X) isprint((unsigned char)(X))
+
+typedef sqlite3_int64 i64; /* 64-bit signed integer type */
+
+
+/*
+** Convert the var-int format into i64. Return the number of bytes
+** in the var-int. Write the var-int value into *pVal.
+*/
+static int decodeVarint(const unsigned char *z, i64 *pVal){
+ i64 v = 0;
+ int i;
+ for(i=0; i<8; i++){
+ v = (v<<7) + (z[i]&0x7f);
+ if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
+ }
+ v = (v<<8) + (z[i]&0xff);
+ *pVal = v;
+ return 9;
+}
+
+
+
+int main(int argc, char **argv){
+ sqlite3 *db;
+ sqlite3_stmt *pStmt;
+ char *zIdx = 0;
+ int rc, j, x, y, mxHdr;
+ const unsigned char *aSample;
+ int nSample;
+ i64 iVal;
+ const char *zSep;
+ int iRow = 0;
+
+ if( argc!=2 ){
+ fprintf(stderr, "Usage: %s DATABASE-FILE\n", argv[0]);
+ exit(1);
+ }
+ rc = sqlite3_open(argv[1], &db);
+ if( rc!=SQLITE_OK || db==0 ){
+ fprintf(stderr, "Cannot open database file [%s]\n", argv[1]);
+ exit(1);
+ }
+ rc = sqlite3_prepare_v2(db,
+ "SELECT tbl||'.'||idx, nEq, nLT, nDLt, sample "
+ "FROM sqlite_stat4 ORDER BY 1", -1,
+ &pStmt, 0);
+ if( rc!=SQLITE_OK || pStmt==0 ){
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ exit(1);
+ }
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( zIdx==0 || strcmp(zIdx, (const char*)sqlite3_column_text(pStmt,0))!=0 ){
+ if( zIdx ) printf("\n**************************************"
+ "**************\n\n");
+ sqlite3_free(zIdx);
+ zIdx = sqlite3_mprintf("%s", sqlite3_column_text(pStmt,0));
+ iRow = 0;
+ }
+ printf("%s sample %d ------------------------------------\n", zIdx, ++iRow);
+ printf(" nEq = %s\n", sqlite3_column_text(pStmt,1));
+ printf(" nLt = %s\n", sqlite3_column_text(pStmt,2));
+ printf(" nDLt = %s\n", sqlite3_column_text(pStmt,3));
+ printf(" sample = x'");
+ aSample = sqlite3_column_blob(pStmt,4);
+ nSample = sqlite3_column_bytes(pStmt,4);
+ for(j=0; j<nSample; j++) printf("%02x", aSample[j]);
+ printf("'\n ");
+ zSep = " ";
+ x = decodeVarint(aSample, &iVal);
+ if( iVal<x || iVal>nSample ){
+ printf(" <error>\n");
+ continue;
+ }
+ y = mxHdr = (int)iVal;
+ while( x<mxHdr ){
+ int sz;
+ i64 v;
+ x += decodeVarint(aSample+x, &iVal);
+ if( x>mxHdr ) break;
+ if( iVal<0 ) break;
+ switch( iVal ){
+ case 0: sz = 0; break;
+ case 1: sz = 1; break;
+ case 2: sz = 2; break;
+ case 3: sz = 3; break;
+ case 4: sz = 4; break;
+ case 5: sz = 6; break;
+ case 6: sz = 8; break;
+ case 7: sz = 8; break;
+ case 8: sz = 0; break;
+ case 9: sz = 0; break;
+ case 10:
+ case 11: sz = 0; break;
+ default: sz = (int)(iVal-12)/2; break;
+ }
+ if( y+sz>nSample ) break;
+ if( iVal==0 ){
+ printf("%sNULL", zSep);
+ }else if( iVal==8 || iVal==9 ){
+ printf("%s%d", zSep, ((int)iVal)-8);
+ }else if( iVal<=7 ){
+ v = (signed char)aSample[y];
+ for(j=1; j<sz; j++){
+ v = (v<<8) + aSample[y+j];
+ }
+ if( iVal==7 ){
+ double r;
+ char *z;
+ memcpy(&r, &v, sizeof(r));
+ z = sqlite3_mprintf("%s%!.15g", zSep, r);
+ printf("%s", z);
+ sqlite3_free(z);
+ }else{
+ printf("%s%lld", zSep, v);
+ }
+ }else if( (iVal&1)==0 ){
+ printf("%sx'", zSep);
+ for(j=0; j<sz; j++){
+ printf("%02x", aSample[y+j]);
+ }
+ printf("'");
+ }else{
+ printf("%s'", zSep);
+ for(j=0; j<sz; j++){
+ char c = (char)aSample[y+j];
+ if( ISPRINT(c) ){
+ if( c=='\'' || c=='\\' ) putchar('\\');
+ putchar(c);
+ }else if( c=='\n' ){
+ printf("\\n");
+ }else if( c=='\t' ){
+ printf("\\t");
+ }else if( c=='\r' ){
+ printf("\\r");
+ }else{
+ printf("\\%03o", c);
+ }
+ }
+ printf("'");
+ }
+ zSep = ",";
+ y += sz;
+ }
+ printf("\n");
+ }
+ sqlite3_free(zIdx);
+ sqlite3_finalize(pStmt);
+ sqlite3_close(db);
+ return 0;
+}
diff --git a/tool/showwal.c b/tool/showwal.c
new file mode 100644
index 0000000..7e6c0e1
--- /dev/null
+++ b/tool/showwal.c
@@ -0,0 +1,639 @@
+/*
+** A utility for printing content from a write-ahead log file.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+#define ISPRINT(X) isprint((unsigned char)(X))
+
+#if !defined(_MSC_VER)
+#include <unistd.h>
+#include <sys/types.h>
+#else
+#include <io.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+
+static int pagesize = 1024; /* Size of a database page */
+static int fd = -1; /* File descriptor for reading the WAL file */
+static int mxFrame = 0; /* Last frame */
+static int perLine = 16; /* HEX elements to print per line */
+
+typedef long long int i64; /* Datatype for 64-bit integers */
+
+/* Information for computing the checksum */
+typedef struct Cksum Cksum;
+struct Cksum {
+ int bSwap; /* True to do byte swapping on 32-bit words */
+ unsigned s0, s1; /* Current checksum value */
+};
+
+/*
+** extract a 32-bit big-endian integer
+*/
+static unsigned int getInt32(const unsigned char *a){
+ unsigned int x = (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
+ return x;
+}
+
+/*
+** Swap bytes on a 32-bit unsigned integer
+*/
+static unsigned int swab32(unsigned int x){
+ return (((x)&0x000000FF)<<24) + (((x)&0x0000FF00)<<8)
+ + (((x)&0x00FF0000)>>8) + (((x)&0xFF000000)>>24);
+}
+
+/* Extend the checksum. Reinitialize the checksum if bInit is true.
+*/
+static void extendCksum(
+ Cksum *pCksum,
+ unsigned char *aData,
+ unsigned int nByte,
+ int bInit
+){
+ unsigned int *a32;
+ if( bInit ){
+ int a = 0;
+ *((char*)&a) = 1;
+ if( a==1 ){
+ /* Host is little-endian */
+ pCksum->bSwap = getInt32(aData)!=0x377f0682;
+ }else{
+ /* Host is big-endian */
+ pCksum->bSwap = getInt32(aData)!=0x377f0683;
+ }
+ pCksum->s0 = 0;
+ pCksum->s1 = 0;
+ }
+ a32 = (unsigned int*)aData;
+ while( nByte>0 ){
+ unsigned int x0 = a32[0];
+ unsigned int x1 = a32[1];
+ if( pCksum->bSwap ){
+ x0 = swab32(x0);
+ x1 = swab32(x1);
+ }
+ pCksum->s0 += x0 + pCksum->s1;
+ pCksum->s1 += x1 + pCksum->s0;
+ nByte -= 8;
+ a32 += 2;
+ }
+}
+
+/*
+** Convert the var-int format into i64. Return the number of bytes
+** in the var-int. Write the var-int value into *pVal.
+*/
+static int decodeVarint(const unsigned char *z, i64 *pVal){
+ i64 v = 0;
+ int i;
+ for(i=0; i<8; i++){
+ v = (v<<7) + (z[i]&0x7f);
+ if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
+ }
+ v = (v<<8) + (z[i]&0xff);
+ *pVal = v;
+ return 9;
+}
+
+/* Report an out-of-memory error and die.
+*/
+static void out_of_memory(void){
+ fprintf(stderr,"Out of memory...\n");
+ exit(1);
+}
+
+/*
+** Read content from the file.
+**
+** Space to hold the content is obtained from malloc() and needs to be
+** freed by the caller.
+*/
+static unsigned char *getContent(i64 ofst, int nByte){
+ unsigned char *aData;
+ aData = malloc(nByte);
+ if( aData==0 ) out_of_memory();
+ lseek(fd, ofst, SEEK_SET);
+ read(fd, aData, nByte);
+ return aData;
+}
+
+/*
+** Print a range of bytes as hex and as ascii.
+*/
+static void print_byte_range(
+ int ofst, /* First byte in the range of bytes to print */
+ int nByte, /* Number of bytes to print */
+ unsigned char *aData, /* Content to print */
+ int printOfst /* Add this amount to the index on the left column */
+){
+ int i, j;
+ const char *zOfstFmt;
+
+ if( ((printOfst+nByte)&~0xfff)==0 ){
+ zOfstFmt = " %03x: ";
+ }else if( ((printOfst+nByte)&~0xffff)==0 ){
+ zOfstFmt = " %04x: ";
+ }else if( ((printOfst+nByte)&~0xfffff)==0 ){
+ zOfstFmt = " %05x: ";
+ }else if( ((printOfst+nByte)&~0xffffff)==0 ){
+ zOfstFmt = " %06x: ";
+ }else{
+ zOfstFmt = " %08x: ";
+ }
+
+ for(i=0; i<nByte; i += perLine){
+ fprintf(stdout, zOfstFmt, i+printOfst);
+ for(j=0; j<perLine; j++){
+ if( i+j>nByte ){
+ fprintf(stdout, " ");
+ }else{
+ fprintf(stdout,"%02x ", aData[i+j]);
+ }
+ }
+ for(j=0; j<perLine; j++){
+ if( i+j>nByte ){
+ fprintf(stdout, " ");
+ }else{
+ fprintf(stdout,"%c", ISPRINT(aData[i+j]) ? aData[i+j] : '.');
+ }
+ }
+ fprintf(stdout,"\n");
+ }
+}
+
+/* Print a line of decode output showing a 4-byte integer.
+*/
+static void print_decode_line(
+ unsigned char *aData, /* Content being decoded */
+ int ofst, int nByte, /* Start and size of decode */
+ int asHex, /* If true, output value as hex */
+ const char *zMsg /* Message to append */
+){
+ int i, j;
+ int val = aData[ofst];
+ char zBuf[100];
+ sprintf(zBuf, " %03x: %02x", ofst, aData[ofst]);
+ i = (int)strlen(zBuf);
+ for(j=1; j<4; j++){
+ if( j>=nByte ){
+ sprintf(&zBuf[i], " ");
+ }else{
+ sprintf(&zBuf[i], " %02x", aData[ofst+j]);
+ val = val*256 + aData[ofst+j];
+ }
+ i += (int)strlen(&zBuf[i]);
+ }
+ if( asHex ){
+ sprintf(&zBuf[i], " 0x%08x", val);
+ }else{
+ sprintf(&zBuf[i], " %9d", val);
+ }
+ printf("%s %s\n", zBuf, zMsg);
+}
+
+/*
+** Print an entire page of content as hex
+*/
+static void print_frame(int iFrame){
+ i64 iStart;
+ unsigned char *aData;
+ iStart = 32 + (i64)(iFrame-1)*(pagesize+24);
+ fprintf(stdout, "Frame %d: (offsets 0x%llx..0x%llx)\n",
+ iFrame, iStart, iStart+pagesize+24);
+ aData = getContent(iStart, pagesize+24);
+ print_decode_line(aData, 0, 4, 0, "Page number");
+ print_decode_line(aData, 4, 4, 0, "DB size, or 0 for non-commit");
+ print_decode_line(aData, 8, 4, 1, "Salt-1");
+ print_decode_line(aData,12, 4, 1, "Salt-2");
+ print_decode_line(aData,16, 4, 1, "Checksum-1");
+ print_decode_line(aData,20, 4, 1, "Checksum-2");
+ print_byte_range(iStart+24, pagesize, aData+24, 0);
+ free(aData);
+}
+
+/*
+** Summarize a single frame on a single line.
+*/
+static void print_oneline_frame(int iFrame, Cksum *pCksum){
+ i64 iStart;
+ unsigned char *aData;
+ unsigned int s0, s1;
+ iStart = 32 + (i64)(iFrame-1)*(pagesize+24);
+ aData = getContent(iStart, 24);
+ extendCksum(pCksum, aData, 8, 0);
+ extendCksum(pCksum, getContent(iStart+24, pagesize), pagesize, 0);
+ s0 = getInt32(aData+16);
+ s1 = getInt32(aData+20);
+ fprintf(stdout, "Frame %4d: %6d %6d 0x%08x,%08x 0x%08x,%08x",
+ iFrame,
+ getInt32(aData),
+ getInt32(aData+4),
+ getInt32(aData+8),
+ getInt32(aData+12),
+ s0,
+ s1
+ );
+ if( s0==pCksum->s0 && s1==pCksum->s1 ){
+ fprintf(stdout, "\n");
+ }else{
+ fprintf(stdout, " should be 0x%08x,%08x\n",
+ pCksum->s0, pCksum->s1);
+ }
+
+ /* Reset the checksum so that a single frame checksum failure will not
+ ** cause all subsequent frames to also show a failure. */
+ pCksum->s0 = s0;
+ pCksum->s1 = s1;
+ free(aData);
+}
+
+/*
+** Decode the WAL header.
+*/
+static void print_wal_header(Cksum *pCksum){
+ unsigned char *aData;
+ aData = getContent(0, 32);
+ if( pCksum ){
+ extendCksum(pCksum, aData, 24, 1);
+ printf("Checksum byte order: %s\n", pCksum->bSwap ? "swapped" : "native");
+ }
+ printf("WAL Header:\n");
+ print_decode_line(aData, 0, 4,1,"Magic. 0x377f0682 (le) or 0x377f0683 (be)");
+ print_decode_line(aData, 4, 4, 0, "File format");
+ print_decode_line(aData, 8, 4, 0, "Database page size");
+ print_decode_line(aData, 12,4, 0, "Checkpoint sequence number");
+ print_decode_line(aData, 16,4, 1, "Salt-1");
+ print_decode_line(aData, 20,4, 1, "Salt-2");
+ print_decode_line(aData, 24,4, 1, "Checksum-1");
+ print_decode_line(aData, 28,4, 1, "Checksum-2");
+ if( pCksum ){
+ if( pCksum->s0!=getInt32(aData+24) ){
+ printf("**** cksum-1 mismatch: 0x%08x\n", pCksum->s0);
+ }
+ if( pCksum->s1!=getInt32(aData+28) ){
+ printf("**** cksum-2 mismatch: 0x%08x\n", pCksum->s1);
+ }
+ }
+ free(aData);
+}
+/*
+** Describe cell content.
+*/
+static i64 describeContent(
+ unsigned char *a, /* Cell content */
+ i64 nLocal, /* Bytes in a[] */
+ char *zDesc /* Write description here */
+){
+ int nDesc = 0;
+ int n, j;
+ i64 i, x, v;
+ const unsigned char *pData;
+ const unsigned char *pLimit;
+ char sep = ' ';
+
+ pLimit = &a[nLocal];
+ n = decodeVarint(a, &x);
+ pData = &a[x];
+ a += n;
+ i = x - n;
+ while( i>0 && pData<=pLimit ){
+ n = decodeVarint(a, &x);
+ a += n;
+ i -= n;
+ nLocal -= n;
+ zDesc[0] = sep;
+ sep = ',';
+ nDesc++;
+ zDesc++;
+ if( x==0 ){
+ sprintf(zDesc, "*"); /* NULL is a "*" */
+ }else if( x>=1 && x<=6 ){
+ v = (signed char)pData[0];
+ pData++;
+ switch( x ){
+ case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 4: v = (v<<8) + pData[0]; pData++;
+ case 3: v = (v<<8) + pData[0]; pData++;
+ case 2: v = (v<<8) + pData[0]; pData++;
+ }
+ sprintf(zDesc, "%lld", v);
+ }else if( x==7 ){
+ sprintf(zDesc, "real");
+ pData += 8;
+ }else if( x==8 ){
+ sprintf(zDesc, "0");
+ }else if( x==9 ){
+ sprintf(zDesc, "1");
+ }else if( x>=12 ){
+ i64 size = (x-12)/2;
+ if( (x&1)==0 ){
+ sprintf(zDesc, "blob(%lld)", size);
+ }else{
+ sprintf(zDesc, "txt(%lld)", size);
+ }
+ pData += size;
+ }
+ j = (int)strlen(zDesc);
+ zDesc += j;
+ nDesc += j;
+ }
+ return nDesc;
+}
+
+/*
+** Compute the local payload size given the total payload size and
+** the page size.
+*/
+static i64 localPayload(i64 nPayload, char cType){
+ i64 maxLocal;
+ i64 minLocal;
+ i64 surplus;
+ i64 nLocal;
+ if( cType==13 ){
+ /* Table leaf */
+ maxLocal = pagesize-35;
+ minLocal = (pagesize-12)*32/255-23;
+ }else{
+ maxLocal = (pagesize-12)*64/255-23;
+ minLocal = (pagesize-12)*32/255-23;
+ }
+ if( nPayload>maxLocal ){
+ surplus = minLocal + (nPayload-minLocal)%(pagesize-4);
+ if( surplus<=maxLocal ){
+ nLocal = surplus;
+ }else{
+ nLocal = minLocal;
+ }
+ }else{
+ nLocal = nPayload;
+ }
+ return nLocal;
+}
+
+/*
+** Create a description for a single cell.
+**
+** The return value is the local cell size.
+*/
+static i64 describeCell(
+ unsigned char cType, /* Page type */
+ unsigned char *a, /* Cell content */
+ int showCellContent, /* Show cell content if true */
+ char **pzDesc /* Store description here */
+){
+ int i;
+ i64 nDesc = 0;
+ int n = 0;
+ int leftChild;
+ i64 nPayload;
+ i64 rowid;
+ i64 nLocal;
+ static char zDesc[1000];
+ i = 0;
+ if( cType<=5 ){
+ leftChild = ((a[0]*256 + a[1])*256 + a[2])*256 + a[3];
+ a += 4;
+ n += 4;
+ sprintf(zDesc, "lx: %d ", leftChild);
+ nDesc = strlen(zDesc);
+ }
+ if( cType!=5 ){
+ i = decodeVarint(a, &nPayload);
+ a += i;
+ n += i;
+ sprintf(&zDesc[nDesc], "n: %lld ", nPayload);
+ nDesc += strlen(&zDesc[nDesc]);
+ nLocal = localPayload(nPayload, cType);
+ }else{
+ nPayload = nLocal = 0;
+ }
+ if( cType==5 || cType==13 ){
+ i = decodeVarint(a, &rowid);
+ a += i;
+ n += i;
+ sprintf(&zDesc[nDesc], "r: %lld ", rowid);
+ nDesc += strlen(&zDesc[nDesc]);
+ }
+ if( nLocal<nPayload ){
+ int ovfl;
+ unsigned char *b = &a[nLocal];
+ ovfl = ((b[0]*256 + b[1])*256 + b[2])*256 + b[3];
+ sprintf(&zDesc[nDesc], "ov: %d ", ovfl);
+ nDesc += strlen(&zDesc[nDesc]);
+ n += 4;
+ }
+ if( showCellContent && cType!=5 ){
+ nDesc += describeContent(a, nLocal, &zDesc[nDesc-1]);
+ }
+ *pzDesc = zDesc;
+ return nLocal+n;
+}
+
+/*
+** Decode a btree page
+*/
+static void decode_btree_page(
+ unsigned char *a, /* Content of the btree page to be decoded */
+ int pgno, /* Page number */
+ int hdrSize, /* Size of the page1-header in bytes */
+ const char *zArgs /* Flags to control formatting */
+){
+ const char *zType = "unknown";
+ int nCell;
+ int i, j;
+ int iCellPtr;
+ int showCellContent = 0;
+ int showMap = 0;
+ char *zMap = 0;
+ switch( a[0] ){
+ case 2: zType = "index interior node"; break;
+ case 5: zType = "table interior node"; break;
+ case 10: zType = "index leaf"; break;
+ case 13: zType = "table leaf"; break;
+ }
+ while( zArgs[0] ){
+ switch( zArgs[0] ){
+ case 'c': showCellContent = 1; break;
+ case 'm': showMap = 1; break;
+ }
+ zArgs++;
+ }
+ printf("Decode of btree page %d:\n", pgno);
+ print_decode_line(a, 0, 1, 0, zType);
+ print_decode_line(a, 1, 2, 0, "Offset to first freeblock");
+ print_decode_line(a, 3, 2, 0, "Number of cells on this page");
+ nCell = a[3]*256 + a[4];
+ print_decode_line(a, 5, 2, 0, "Offset to cell content area");
+ print_decode_line(a, 7, 1, 0, "Fragmented byte count");
+ if( a[0]==2 || a[0]==5 ){
+ print_decode_line(a, 8, 4, 0, "Right child");
+ iCellPtr = 12;
+ }else{
+ iCellPtr = 8;
+ }
+ if( nCell>0 ){
+ printf(" key: lx=left-child n=payload-size r=rowid\n");
+ }
+ if( showMap ){
+ zMap = malloc(pagesize);
+ memset(zMap, '.', pagesize);
+ memset(zMap, '1', hdrSize);
+ memset(&zMap[hdrSize], 'H', iCellPtr);
+ memset(&zMap[hdrSize+iCellPtr], 'P', 2*nCell);
+ }
+ for(i=0; i<nCell; i++){
+ int cofst = iCellPtr + i*2;
+ char *zDesc;
+ i64 n;
+
+ cofst = a[cofst]*256 + a[cofst+1];
+ n = describeCell(a[0], &a[cofst-hdrSize], showCellContent, &zDesc);
+ if( showMap ){
+ char zBuf[30];
+ memset(&zMap[cofst], '*', (size_t)n);
+ zMap[cofst] = '[';
+ zMap[cofst+n-1] = ']';
+ sprintf(zBuf, "%d", i);
+ j = (int)strlen(zBuf);
+ if( j<=n-2 ) memcpy(&zMap[cofst+1], zBuf, j);
+ }
+ printf(" %03x: cell[%d] %s\n", cofst, i, zDesc);
+ }
+ if( showMap ){
+ for(i=0; i<pagesize; i+=64){
+ printf(" %03x: %.64s\n", i, &zMap[i]);
+ }
+ free(zMap);
+ }
+}
+
+/*
+** Check the range validity for a page number. Print an error and
+** exit if the page is out of range.
+*/
+static void checkPageValidity(int iPage, int mxPage){
+ if( iPage<1 || iPage>mxPage ){
+ fprintf(stderr, "Invalid page number %d: valid range is 1..%d\n",
+ iPage, mxPage);
+ exit(1);
+ }
+}
+
+int main(int argc, char **argv){
+ struct stat sbuf;
+ unsigned char zPgSz[4];
+ if( argc<2 ){
+ fprintf(stderr,"Usage: %s FILENAME ?PAGE? ...\n", argv[0]);
+ exit(1);
+ }
+ fd = open(argv[1], O_RDONLY);
+ if( fd<0 ){
+ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
+ exit(1);
+ }
+ zPgSz[0] = 0;
+ zPgSz[1] = 0;
+ fstat(fd, &sbuf);
+ if( sbuf.st_size<32 ){
+ printf("%s: file too small to be a WAL - only %d bytes\n",
+ argv[1], (int)sbuf.st_size);
+ return 0;
+ }
+ if( lseek(fd, 8, SEEK_SET)!=8 ){
+ printf("\"%s\" seems to not be a valid WAL file\n", argv[1]);
+ return 1;
+ }
+ if( read(fd, zPgSz, 4)!=4 ){
+ printf("\"%s\": cannot read the page size\n", argv[1]);
+ return 1;
+ }
+ pagesize = zPgSz[1]*65536 + zPgSz[2]*256 + zPgSz[3];
+ if( pagesize==0 ) pagesize = 1024;
+ printf("Pagesize: %d\n", pagesize);
+ if( (pagesize & (pagesize-1))!=0 || pagesize<512 || pagesize>65536 ){
+ printf("\"%s\": invalid page size.\n", argv[1]);
+ return 1;
+ }
+ mxFrame = (sbuf.st_size - 32)/(pagesize + 24);
+ printf("Available pages: 1..%d\n", mxFrame);
+ if( argc==2 ){
+ int i;
+ Cksum x;
+ print_wal_header(&x);
+ for(i=1; i<=mxFrame; i++){
+ print_oneline_frame(i, &x);
+ }
+ }else{
+ int i;
+ for(i=2; i<argc; i++){
+ int iStart, iEnd;
+ char *zLeft;
+ if( strcmp(argv[i], "header")==0 ){
+ print_wal_header(0);
+ continue;
+ }
+ if( !ISDIGIT(argv[i][0]) ){
+ fprintf(stderr, "%s: unknown option: [%s]\n", argv[0], argv[i]);
+ continue;
+ }
+ iStart = strtol(argv[i], &zLeft, 0);
+ checkPageValidity(iStart, mxFrame);
+ if( zLeft && strcmp(zLeft,"..end")==0 ){
+ iEnd = mxFrame;
+ }else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){
+ iEnd = strtol(&zLeft[2], 0, 0);
+ checkPageValidity(iEnd, mxFrame);
+ }else if( zLeft && zLeft[0]=='b' ){
+ i64 ofst;
+ int nByte, hdrSize;
+ unsigned char *a;
+ if( iStart==1 ){
+ hdrSize = 100;
+ ofst = hdrSize = 100;
+ nByte = pagesize-100;
+ }else{
+ hdrSize = 0;
+ ofst = (i64)(iStart-1)*pagesize;
+ nByte = pagesize;
+ }
+ ofst = 32 + hdrSize + (i64)(iStart-1)*(pagesize+24) + 24;
+ a = getContent(ofst, nByte);
+ decode_btree_page(a, iStart, hdrSize, zLeft+1);
+ free(a);
+ continue;
+#if !defined(_MSC_VER)
+ }else if( zLeft && strcmp(zLeft,"truncate")==0 ){
+ /* Frame number followed by "truncate" truncates the WAL file
+ ** after that frame */
+ off_t newSize = 32 + iStart*(pagesize+24);
+ truncate(argv[1], newSize);
+ continue;
+#endif
+ }else{
+ iEnd = iStart;
+ }
+ if( iStart<1 || iEnd<iStart || iEnd>mxFrame ){
+ fprintf(stderr,
+ "Page argument should be LOWER?..UPPER?. Range 1 to %d\n",
+ mxFrame);
+ exit(1);
+ }
+ while( iStart<=iEnd ){
+ print_frame(iStart);
+ iStart++;
+ }
+ }
+ }
+ close(fd);
+ return 0;
+}
diff --git a/tool/soak1.tcl b/tool/soak1.tcl
new file mode 100644
index 0000000..846f905
--- /dev/null
+++ b/tool/soak1.tcl
@@ -0,0 +1,103 @@
+#!/usr/bin/tclsh
+#
+# Usage:
+#
+# tclsh soak1.tcl local-makefile.mk ?target? ?scenario?
+#
+# This generates many variations on local-makefile.mk (by modifing
+# the OPT = lines) and runs them will fulltest, one by one. The
+# constructed makefiles are named "soak1.mk".
+#
+# If ?target? is provided, that is the makefile target that is run.
+# The default is "fulltest"
+#
+# If ?scenario? is provided, it is the name of a single scenario to
+# be run. All other scenarios are skipped.
+#
+set localmake [lindex $argv 0]
+set target [lindex $argv 1]
+set scene [lindex $argv 2]
+if {$target==""} {set target fulltest}
+if {$scene==""} {set scene all}
+
+set in [open $localmake]
+set maketxt [read $in]
+close $in
+regsub -all {\\\n} $maketxt {} maketxt
+#set makefilename "soak1-[expr {int(rand()*1000000000)}].mk"
+set makefilename "soak1.mk"
+
+# Generate a makefile
+#
+proc generate_makefile {pattern} {
+ global makefilename maketxt
+ set out [open $makefilename w]
+ set seen_opt 0
+ foreach line [split $maketxt \n] {
+ if {[regexp {^ *#? *OPTS[ =+]} $line]} {
+ if {!$seen_opt} {
+ puts $out "OPTS += -DSQLITE_NO_SYNC=1"
+ foreach x $pattern {
+ puts $out "OPTS += -D$x"
+ }
+ set seen_opt 1
+ }
+ } else {
+ puts $out $line
+ }
+ }
+ close $out
+}
+
+# Run a test
+#
+proc scenario {id title pattern} {
+ global makefilename target scene
+ if {$scene!="all" && $scene!=$id && $scene!=$title} return
+ puts "**************** $title ***************"
+ generate_makefile $pattern
+ exec make -f $makefilename clean >@stdout 2>@stdout
+ exec make -f $makefilename $target >@stdout 2>@stdout
+}
+
+###############################################################################
+# Add new scenarios here
+#
+scenario 0 {Default} {}
+scenario 1 {Debug} {
+ SQLITE_DEBUG=1
+ SQLITE_MEMDEBUG=1
+}
+scenario 2 {Everything} {
+ SQLITE_DEBUG=1
+ SQLITE_MEMDEBUG=1
+ SQLITE_ENABLE_MEMORY_MANAGEMENT=1
+ SQLITE_ENABLE_COLUMN_METADATA=1
+ SQLITE_ENABLE_LOAD_EXTENSION=1 HAVE_DLOPEN=1
+ SQLITE_ENABLE_MEMORY_MANAGEMENT=1
+}
+scenario 3 {Customer-1} {
+ SQLITE_DEBUG=1 SQLITE_MEMDEBUG=1
+ SQLITE_THREADSAFE=1 SQLITE_OS_UNIX=1
+ SQLITE_DISABLE_LFS=1
+ SQLITE_DEFAULT_AUTOVACUUM=1
+ SQLITE_DEFAULT_PAGE_SIZE=1024
+ SQLITE_MAX_PAGE_SIZE=4096
+ SQLITE_DEFAULT_CACHE_SIZE=64
+ SQLITE_DEFAULT_TEMP_CACHE_SIZE=32
+ SQLITE_TEMP_STORE=3
+ SQLITE_OMIT_PROGRESS_CALLBACK=1
+ SQLITE_OMIT_LOAD_EXTENSION=1
+ SQLITE_OMIT_VIRTUALTABLE=1
+ SQLITE_ENABLE_IOTRACE=1
+}
+scenario 4 {Small-Cache} {
+ SQLITE_DEBUG=1 SQLITE_MEMDEBUG=1
+ SQLITE_THREADSAFE=1 SQLITE_OS_UNIX=1
+ SQLITE_DEFAULT_AUTOVACUUM=1
+ SQLITE_DEFAULT_PAGE_SIZE=1024
+ SQLITE_MAX_PAGE_SIZE=2048
+ SQLITE_DEFAULT_CACHE_SIZE=13
+ SQLITE_DEFAULT_TEMP_CACHE_SIZE=11
+ SQLITE_TEMP_STORE=1
+}
diff --git a/tool/spaceanal.tcl b/tool/spaceanal.tcl
new file mode 100644
index 0000000..8fe72b9
--- /dev/null
+++ b/tool/spaceanal.tcl
@@ -0,0 +1,895 @@
+# Run this TCL script using an SQLite-enabled TCL interpreter to get a report
+# on how much disk space is used by a particular data to actually store data
+# versus how much space is unused.
+#
+# The dbstat virtual table is required.
+#
+
+if {[catch {
+
+# Argument $tname is the name of a table within the database opened by
+# database handle [db]. Return true if it is a WITHOUT ROWID table, or
+# false otherwise.
+#
+proc is_without_rowid {tname} {
+ set t [string map {' ''} $tname]
+ db eval "PRAGMA index_list = '$t'" o {
+ if {$o(origin) == "pk"} {
+ set n $o(name)
+ if {0==[db one { SELECT count(*) FROM sqlite_schema WHERE name=$n }]} {
+ return 1
+ }
+ }
+ }
+ return 0
+}
+
+# Read and run TCL commands from standard input. Used to implement
+# the --tclsh option.
+#
+proc tclsh {} {
+ set line {}
+ while {![eof stdin]} {
+ if {$line!=""} {
+ puts -nonewline "> "
+ } else {
+ puts -nonewline "% "
+ }
+ flush stdout
+ append line [gets stdin]
+ if {[info complete $line]} {
+ if {[catch {uplevel #0 $line} result]} {
+ puts stderr "Error: $result"
+ } elseif {$result!=""} {
+ puts $result
+ }
+ set line {}
+ } else {
+ append line \n
+ }
+ }
+}
+
+
+# Get the name of the database to analyze
+#
+proc usage {} {
+ set argv0 [file rootname [file tail [info nameofexecutable]]]
+ puts stderr "Usage: $argv0 ?--pageinfo? ?--stats? database-filename"
+ puts stderr {
+Analyze the SQLite3 database file specified by the "database-filename"
+argument and output a report detailing size and storage efficiency
+information for the database and its constituent tables and indexes.
+
+Options:
+
+ --pageinfo Show how each page of the database-file is used
+
+ --stats Output SQL text that creates a new database containing
+ statistics about the database that was analyzed
+
+ --tclsh Run the built-in TCL interpreter interactively (for debugging)
+
+ --version Show the version number of SQLite
+}
+ exit 1
+}
+set file_to_analyze {}
+set flags(-pageinfo) 0
+set flags(-stats) 0
+set flags(-debug) 0
+append argv {}
+foreach arg $argv {
+ if {[regexp {^-+pageinfo$} $arg]} {
+ set flags(-pageinfo) 1
+ } elseif {[regexp {^-+stats$} $arg]} {
+ set flags(-stats) 1
+ } elseif {[regexp {^-+debug$} $arg]} {
+ set flags(-debug) 1
+ } elseif {[regexp {^-+tclsh$} $arg]} {
+ tclsh
+ exit 0
+ } elseif {[regexp {^-+version$} $arg]} {
+ sqlite3 mem :memory:
+ puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}]
+ mem close
+ exit 0
+ } elseif {[regexp {^-} $arg]} {
+ puts stderr "Unknown option: $arg"
+ usage
+ } elseif {$file_to_analyze!=""} {
+ usage
+ } else {
+ set file_to_analyze $arg
+ }
+}
+if {$file_to_analyze==""} usage
+set root_filename $file_to_analyze
+regexp {^file:(//)?([^?]*)} $file_to_analyze all x1 root_filename
+if {![file exists $root_filename]} {
+ puts stderr "No such file: $root_filename"
+ exit 1
+}
+if {![file readable $root_filename]} {
+ puts stderr "File is not readable: $root_filename"
+ exit 1
+}
+set true_file_size [file size $root_filename]
+if {$true_file_size<512} {
+ puts stderr "Empty or malformed database: $root_filename"
+ exit 1
+}
+
+# Compute the total file size assuming test_multiplexor is being used.
+# Assume that SQLITE_ENABLE_8_3_NAMES might be enabled
+#
+set extension [file extension $root_filename]
+set pattern $root_filename
+append pattern {[0-3][0-9][0-9]}
+foreach f [glob -nocomplain $pattern] {
+ incr true_file_size [file size $f]
+ set extension {}
+}
+if {[string length $extension]>=2 && [string length $extension]<=4} {
+ set pattern [file rootname $root_filename]
+ append pattern {.[0-3][0-9][0-9]}
+ foreach f [glob -nocomplain $pattern] {
+ incr true_file_size [file size $f]
+ }
+}
+
+# Open the database
+#
+if {[catch {sqlite3 db $file_to_analyze -uri 1} msg]} {
+ puts stderr "error trying to open $file_to_analyze: $msg"
+ exit 1
+}
+if {$flags(-debug)} {
+ proc dbtrace {txt} {puts $txt; flush stdout;}
+ db trace ::dbtrace
+}
+
+# Make sure all required compile-time options are available
+#
+if {![db exists {SELECT 1 FROM pragma_compile_options
+ WHERE compile_options='ENABLE_DBSTAT_VTAB'}]} {
+ puts "The SQLite database engine linked with this application\
+ lacks required capabilities. Recompile using the\
+ -DSQLITE_ENABLE_DBSTAT_VTAB compile-time option to fix\
+ this problem."
+ exit 1
+}
+
+db eval {SELECT count(*) FROM sqlite_schema}
+set pageSize [expr {wide([db one {PRAGMA page_size}])}]
+
+if {$flags(-pageinfo)} {
+ db eval {CREATE VIRTUAL TABLE temp.stat USING dbstat}
+ db eval {SELECT name, path, pageno FROM temp.stat ORDER BY pageno} {
+ puts "$pageno $name $path"
+ }
+ exit 0
+}
+if {$flags(-stats)} {
+ db eval {CREATE VIRTUAL TABLE temp.stat USING dbstat}
+ puts "BEGIN;"
+ puts "CREATE TABLE stats("
+ puts " name STRING, /* Name of table or index */"
+ puts " path INTEGER, /* Path to page from root */"
+ puts " pageno INTEGER, /* Page number */"
+ puts " pagetype STRING, /* 'internal', 'leaf' or 'overflow' */"
+ puts " ncell INTEGER, /* Cells on page (0 for overflow) */"
+ puts " payload INTEGER, /* Bytes of payload on this page */"
+ puts " unused INTEGER, /* Bytes of unused space on this page */"
+ puts " mx_payload INTEGER, /* Largest payload size of all cells */"
+ puts " pgoffset INTEGER, /* Offset of page in file */"
+ puts " pgsize INTEGER /* Size of the page */"
+ puts ");"
+ db eval {SELECT quote(name) || ',' ||
+ quote(path) || ',' ||
+ quote(pageno) || ',' ||
+ quote(pagetype) || ',' ||
+ quote(ncell) || ',' ||
+ quote(payload) || ',' ||
+ quote(unused) || ',' ||
+ quote(mx_payload) || ',' ||
+ quote(pgoffset) || ',' ||
+ quote(pgsize) AS x FROM stat} {
+ puts "INSERT INTO stats VALUES($x);"
+ }
+ puts "COMMIT;"
+ exit 0
+}
+
+
+# In-memory database for collecting statistics. This script loops through
+# the tables and indices in the database being analyzed, adding a row for each
+# to an in-memory database (for which the schema is shown below). It then
+# queries the in-memory db to produce the space-analysis report.
+#
+sqlite3 mem :memory:
+if {$flags(-debug)} {
+ proc dbtrace {txt} {puts $txt; flush stdout;}
+ mem trace ::dbtrace
+}
+set tabledef {CREATE TABLE space_used(
+ name clob, -- Name of a table or index in the database file
+ tblname clob, -- Name of associated table
+ is_index boolean, -- TRUE if it is an index, false for a table
+ is_without_rowid boolean, -- TRUE if WITHOUT ROWID table
+ nentry int, -- Number of entries in the BTree
+ leaf_entries int, -- Number of leaf entries
+ depth int, -- Depth of the b-tree
+ payload int, -- Total amount of data stored in this table or index
+ ovfl_payload int, -- Total amount of data stored on overflow pages
+ ovfl_cnt int, -- Number of entries that use overflow
+ mx_payload int, -- Maximum payload size
+ int_pages int, -- Number of interior pages used
+ leaf_pages int, -- Number of leaf pages used
+ ovfl_pages int, -- Number of overflow pages used
+ int_unused int, -- Number of unused bytes on interior pages
+ leaf_unused int, -- Number of unused bytes on primary pages
+ ovfl_unused int, -- Number of unused bytes on overflow pages
+ gap_cnt int, -- Number of gaps in the page layout
+ compressed_size int -- Total bytes stored on disk
+);}
+mem eval $tabledef
+
+# Create a temporary "dbstat" virtual table.
+#
+db eval {CREATE VIRTUAL TABLE temp.stat USING dbstat}
+db eval {CREATE TEMP TABLE dbstat AS SELECT * FROM temp.stat
+ ORDER BY name, path}
+db eval {DROP TABLE temp.stat}
+
+set isCompressed 0
+set compressOverhead 0
+set depth 0
+set sql { SELECT name, tbl_name FROM sqlite_schema WHERE rootpage>0 }
+foreach {name tblname} [concat sqlite_schema sqlite_schema [db eval $sql]] {
+
+ set is_index [expr {$name!=$tblname}]
+ set is_without_rowid [is_without_rowid $name]
+ db eval {
+ SELECT
+ sum(ncell) AS nentry,
+ sum((pagetype=='leaf')*ncell) AS leaf_entries,
+ sum(payload) AS payload,
+ sum((pagetype=='overflow') * payload) AS ovfl_payload,
+ sum(path LIKE '%+000000') AS ovfl_cnt,
+ max(mx_payload) AS mx_payload,
+ sum(pagetype=='internal') AS int_pages,
+ sum(pagetype=='leaf') AS leaf_pages,
+ sum(pagetype=='overflow') AS ovfl_pages,
+ sum((pagetype=='internal') * unused) AS int_unused,
+ sum((pagetype=='leaf') * unused) AS leaf_unused,
+ sum((pagetype=='overflow') * unused) AS ovfl_unused,
+ sum(pgsize) AS compressed_size,
+ max((length(CASE WHEN path LIKE '%+%' THEN '' ELSE path END)+3)/4)
+ AS depth
+ FROM temp.dbstat WHERE name = $name
+ } break
+
+ set total_pages [expr {$leaf_pages+$int_pages+$ovfl_pages}]
+ set storage [expr {$total_pages*$pageSize}]
+ if {!$isCompressed && $storage>$compressed_size} {
+ set isCompressed 1
+ set compressOverhead 14
+ }
+
+ # Column 'gap_cnt' is set to the number of non-contiguous entries in the
+ # list of pages visited if the b-tree structure is traversed in a top-down
+ # fashion (each node visited before its child-tree is passed). Any overflow
+ # chains present are traversed from start to finish before any child-tree
+ # is.
+ #
+ set gap_cnt 0
+ set prev 0
+ db eval {
+ SELECT pageno, pagetype FROM temp.dbstat
+ WHERE name=$name
+ ORDER BY pageno
+ } {
+ if {$prev>0 && $pagetype=="leaf" && $pageno!=$prev+1} {
+ incr gap_cnt
+ }
+ set prev $pageno
+ }
+ mem eval {
+ INSERT INTO space_used VALUES(
+ $name,
+ $tblname,
+ $is_index,
+ $is_without_rowid,
+ $nentry,
+ $leaf_entries,
+ $depth,
+ $payload,
+ $ovfl_payload,
+ $ovfl_cnt,
+ $mx_payload,
+ $int_pages,
+ $leaf_pages,
+ $ovfl_pages,
+ $int_unused,
+ $leaf_unused,
+ $ovfl_unused,
+ $gap_cnt,
+ $compressed_size
+ );
+ }
+}
+
+proc integerify {real} {
+ if {[string is double -strict $real]} {
+ return [expr {wide($real)}]
+ } else {
+ return 0
+ }
+}
+mem function int integerify
+
+# Quote a string for use in an SQL query. Examples:
+#
+# [quote {hello world}] == {'hello world'}
+# [quote {hello world's}] == {'hello world''s'}
+#
+proc quote {txt} {
+ return [string map {' ''} $txt]
+}
+
+# Output a title line
+#
+proc titleline {title} {
+ if {$title==""} {
+ puts [string repeat * 79]
+ } else {
+ set len [string length $title]
+ set stars [string repeat * [expr 79-$len-5]]
+ puts "*** $title $stars"
+ }
+}
+
+# Generate a single line of output in the statistics section of the
+# report.
+#
+proc statline {title value {extra {}}} {
+ set len [string length $title]
+ set dots [string repeat . [expr 50-$len]]
+ set len [string length $value]
+ set sp2 [string range { } $len end]
+ if {$extra ne ""} {
+ set extra " $extra"
+ }
+ puts "$title$dots $value$sp2$extra"
+}
+
+# Generate a formatted percentage value for $num/$denom
+#
+proc percent {num denom {of {}}} {
+ if {$denom==0.0} {return ""}
+ set v [expr {$num*100.0/$denom}]
+ set of {}
+ if {$v==100.0 || $v<0.001 || ($v>1.0 && $v<99.0)} {
+ return [format {%5.1f%% %s} $v $of]
+ } elseif {$v<0.1 || $v>99.9} {
+ return [format {%7.3f%% %s} $v $of]
+ } else {
+ return [format {%6.2f%% %s} $v $of]
+ }
+}
+
+proc divide {num denom} {
+ if {$denom==0} {return 0.0}
+ return [format %.2f [expr double($num)/double($denom)]]
+}
+
+# Generate a subreport that covers some subset of the database.
+# the $where clause determines which subset to analyze.
+#
+proc subreport {title where showFrag} {
+ global pageSize file_pgcnt compressOverhead
+
+ # Query the in-memory database for the sum of various statistics
+ # for the subset of tables/indices identified by the WHERE clause in
+ # $where. Note that even if the WHERE clause matches no rows, the
+ # following query returns exactly one row (because it is an aggregate).
+ #
+ # The results of the query are stored directly by SQLite into local
+ # variables (i.e. $nentry, $payload etc.).
+ #
+ mem eval "
+ SELECT
+ int(sum(
+ CASE WHEN (is_without_rowid OR is_index) THEN nentry
+ ELSE leaf_entries
+ END
+ )) AS nentry,
+ int(sum(payload)) AS payload,
+ int(sum(ovfl_payload)) AS ovfl_payload,
+ max(mx_payload) AS mx_payload,
+ int(sum(ovfl_cnt)) as ovfl_cnt,
+ int(sum(leaf_pages)) AS leaf_pages,
+ int(sum(int_pages)) AS int_pages,
+ int(sum(ovfl_pages)) AS ovfl_pages,
+ int(sum(leaf_unused)) AS leaf_unused,
+ int(sum(int_unused)) AS int_unused,
+ int(sum(ovfl_unused)) AS ovfl_unused,
+ int(sum(gap_cnt)) AS gap_cnt,
+ int(sum(compressed_size)) AS compressed_size,
+ int(max(depth)) AS depth,
+ count(*) AS cnt
+ FROM space_used WHERE $where" {} {}
+
+ # Output the sub-report title, nicely decorated with * characters.
+ #
+ puts ""
+ titleline $title
+ puts ""
+
+ # Calculate statistics and store the results in TCL variables, as follows:
+ #
+ # total_pages: Database pages consumed.
+ # total_pages_percent: Pages consumed as a percentage of the file.
+ # storage: Bytes consumed.
+ # payload_percent: Payload bytes used as a percentage of $storage.
+ # total_unused: Unused bytes on pages.
+ # avg_payload: Average payload per btree entry.
+ # avg_fanout: Average fanout for internal pages.
+ # avg_unused: Average unused bytes per btree entry.
+ # avg_meta: Average metadata overhead per entry.
+ # ovfl_cnt_percent: Percentage of btree entries that use overflow pages.
+ #
+ set total_pages [expr {$leaf_pages+$int_pages+$ovfl_pages}]
+ set total_pages_percent [percent $total_pages $file_pgcnt]
+ set storage [expr {$total_pages*$pageSize}]
+ set payload_percent [percent $payload $storage {of storage consumed}]
+ set total_unused [expr {$ovfl_unused+$int_unused+$leaf_unused}]
+ set avg_payload [divide $payload $nentry]
+ set avg_unused [divide $total_unused $nentry]
+ set total_meta [expr {$storage - $payload - $total_unused}]
+ set total_meta [expr {$total_meta + 4*($ovfl_pages - $ovfl_cnt)}]
+ set meta_percent [percent $total_meta $storage {of metadata}]
+ set avg_meta [divide $total_meta $nentry]
+ if {$int_pages>0} {
+ # TODO: Is this formula correct?
+ set nTab [mem eval "
+ SELECT count(*) FROM (
+ SELECT DISTINCT tblname FROM space_used WHERE $where AND is_index=0
+ )
+ "]
+ set avg_fanout [mem eval "
+ SELECT (sum(leaf_pages+int_pages)-$nTab)/sum(int_pages) FROM space_used
+ WHERE $where
+ "]
+ set avg_fanout [format %.2f $avg_fanout]
+ }
+ set ovfl_cnt_percent [percent $ovfl_cnt $nentry {of all entries}]
+
+ # Print out the sub-report statistics.
+ #
+ statline {Percentage of total database} $total_pages_percent
+ statline {Number of entries} $nentry
+ statline {Bytes of storage consumed} $storage
+ if {$compressed_size!=$storage} {
+ set compressed_size [expr {$compressed_size+$compressOverhead*$total_pages}]
+ set pct [expr {$compressed_size*100.0/$storage}]
+ set pct [format {%5.1f%%} $pct]
+ statline {Bytes used after compression} $compressed_size $pct
+ }
+ statline {Bytes of payload} $payload $payload_percent
+ statline {Bytes of metadata} $total_meta $meta_percent
+ if {$cnt==1} {statline {B-tree depth} $depth}
+ statline {Average payload per entry} $avg_payload
+ statline {Average unused bytes per entry} $avg_unused
+ statline {Average metadata per entry} $avg_meta
+ if {[info exists avg_fanout]} {
+ statline {Average fanout} $avg_fanout
+ }
+ if {$showFrag && $total_pages>1} {
+ set fragmentation [percent $gap_cnt [expr {$total_pages-1}]]
+ statline {Non-sequential pages} $gap_cnt $fragmentation
+ }
+ statline {Maximum payload per entry} $mx_payload
+ statline {Entries that use overflow} $ovfl_cnt $ovfl_cnt_percent
+ if {$int_pages>0} {
+ statline {Index pages used} $int_pages
+ }
+ statline {Primary pages used} $leaf_pages
+ statline {Overflow pages used} $ovfl_pages
+ statline {Total pages used} $total_pages
+ if {$int_unused>0} {
+ set int_unused_percent [
+ percent $int_unused [expr {$int_pages*$pageSize}] {of index space}]
+ statline "Unused bytes on index pages" $int_unused $int_unused_percent
+ }
+ statline "Unused bytes on primary pages" $leaf_unused [
+ percent $leaf_unused [expr {$leaf_pages*$pageSize}] {of primary space}]
+ statline "Unused bytes on overflow pages" $ovfl_unused [
+ percent $ovfl_unused [expr {$ovfl_pages*$pageSize}] {of overflow space}]
+ statline "Unused bytes on all pages" $total_unused [
+ percent $total_unused $storage {of all space}]
+ return 1
+}
+
+# Calculate the overhead in pages caused by auto-vacuum.
+#
+# This procedure calculates and returns the number of pages used by the
+# auto-vacuum 'pointer-map'. If the database does not support auto-vacuum,
+# then 0 is returned. The two arguments are the size of the database file in
+# pages and the page size used by the database (in bytes).
+proc autovacuum_overhead {filePages pageSize} {
+
+ # Set $autovacuum to non-zero for databases that support auto-vacuum.
+ set autovacuum [db one {PRAGMA auto_vacuum}]
+
+ # If the database is not an auto-vacuum database or the file consists
+ # of one page only then there is no overhead for auto-vacuum. Return zero.
+ if {0==$autovacuum || $filePages==1} {
+ return 0
+ }
+
+ # The number of entries on each pointer map page. The layout of the
+ # database file is one pointer-map page, followed by $ptrsPerPage other
+ # pages, followed by a pointer-map page etc. The first pointer-map page
+ # is the second page of the file overall.
+ set ptrsPerPage [expr double($pageSize/5)]
+
+ # Return the number of pointer map pages in the database.
+ return [expr wide(ceil( ($filePages-1.0)/($ptrsPerPage+1.0) ))]
+}
+
+
+# Calculate the summary statistics for the database and store the results
+# in TCL variables. They are output below. Variables are as follows:
+#
+# pageSize: Size of each page in bytes.
+# file_bytes: File size in bytes.
+# file_pgcnt: Number of pages in the file.
+# file_pgcnt2: Number of pages in the file (calculated).
+# av_pgcnt: Pages consumed by the auto-vacuum pointer-map.
+# av_percent: Percentage of the file consumed by auto-vacuum pointer-map.
+# inuse_pgcnt: Data pages in the file.
+# inuse_percent: Percentage of pages used to store data.
+# free_pgcnt: Free pages calculated as (<total pages> - <in-use pages>)
+# free_pgcnt2: Free pages in the file according to the file header.
+# free_percent: Percentage of file consumed by free pages (calculated).
+# free_percent2: Percentage of file consumed by free pages (header).
+# ntable: Number of tables in the db.
+# nindex: Number of indices in the db.
+# nautoindex: Number of indices created automatically.
+# nmanindex: Number of indices created manually.
+# user_payload: Number of bytes of payload in table btrees
+# (not including sqlite_schema)
+# user_percent: $user_payload as a percentage of total file size.
+
+### The following, setting $file_bytes based on the actual size of the file
+### on disk, causes this tool to choke on zipvfs databases. So set it based
+### on the return of [PRAGMA page_count] instead.
+if 0 {
+ set file_bytes [file size $file_to_analyze]
+ set file_pgcnt [expr {$file_bytes/$pageSize}]
+}
+set file_pgcnt [db one {PRAGMA page_count}]
+set file_bytes [expr {$file_pgcnt * $pageSize}]
+
+set av_pgcnt [autovacuum_overhead $file_pgcnt $pageSize]
+set av_percent [percent $av_pgcnt $file_pgcnt]
+
+set sql {SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM space_used}
+set inuse_pgcnt [expr wide([mem eval $sql])]
+set inuse_percent [percent $inuse_pgcnt $file_pgcnt]
+
+set free_pgcnt [expr {$file_pgcnt-$inuse_pgcnt-$av_pgcnt}]
+if {$file_bytes>1073741824 && $free_pgcnt>0} {incr free_pgcnt -1}
+set free_percent [percent $free_pgcnt $file_pgcnt]
+set free_pgcnt2 [db one {PRAGMA freelist_count}]
+set free_percent2 [percent $free_pgcnt2 $file_pgcnt]
+
+set file_pgcnt2 [expr {$inuse_pgcnt+$free_pgcnt2+$av_pgcnt}]
+
+# Account for the lockbyte page
+if {$file_pgcnt2*$pageSize>1073742335} {incr file_pgcnt2}
+
+set ntable [db eval {SELECT count(*)+1 FROM sqlite_schema WHERE type='table'}]
+set nindex [db eval {SELECT count(*) FROM sqlite_schema WHERE type='index'}]
+set sql {SELECT count(*) FROM sqlite_schema WHERE name LIKE 'sqlite_autoindex%'}
+set nautoindex [db eval $sql]
+set nmanindex [expr {$nindex-$nautoindex}]
+
+# set total_payload [mem eval "SELECT sum(payload) FROM space_used"]
+set user_payload [mem one {SELECT int(sum(payload)) FROM space_used
+ WHERE NOT is_index AND name NOT LIKE 'sqlite_schema'}]
+set user_percent [percent $user_payload $file_bytes]
+
+# Output the summary statistics calculated above.
+#
+puts "/** Disk-Space Utilization Report For $root_filename"
+puts ""
+statline {Page size in bytes} $pageSize
+statline {Pages in the whole file (measured)} $file_pgcnt
+statline {Pages in the whole file (calculated)} $file_pgcnt2
+statline {Pages that store data} $inuse_pgcnt $inuse_percent
+statline {Pages on the freelist (per header)} $free_pgcnt2 $free_percent2
+statline {Pages on the freelist (calculated)} $free_pgcnt $free_percent
+statline {Pages of auto-vacuum overhead} $av_pgcnt $av_percent
+statline {Number of tables in the database} $ntable
+statline {Number of indices} $nindex
+statline {Number of defined indices} $nmanindex
+statline {Number of implied indices} $nautoindex
+if {$isCompressed} {
+ statline {Size of uncompressed content in bytes} $file_bytes
+ set efficiency [percent $true_file_size $file_bytes]
+ statline {Size of compressed file on disk} $true_file_size $efficiency
+} else {
+ statline {Size of the file in bytes} $file_bytes
+}
+statline {Bytes of user payload stored} $user_payload $user_percent
+
+# Output table rankings
+#
+puts ""
+titleline "Page counts for all tables with their indices"
+puts ""
+mem eval {SELECT tblname, count(*) AS cnt,
+ int(sum(int_pages+leaf_pages+ovfl_pages)) AS size
+ FROM space_used GROUP BY tblname ORDER BY size+0 DESC, tblname} {} {
+ statline [string toupper $tblname] $size [percent $size $file_pgcnt]
+}
+puts ""
+titleline "Page counts for all tables and indices separately"
+puts ""
+mem eval {
+ SELECT
+ upper(name) AS nm,
+ int(int_pages+leaf_pages+ovfl_pages) AS size
+ FROM space_used
+ ORDER BY size+0 DESC, name} {} {
+ statline $nm $size [percent $size $file_pgcnt]
+}
+if {$isCompressed} {
+ puts ""
+ titleline "Bytes of disk space used after compression"
+ puts ""
+ set csum 0
+ mem eval {SELECT tblname,
+ int(sum(compressed_size)) +
+ $compressOverhead*sum(int_pages+leaf_pages+ovfl_pages)
+ AS csize
+ FROM space_used GROUP BY tblname ORDER BY csize+0 DESC, tblname} {} {
+ incr csum $csize
+ statline [string toupper $tblname] $csize [percent $csize $true_file_size]
+ }
+ set overhead [expr {$true_file_size - $csum}]
+ if {$overhead>0} {
+ statline {Header and free space} $overhead [percent $overhead $true_file_size]
+ }
+}
+
+# Output subreports
+#
+if {$nindex>0} {
+ subreport {All tables and indices} 1 0
+}
+subreport {All tables} {NOT is_index} 0
+if {$nindex>0} {
+ subreport {All indices} {is_index} 0
+}
+foreach tbl [mem eval {SELECT DISTINCT tblname name FROM space_used
+ ORDER BY name}] {
+ set qn [quote $tbl]
+ set name [string toupper $tbl]
+ set n [mem eval {SELECT count(*) FROM space_used WHERE tblname=$tbl}]
+ if {$n>1} {
+ set idxlist [mem eval "SELECT name FROM space_used
+ WHERE tblname='$qn' AND is_index
+ ORDER BY 1"]
+ subreport "Table $name and all its indices" "tblname='$qn'" 0
+ subreport "Table $name w/o any indices" "name='$qn'" 1
+ if {[llength $idxlist]>1} {
+ subreport "Indices of table $name" "tblname='$qn' AND is_index" 0
+ }
+ foreach idx $idxlist {
+ set qidx [quote $idx]
+ subreport "Index [string toupper $idx] of table $name" "name='$qidx'" 1
+ }
+ } else {
+ subreport "Table $name" "name='$qn'" 1
+ }
+}
+
+# Output instructions on what the numbers above mean.
+#
+puts ""
+titleline Definitions
+puts {
+Page size in bytes
+
+ The number of bytes in a single page of the database file.
+ Usually 1024.
+
+Number of pages in the whole file
+}
+puts " The number of $pageSize-byte pages that go into forming the complete
+ database"
+puts {
+Pages that store data
+
+ The number of pages that store data, either as primary B*Tree pages or
+ as overflow pages. The number at the right is the data pages divided by
+ the total number of pages in the file.
+
+Pages on the freelist
+
+ The number of pages that are not currently in use but are reserved for
+ future use. The percentage at the right is the number of freelist pages
+ divided by the total number of pages in the file.
+
+Pages of auto-vacuum overhead
+
+ The number of pages that store data used by the database to facilitate
+ auto-vacuum. This is zero for databases that do not support auto-vacuum.
+
+Number of tables in the database
+
+ The number of tables in the database, including the SQLITE_SCHEMA table
+ used to store schema information.
+
+Number of indices
+
+ The total number of indices in the database.
+
+Number of defined indices
+
+ The number of indices created using an explicit CREATE INDEX statement.
+
+Number of implied indices
+
+ The number of indices used to implement PRIMARY KEY or UNIQUE constraints
+ on tables.
+
+Size of the file in bytes
+
+ The total amount of disk space used by the entire database files.
+
+Bytes of user payload stored
+
+ The total number of bytes of user payload stored in the database. The
+ schema information in the SQLITE_SCHEMA table is not counted when
+ computing this number. The percentage at the right shows the payload
+ divided by the total file size.
+
+Percentage of total database
+
+ The amount of the complete database file that is devoted to storing
+ information described by this category.
+
+Number of entries
+
+ The total number of B-Tree key/value pairs stored under this category.
+
+Bytes of storage consumed
+
+ The total amount of disk space required to store all B-Tree entries
+ under this category. The is the total number of pages used times
+ the pages size.
+
+Bytes of payload
+
+ The amount of payload stored under this category. Payload is the data
+ part of table entries and the key part of index entries. The percentage
+ at the right is the bytes of payload divided by the bytes of storage
+ consumed.
+
+Bytes of metadata
+
+ The amount of formatting and structural information stored in the
+ table or index. Metadata includes the btree page header, the cell pointer
+ array, the size field for each cell, the left child pointer or non-leaf
+ cells, the overflow pointers for overflow cells, and the rowid value for
+ rowid table cells. In other words, metadata is everything that is neither
+ unused space nor content. The record header in the payload is counted as
+ content, not metadata.
+
+Average payload per entry
+
+ The average amount of payload on each entry. This is just the bytes of
+ payload divided by the number of entries.
+
+Average unused bytes per entry
+
+ The average amount of free space remaining on all pages under this
+ category on a per-entry basis. This is the number of unused bytes on
+ all pages divided by the number of entries.
+
+Non-sequential pages
+
+ The number of pages in the table or index that are out of sequence.
+ Many filesystems are optimized for sequential file access so a small
+ number of non-sequential pages might result in faster queries,
+ especially for larger database files that do not fit in the disk cache.
+ Note that after running VACUUM, the root page of each table or index is
+ at the beginning of the database file and all other pages are in a
+ separate part of the database file, resulting in a single non-
+ sequential page.
+
+Maximum payload per entry
+
+ The largest payload size of any entry.
+
+Entries that use overflow
+
+ The number of entries that user one or more overflow pages.
+
+Total pages used
+
+ This is the number of pages used to hold all information in the current
+ category. This is the sum of index, primary, and overflow pages.
+
+Index pages used
+
+ This is the number of pages in a table B-tree that hold only key (rowid)
+ information and no data.
+
+Primary pages used
+
+ This is the number of B-tree pages that hold both key and data.
+
+Overflow pages used
+
+ The total number of overflow pages used for this category.
+
+Unused bytes on index pages
+
+ The total number of bytes of unused space on all index pages. The
+ percentage at the right is the number of unused bytes divided by the
+ total number of bytes on index pages.
+
+Unused bytes on primary pages
+
+ The total number of bytes of unused space on all primary pages. The
+ percentage at the right is the number of unused bytes divided by the
+ total number of bytes on primary pages.
+
+Unused bytes on overflow pages
+
+ The total number of bytes of unused space on all overflow pages. The
+ percentage at the right is the number of unused bytes divided by the
+ total number of bytes on overflow pages.
+
+Unused bytes on all pages
+
+ The total number of bytes of unused space on all primary and overflow
+ pages. The percentage at the right is the number of unused bytes
+ divided by the total number of bytes.
+}
+
+# Output a dump of the in-memory database. This can be used for more
+# complex offline analysis.
+#
+titleline {}
+puts "The entire text of this report can be sourced into any SQL database"
+puts "engine for further analysis. All of the text above is an SQL comment."
+puts "The data used to generate this report follows:"
+puts "*/"
+puts "BEGIN;"
+puts $tabledef
+unset -nocomplain x
+mem eval {SELECT * FROM space_used} x {
+ puts -nonewline "INSERT INTO space_used VALUES"
+ set sep (
+ foreach col $x(*) {
+ set v $x($col)
+ if {$v=="" || ![string is double $v]} {set v '[quote $v]'}
+ puts -nonewline $sep$v
+ set sep ,
+ }
+ puts ");"
+}
+puts "COMMIT;"
+
+} err]} {
+ puts "ERROR: $err"
+ puts $errorInfo
+ exit 1
+}
diff --git a/tool/speed-check.sh b/tool/speed-check.sh
new file mode 100644
index 0000000..4cc2579
--- /dev/null
+++ b/tool/speed-check.sh
@@ -0,0 +1,219 @@
+#!/bin/bash
+#
+# This is a template for a script used for day-to-day size and
+# performance monitoring of SQLite. Typical usage:
+#
+# sh speed-check.sh trunk # Baseline measurement of trunk
+# sh speed-check.sh x1 # Measure some experimental change
+# fossil xdiff --tk cout-trunk.txt cout-x1.txt # View chanages
+#
+# There are multiple output files, all with a base name given by
+# the first argument:
+#
+# summary-$BASE.txt # Copy of standard output
+# cout-$BASE.txt # cachegrind output
+# explain-$BASE.txt # EXPLAIN listings (only with --explain)
+#
+if test "$1" = ""
+then
+ echo "Usage: $0 OUTPUTFILE [OPTIONS]"
+ exit
+fi
+NAME=$1
+shift
+#CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5"
+CC_OPTS="-DSQLITE_ENABLE_MEMSYS5"
+CC=gcc
+SPEEDTEST_OPTS="--shrink-memory --reprepare --stats --heap 10000000 64"
+SIZE=5
+LEAN_OPTS="-DSQLITE_THREADSAFE=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_MEMSTATUS=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_LIKE_DOESNT_MATCH_BLOBS"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_MAX_EXPR_DEPTH=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DECLTYPE"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DEPRECATED"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_PROGRESS_CALLBACK"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_SHARED_CACHE"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA"
+BASELINE="trunk"
+doExplain=0
+doCachegrind=1
+doVdbeProfile=0
+doWal=1
+doDiff=1
+while test "$1" != ""; do
+ case $1 in
+ --nodiff)
+ doDiff=0
+ ;;
+ --reprepare)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --autovacuum)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --utf16be)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --stats)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --without-rowid)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --strict)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --nomemstat)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --multithread)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --singlethread)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --serialized)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
+ ;;
+ --temp)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --temp 6"
+ ;;
+ --legacy)
+ doWal=0
+ CC_OPTS="$CC_OPTS -DSPEEDTEST_OMIT_HASH"
+ ;;
+ --verify)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --verify"
+ ;;
+ --wal)
+ doWal=1
+ ;;
+ --size)
+ shift; SIZE=$1
+ ;;
+ --cachesize)
+ shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --cachesize $1"
+ ;;
+ --stmtcache)
+ shift; SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtcache $1"
+ ;;
+ --nolongdouble)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --nolongdouble"
+ ;;
+ --checkpoint)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --checkpoint"
+ ;;
+ --explain)
+ doExplain=1
+ ;;
+ --vdbeprofile)
+ rm -f vdbe_profile.out
+ CC_OPTS="$CC_OPTS -DVDBE_PROFILE"
+ doCachegrind=0
+ doVdbeProfile=1
+ ;;
+ --lean)
+ CC_OPTS="$CC_OPTS $LEAN_OPTS"
+ ;;
+ --clang)
+ CC=clang
+ ;;
+ --icc)
+ CC=/home/drh/intel/bin/icc
+ ;;
+ --gcc7)
+ CC=gcc-7
+ ;;
+ --heap)
+ CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_MEMSYS5"
+ shift;
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --heap $1 64"
+ ;;
+ --lookaside)
+ shift;
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --lookaside $1 $2"
+ shift;
+ ;;
+ --repeat)
+ CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_RCACHE"
+ shift;
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --repeat $1"
+ ;;
+ --mmap)
+ shift;
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --mmap $1"
+ ;;
+ --rtree)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset rtree"
+ CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_RTREE"
+ ;;
+ --persist)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --persist"
+ ;;
+ --orm)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset orm"
+ ;;
+ --cte)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset cte"
+ ;;
+ --fp)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset fp"
+ ;;
+ --stmtscanstatus)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --stmtscanstatus"
+ ;;
+ -*)
+ CC_OPTS="$CC_OPTS $1"
+ ;;
+ *)
+ BASELINE=$1
+ ;;
+ esac
+ shift
+done
+if test $doWal -eq 1; then
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal"
+fi
+SPEEDTEST_OPTS="$SPEEDTEST_OPTS --size $SIZE"
+echo "NAME = $NAME" | tee summary-$NAME.txt
+echo "SPEEDTEST_OPTS = $SPEEDTEST_OPTS" | tee -a summary-$NAME.txt
+echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt
+rm -f cachegrind.out.* speedtest1 speedtest1.db sqlite3.o
+if test $doVdbeProfile -eq 1; then
+ rm -f vdbe_profile.out
+fi
+$CC -g -Os -Wall -I. $CC_OPTS -c sqlite3.c
+size sqlite3.o | tee -a summary-$NAME.txt
+if test $doExplain -eq 1; then
+ $CC -g -Os -Wall -I. $CC_OPTS \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ ./shell.c ./sqlite3.c -o sqlite3 -ldl -lpthread
+fi
+SRC=./speedtest1.c
+$CC -g -Os -Wall -I. $CC_OPTS $SRC ./sqlite3.o -o speedtest1 -ldl -lpthread
+ls -l speedtest1 | tee -a summary-$NAME.txt
+if test $doCachegrind -eq 1; then
+ valgrind --tool=cachegrind ./speedtest1 speedtest1.db \
+ $SPEEDTEST_OPTS 2>&1 | tee -a summary-$NAME.txt
+else
+ ./speedtest1 speedtest1.db $SPEEDTEST_OPTS 2>&1 | tee -a summary-$NAME.txt
+fi
+size sqlite3.o | tee -a summary-$NAME.txt
+wc sqlite3.c
+if test $doCachegrind -eq 1; then
+ cg_anno.tcl cachegrind.out.* >cout-$NAME.txt
+ echo '*****************************************************' >>cout-$NAME.txt
+ sed 's/^[0-9=-]\{9\}/==00000==/' summary-$NAME.txt >>cout-$NAME.txt
+fi
+if test $doExplain -eq 1; then
+ ./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt
+fi
+if test $doVdbeProfile -eq 1; then
+ tclsh ../sqlite/tool/vdbe_profile.tcl >vdbeprofile-$NAME.txt
+ open vdbeprofile-$NAME.txt
+fi
+if test "$NAME" != "$BASELINE" -a $doVdbeProfile -ne 1 -a $doDiff -ne 0; then
+ fossil test-diff --tk -c 20 cout-$BASELINE.txt cout-$NAME.txt
+fi
diff --git a/tool/speedtest.tcl b/tool/speedtest.tcl
new file mode 100644
index 0000000..ef39dc5
--- /dev/null
+++ b/tool/speedtest.tcl
@@ -0,0 +1,275 @@
+#!/usr/bin/tclsh
+#
+# Run this script using TCLSH to do a speed comparison between
+# various versions of SQLite and PostgreSQL and MySQL
+#
+
+# Run a test
+#
+set cnt 1
+proc runtest {title} {
+ global cnt
+ set sqlfile test$cnt.sql
+ puts "<h2>Test $cnt: $title</h2>"
+ incr cnt
+ set fd [open $sqlfile r]
+ set sql [string trim [read $fd [file size $sqlfile]]]
+ close $fd
+ set sx [split $sql \n]
+ set n [llength $sx]
+ if {$n>8} {
+ set sql {}
+ for {set i 0} {$i<3} {incr i} {append sql [lindex $sx $i]<br>\n}
+ append sql "<i>... [expr {$n-6}] lines omitted</i><br>\n"
+ for {set i [expr {$n-3}]} {$i<$n} {incr i} {
+ append sql [lindex $sx $i]<br>\n
+ }
+ } else {
+ regsub -all \n [string trim $sql] <br> sql
+ }
+ puts "<blockquote>"
+ puts "$sql"
+ puts "</blockquote><table border=0 cellpadding=0 cellspacing=0>"
+ set format {<tr><td>%s</td><td align="right">&nbsp;&nbsp;&nbsp;%.3f</td></tr>}
+ set delay 1000
+# exec sync; after $delay;
+# set t [time "exec psql drh <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format PostgreSQL: $t]
+ exec sync; after $delay;
+ set t [time "exec mysql -f drh <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format MySQL: $t]
+# set t [time "exec ./sqlite232 s232.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.3.2:} $t]
+# set t [time "exec ./sqlite-100 s100.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.4 (cache=100):} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite248 s2k.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4.8:} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite248 sns.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4.8 (nosync):} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite2412 s2kb.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4.12:} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite2412 snsb.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4.12 (nosync):} $t]
+# set t [time "exec ./sqlite-t1 st1.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.4 (test):} $t]
+ puts "</table>"
+}
+
+# Initialize the environment
+#
+expr srand(1)
+catch {exec /bin/sh -c {rm -f s*.db}}
+set fd [open clear.sql w]
+puts $fd {
+ drop table t1;
+ drop table t2;
+}
+close $fd
+catch {exec psql drh <clear.sql}
+catch {exec mysql drh <clear.sql}
+set fd [open 2kinit.sql w]
+puts $fd {
+ PRAGMA default_cache_size=2000;
+ PRAGMA default_synchronous=on;
+}
+close $fd
+exec ./sqlite248 s2k.db <2kinit.sql
+exec ./sqlite2412 s2kb.db <2kinit.sql
+set fd [open nosync-init.sql w]
+puts $fd {
+ PRAGMA default_cache_size=2000;
+ PRAGMA default_synchronous=off;
+}
+close $fd
+exec ./sqlite248 sns.db <nosync-init.sql
+exec ./sqlite2412 snsb.db <nosync-init.sql
+set ones {zero one two three four five six seven eight nine
+ ten eleven twelve thirteen fourteen fifteen sixteen seventeen
+ eighteen nineteen}
+set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
+proc number_name {n} {
+ if {$n>=1000} {
+ set txt "[number_name [expr {$n/1000}]] thousand"
+ set n [expr {$n%1000}]
+ } else {
+ set txt {}
+ }
+ if {$n>=100} {
+ append txt " [lindex $::ones [expr {$n/100}]] hundred"
+ set n [expr {$n%100}]
+ }
+ if {$n>=20} {
+ append txt " [lindex $::tens [expr {$n/10}]]"
+ set n [expr {$n%10}]
+ }
+ if {$n>0} {
+ append txt " [lindex $::ones $n]"
+ }
+ set txt [string trim $txt]
+ if {$txt==""} {set txt zero}
+ return $txt
+}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd "CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100));"
+for {set i 1} {$i<=1000} {incr i} {
+ set r [expr {int(rand()*100000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+close $fd
+runtest {1000 INSERTs}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+puts $fd "CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100));"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t2 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+
+set fd [open test$cnt.sql w]
+for {set i 0} {$i<100} {incr i} {
+ set lwr [expr {$i*100}]
+ set upr [expr {($i+10)*100}]
+ puts $fd "SELECT count(*), avg(b) FROM t2 WHERE b>=$lwr AND b<$upr;"
+}
+close $fd
+runtest {100 SELECTs without an index}
+
+
+
+set fd [open test$cnt.sql w]
+for {set i 1} {$i<=100} {incr i} {
+ puts $fd "SELECT count(*), avg(b) FROM t2 WHERE c LIKE '%[number_name $i]%';"
+}
+close $fd
+runtest {100 SELECTs on a string comparison}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {CREATE INDEX i2a ON t2(a);}
+puts $fd {CREATE INDEX i2b ON t2(b);}
+close $fd
+runtest {Creating an index}
+
+
+
+set fd [open test$cnt.sql w]
+for {set i 0} {$i<5000} {incr i} {
+ set lwr [expr {$i*100}]
+ set upr [expr {($i+1)*100}]
+ puts $fd "SELECT count(*), avg(b) FROM t2 WHERE b>=$lwr AND b<$upr;"
+}
+close $fd
+runtest {5000 SELECTs with an index}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 0} {$i<1000} {incr i} {
+ set lwr [expr {$i*10}]
+ set upr [expr {($i+1)*10}]
+ puts $fd "UPDATE t1 SET b=b*2 WHERE a>=$lwr AND a<$upr;"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {1000 UPDATEs without an index}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "UPDATE t2 SET b=$r WHERE a=$i;"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 UPDATEs with an index}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "UPDATE t2 SET c='[number_name $r]' WHERE a=$i;"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 text UPDATEs with an index}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+puts $fd "INSERT INTO t1 SELECT * FROM t2;"
+puts $fd "INSERT INTO t2 SELECT * FROM t1;"
+puts $fd "COMMIT;"
+close $fd
+runtest {INSERTs from a SELECT}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {DELETE FROM t2 WHERE c LIKE '%fifty%';}
+close $fd
+runtest {DELETE without an index}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {DELETE FROM t2 WHERE a>10 AND a<20000;}
+close $fd
+runtest {DELETE with an index}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {INSERT INTO t2 SELECT * FROM t1;}
+close $fd
+runtest {A big INSERT after a big DELETE}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {BEGIN;}
+puts $fd {DELETE FROM t1;}
+for {set i 1} {$i<=3000} {incr i} {
+ set r [expr {int(rand()*100000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd {COMMIT;}
+close $fd
+runtest {A big DELETE followed by many small INSERTs}
+
+
+
+set fd [open test$cnt.sql w]
+puts $fd {DROP TABLE t1;}
+puts $fd {DROP TABLE t2;}
+close $fd
+runtest {DROP TABLE}
diff --git a/tool/speedtest16.c b/tool/speedtest16.c
new file mode 100644
index 0000000..993cc19
--- /dev/null
+++ b/tool/speedtest16.c
@@ -0,0 +1,171 @@
+/*
+** Performance test for SQLite.
+**
+** This program reads ASCII text from a file named on the command-line.
+** It converts each SQL statement into UTF16 and submits it to SQLite
+** for evaluation. A new UTF16 database is created at the beginning of
+** the program. All statements are timed using the high-resolution timer
+** built into Intel-class processors.
+**
+** To compile this program, first compile the SQLite library separately
+** will full optimizations. For example:
+**
+** gcc -c -O6 -DSQLITE_THREADSAFE=0 sqlite3.c
+**
+** Then link against this program. But to do optimize this program
+** because that defeats the hi-res timer.
+**
+** gcc speedtest16.c sqlite3.o -ldl -I../src
+**
+** Then run this program with a single argument which is the name of
+** a file containing SQL script that you want to test:
+**
+** ./a.out database.db test.sql
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include "sqlite3.h"
+
+#define ISSPACE(X) isspace((unsigned char)(X))
+
+/*
+** hwtime.h contains inline assembler code for implementing
+** high-performance timing routines.
+*/
+#include "hwtime.h"
+
+/*
+** Convert a zero-terminated ASCII string into a zero-terminated
+** UTF-16le string. Memory to hold the returned string comes
+** from malloc() and should be freed by the caller.
+*/
+static void *asciiToUtf16le(const char *z){
+ int n = strlen(z);
+ char *z16;
+ int i, j;
+
+ z16 = malloc( n*2 + 2 );
+ for(i=j=0; i<=n; i++){
+ z16[j++] = z[i];
+ z16[j++] = 0;
+ }
+ return (void*)z16;
+}
+
+/*
+** Timers
+*/
+static sqlite_uint64 prepTime = 0;
+static sqlite_uint64 runTime = 0;
+static sqlite_uint64 finalizeTime = 0;
+
+/*
+** Prepare and run a single statement of SQL.
+*/
+static void prepareAndRun(sqlite3 *db, const char *zSql){
+ void *utf16;
+ sqlite3_stmt *pStmt;
+ const void *stmtTail;
+ sqlite_uint64 iStart, iElapse;
+ int rc;
+
+ printf("****************************************************************\n");
+ printf("SQL statement: [%s]\n", zSql);
+ utf16 = asciiToUtf16le(zSql);
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_prepare16_v2(db, utf16, -1, &pStmt, &stmtTail);
+ iElapse = sqlite3Hwtime() - iStart;
+ prepTime += iElapse;
+ printf("sqlite3_prepare16_v2() returns %d in %llu cycles\n", rc, iElapse);
+ if( rc==SQLITE_OK ){
+ int nRow = 0;
+ iStart = sqlite3Hwtime();
+ while( (rc=sqlite3_step(pStmt))==SQLITE_ROW ){ nRow++; }
+ iElapse = sqlite3Hwtime() - iStart;
+ runTime += iElapse;
+ printf("sqlite3_step() returns %d after %d rows in %llu cycles\n",
+ rc, nRow, iElapse);
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_finalize(pStmt);
+ iElapse = sqlite3Hwtime() - iStart;
+ finalizeTime += iElapse;
+ printf("sqlite3_finalize() returns %d in %llu cycles\n", rc, iElapse);
+ }
+ free(utf16);
+}
+
+int main(int argc, char **argv){
+ void *utf16;
+ sqlite3 *db;
+ int rc;
+ int nSql;
+ char *zSql;
+ int i, j;
+ FILE *in;
+ sqlite_uint64 iStart, iElapse;
+ sqlite_uint64 iSetup = 0;
+ int nStmt = 0;
+ int nByte = 0;
+
+ if( argc!=3 ){
+ fprintf(stderr, "Usage: %s FILENAME SQL-SCRIPT\n"
+ "Runs SQL-SCRIPT as UTF16 against a UTF16 database\n",
+ argv[0]);
+ exit(1);
+ }
+ in = fopen(argv[2], "r");
+ fseek(in, 0L, SEEK_END);
+ nSql = ftell(in);
+ zSql = malloc( nSql+1 );
+ fseek(in, 0L, SEEK_SET);
+ nSql = fread(zSql, 1, nSql, in);
+ zSql[nSql] = 0;
+
+ printf("SQLite version: %d\n", sqlite3_libversion_number());
+ unlink(argv[1]);
+ utf16 = asciiToUtf16le(argv[1]);
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_open16(utf16, &db);
+ iElapse = sqlite3Hwtime() - iStart;
+ iSetup = iElapse;
+ printf("sqlite3_open16() returns %d in %llu cycles\n", rc, iElapse);
+ free(utf16);
+ for(i=j=0; j<nSql; j++){
+ if( zSql[j]==';' ){
+ int isComplete;
+ char c = zSql[j+1];
+ zSql[j+1] = 0;
+ isComplete = sqlite3_complete(&zSql[i]);
+ zSql[j+1] = c;
+ if( isComplete ){
+ zSql[j] = 0;
+ while( i<j && ISSPACE(zSql[i]) ){ i++; }
+ if( i<j ){
+ nStmt++;
+ nByte += j-i;
+ prepareAndRun(db, &zSql[i]);
+ }
+ zSql[j] = ';';
+ i = j+1;
+ }
+ }
+ }
+ iStart = sqlite3Hwtime();
+ sqlite3_close(db);
+ iElapse = sqlite3Hwtime() - iStart;
+ iSetup += iElapse;
+ printf("sqlite3_close() returns in %llu cycles\n", iElapse);
+ printf("\n");
+ printf("Statements run: %15d\n", nStmt);
+ printf("Bytes of SQL text: %15d\n", nByte);
+ printf("Total prepare time: %15llu cycles\n", prepTime);
+ printf("Total run time: %15llu cycles\n", runTime);
+ printf("Total finalize time: %15llu cycles\n", finalizeTime);
+ printf("Open/Close time: %15llu cycles\n", iSetup);
+ printf("Total Time: %15llu cycles\n",
+ prepTime + runTime + finalizeTime + iSetup);
+ return 0;
+}
diff --git a/tool/speedtest2.tcl b/tool/speedtest2.tcl
new file mode 100644
index 0000000..4fd632d
--- /dev/null
+++ b/tool/speedtest2.tcl
@@ -0,0 +1,207 @@
+#!/usr/bin/tclsh
+#
+# Run this script using TCLSH to do a speed comparison between
+# various versions of SQLite and PostgreSQL and MySQL
+#
+
+# Run a test
+#
+set cnt 1
+proc runtest {title} {
+ global cnt
+ set sqlfile test$cnt.sql
+ puts "<h2>Test $cnt: $title</h2>"
+ incr cnt
+ set fd [open $sqlfile r]
+ set sql [string trim [read $fd [file size $sqlfile]]]
+ close $fd
+ set sx [split $sql \n]
+ set n [llength $sx]
+ if {$n>8} {
+ set sql {}
+ for {set i 0} {$i<3} {incr i} {append sql [lindex $sx $i]<br>\n}
+ append sql "<i>... [expr {$n-6}] lines omitted</i><br>\n"
+ for {set i [expr {$n-3}]} {$i<$n} {incr i} {
+ append sql [lindex $sx $i]<br>\n
+ }
+ } else {
+ regsub -all \n [string trim $sql] <br> sql
+ }
+ puts "<blockquote>"
+ puts "$sql"
+ puts "</blockquote><table border=0 cellpadding=0 cellspacing=0>"
+ set format {<tr><td>%s</td><td align="right">&nbsp;&nbsp;&nbsp;%.3f</td></tr>}
+ set delay 1000
+ exec sync; after $delay;
+ set t [time "exec psql drh <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format PostgreSQL: $t]
+ exec sync; after $delay;
+ set t [time "exec mysql -f drh <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format MySQL: $t]
+# set t [time "exec ./sqlite232 s232.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.3.2:} $t]
+# set t [time "exec ./sqlite-100 s100.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.4 (cache=100):} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite240 s2k.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4:} $t]
+ exec sync; after $delay;
+ set t [time "exec ./sqlite240 sns.db <$sqlfile" 1]
+ set t [expr {[lindex $t 0]/1000000.0}]
+ puts [format $format {SQLite 2.4 (nosync):} $t]
+# set t [time "exec ./sqlite-t1 st1.db <$sqlfile" 1]
+# set t [expr {[lindex $t 0]/1000000.0}]
+# puts [format $format {SQLite 2.4 (test):} $t]
+ puts "</table>"
+}
+
+# Initialize the environment
+#
+expr srand(1)
+catch {exec /bin/sh -c {rm -f s*.db}}
+set fd [open clear.sql w]
+puts $fd {
+ drop table t1;
+ drop table t2;
+}
+close $fd
+catch {exec psql drh <clear.sql}
+catch {exec mysql drh <clear.sql}
+set fd [open 2kinit.sql w]
+puts $fd {
+ PRAGMA default_cache_size=2000;
+ PRAGMA default_synchronous=on;
+}
+close $fd
+exec ./sqlite240 s2k.db <2kinit.sql
+exec ./sqlite-t1 st1.db <2kinit.sql
+set fd [open nosync-init.sql w]
+puts $fd {
+ PRAGMA default_cache_size=2000;
+ PRAGMA default_synchronous=off;
+}
+close $fd
+exec ./sqlite240 sns.db <nosync-init.sql
+set ones {zero one two three four five six seven eight nine
+ ten eleven twelve thirteen fourteen fifteen sixteen seventeen
+ eighteen nineteen}
+set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
+proc number_name {n} {
+ if {$n>=1000} {
+ set txt "[number_name [expr {$n/1000}]] thousand"
+ set n [expr {$n%1000}]
+ } else {
+ set txt {}
+ }
+ if {$n>=100} {
+ append txt " [lindex $::ones [expr {$n/100}]] hundred"
+ set n [expr {$n%100}]
+ }
+ if {$n>=20} {
+ append txt " [lindex $::tens [expr {$n/10}]]"
+ set n [expr {$n%10}]
+ }
+ if {$n>0} {
+ append txt " [lindex $::ones $n]"
+ }
+ set txt [string trim $txt]
+ if {$txt==""} {set txt zero}
+ return $txt
+}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+puts $fd "CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100));"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "DELETE FROM t1;"
+close $fd
+runtest {DELETE everything}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "DELETE FROM t1;"
+close $fd
+runtest {DELETE everything}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "DELETE FROM t1;"
+close $fd
+runtest {DELETE everything}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "DELETE FROM t1;"
+close $fd
+runtest {DELETE everything}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "BEGIN;"
+for {set i 1} {$i<=25000} {incr i} {
+ set r [expr {int(rand()*500000)}]
+ puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
+}
+puts $fd "COMMIT;"
+close $fd
+runtest {25000 INSERTs in a transaction}
+
+
+set fd [open test$cnt.sql w]
+puts $fd "DELETE FROM t1;"
+close $fd
+runtest {DELETE everything}
+
+
+set fd [open test$cnt.sql w]
+puts $fd {DROP TABLE t1;}
+close $fd
+runtest {DROP TABLE}
diff --git a/tool/speedtest8.c b/tool/speedtest8.c
new file mode 100644
index 0000000..051fc89
--- /dev/null
+++ b/tool/speedtest8.c
@@ -0,0 +1,260 @@
+/*
+** Performance test for SQLite.
+**
+** This program reads ASCII text from a file named on the command-line
+** and submits that text to SQLite for evaluation. A new database
+** is created at the beginning of the program. All statements are
+** timed using the high-resolution timer built into Intel-class processors.
+**
+** To compile this program, first compile the SQLite library separately
+** will full optimizations. For example:
+**
+** gcc -c -O6 -DSQLITE_THREADSAFE=0 sqlite3.c
+**
+** Then link against this program. But to do optimize this program
+** because that defeats the hi-res timer.
+**
+** gcc speedtest8.c sqlite3.o -ldl -I../src
+**
+** Then run this program with a single argument which is the name of
+** a file containing SQL script that you want to test:
+**
+** ./a.out test.db test.sql
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+
+#if defined(_MSC_VER)
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <sys/times.h>
+#include <sched.h>
+#endif
+
+#include "sqlite3.h"
+
+/*
+** hwtime.h contains inline assembler code for implementing
+** high-performance timing routines.
+*/
+#include "hwtime.h"
+
+/*
+** Timers
+*/
+static sqlite_uint64 prepTime = 0;
+static sqlite_uint64 runTime = 0;
+static sqlite_uint64 finalizeTime = 0;
+
+/*
+** Prepare and run a single statement of SQL.
+*/
+static void prepareAndRun(sqlite3 *db, const char *zSql, int bQuiet){
+ sqlite3_stmt *pStmt;
+ const char *stmtTail;
+ sqlite_uint64 iStart, iElapse;
+ int rc;
+
+ if (!bQuiet){
+ printf("***************************************************************\n");
+ }
+ if (!bQuiet) printf("SQL statement: [%s]\n", zSql);
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &stmtTail);
+ iElapse = sqlite3Hwtime() - iStart;
+ prepTime += iElapse;
+ if (!bQuiet){
+ printf("sqlite3_prepare_v2() returns %d in %llu cycles\n", rc, iElapse);
+ }
+ if( rc==SQLITE_OK ){
+ int nRow = 0;
+ iStart = sqlite3Hwtime();
+ while( (rc=sqlite3_step(pStmt))==SQLITE_ROW ){ nRow++; }
+ iElapse = sqlite3Hwtime() - iStart;
+ runTime += iElapse;
+ if (!bQuiet){
+ printf("sqlite3_step() returns %d after %d rows in %llu cycles\n",
+ rc, nRow, iElapse);
+ }
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_finalize(pStmt);
+ iElapse = sqlite3Hwtime() - iStart;
+ finalizeTime += iElapse;
+ if (!bQuiet){
+ printf("sqlite3_finalize() returns %d in %llu cycles\n", rc, iElapse);
+ }
+ }
+}
+
+int main(int argc, char **argv){
+ sqlite3 *db;
+ int rc;
+ int nSql;
+ char *zSql;
+ int i, j;
+ FILE *in;
+ sqlite_uint64 iStart, iElapse;
+ sqlite_uint64 iSetup = 0;
+ int nStmt = 0;
+ int nByte = 0;
+ const char *zArgv0 = argv[0];
+ int bQuiet = 0;
+#if !defined(_MSC_VER)
+ struct tms tmsStart, tmsEnd;
+ clock_t clkStart, clkEnd;
+#endif
+
+#ifdef HAVE_OSINST
+ extern sqlite3_vfs *sqlite3_instvfs_binarylog(char *, char *, char *);
+ extern void sqlite3_instvfs_destroy(sqlite3_vfs *);
+ sqlite3_vfs *pVfs = 0;
+#endif
+
+ while (argc>3)
+ {
+#ifdef HAVE_OSINST
+ if( argc>4 && (strcmp(argv[1], "-log")==0) ){
+ pVfs = sqlite3_instvfs_binarylog("oslog", 0, argv[2]);
+ sqlite3_vfs_register(pVfs, 1);
+ argv += 2;
+ argc -= 2;
+ continue;
+ }
+#endif
+
+ /*
+ ** Increasing the priority slightly above normal can help with
+ ** repeatability of testing. Note that with Cygwin, -5 equates
+ ** to "High", +5 equates to "Low", and anything in between
+ ** equates to "Normal".
+ */
+ if( argc>4 && (strcmp(argv[1], "-priority")==0) ){
+#if defined(_MSC_VER)
+ int new_priority = atoi(argv[2]);
+ if(!SetPriorityClass(GetCurrentProcess(),
+ (new_priority<=-5) ? HIGH_PRIORITY_CLASS :
+ (new_priority<=0) ? ABOVE_NORMAL_PRIORITY_CLASS :
+ (new_priority==0) ? NORMAL_PRIORITY_CLASS :
+ (new_priority<5) ? BELOW_NORMAL_PRIORITY_CLASS :
+ IDLE_PRIORITY_CLASS)){
+ printf ("error setting priority\n");
+ exit(2);
+ }
+#else
+ struct sched_param myParam;
+ sched_getparam(0, &myParam);
+ printf ("Current process priority is %d.\n", (int)myParam.sched_priority);
+ myParam.sched_priority = atoi(argv[2]);
+ printf ("Setting process priority to %d.\n", (int)myParam.sched_priority);
+ if (sched_setparam (0, &myParam) != 0){
+ printf ("error setting priority\n");
+ exit(2);
+ }
+#endif
+ argv += 2;
+ argc -= 2;
+ continue;
+ }
+
+ if( argc>3 && strcmp(argv[1], "-quiet")==0 ){
+ bQuiet = -1;
+ argv++;
+ argc--;
+ continue;
+ }
+
+ break;
+ }
+
+ if( argc!=3 ){
+ fprintf(stderr, "Usage: %s [options] FILENAME SQL-SCRIPT\n"
+ "Runs SQL-SCRIPT against a UTF8 database\n"
+ "\toptions:\n"
+#ifdef HAVE_OSINST
+ "\t-log <log>\n"
+#endif
+ "\t-priority <value> : set priority of task\n"
+ "\t-quiet : only display summary results\n",
+ zArgv0);
+ exit(1);
+ }
+
+ in = fopen(argv[2], "r");
+ fseek(in, 0L, SEEK_END);
+ nSql = ftell(in);
+ zSql = malloc( nSql+1 );
+ fseek(in, 0L, SEEK_SET);
+ nSql = fread(zSql, 1, nSql, in);
+ zSql[nSql] = 0;
+
+ printf("SQLite version: %d\n", sqlite3_libversion_number());
+ unlink(argv[1]);
+#if !defined(_MSC_VER)
+ clkStart = times(&tmsStart);
+#endif
+ iStart = sqlite3Hwtime();
+ rc = sqlite3_open(argv[1], &db);
+ iElapse = sqlite3Hwtime() - iStart;
+ iSetup = iElapse;
+ if (!bQuiet) printf("sqlite3_open() returns %d in %llu cycles\n", rc, iElapse);
+ for(i=j=0; j<nSql; j++){
+ if( zSql[j]==';' ){
+ int isComplete;
+ char c = zSql[j+1];
+ zSql[j+1] = 0;
+ isComplete = sqlite3_complete(&zSql[i]);
+ zSql[j+1] = c;
+ if( isComplete ){
+ zSql[j] = 0;
+ while( i<j && isspace(zSql[i]) ){ i++; }
+ if( i<j ){
+ int n = j - i;
+ if( n>=6 && memcmp(&zSql[i], ".crash",6)==0 ) exit(1);
+ nStmt++;
+ nByte += n;
+ prepareAndRun(db, &zSql[i], bQuiet);
+ }
+ zSql[j] = ';';
+ i = j+1;
+ }
+ }
+ }
+ iStart = sqlite3Hwtime();
+ sqlite3_close(db);
+ iElapse = sqlite3Hwtime() - iStart;
+#if !defined(_MSC_VER)
+ clkEnd = times(&tmsEnd);
+#endif
+ iSetup += iElapse;
+ if (!bQuiet) printf("sqlite3_close() returns in %llu cycles\n", iElapse);
+
+ printf("\n");
+ printf("Statements run: %15d stmts\n", nStmt);
+ printf("Bytes of SQL text: %15d bytes\n", nByte);
+ printf("Total prepare time: %15llu cycles\n", prepTime);
+ printf("Total run time: %15llu cycles\n", runTime);
+ printf("Total finalize time: %15llu cycles\n", finalizeTime);
+ printf("Open/Close time: %15llu cycles\n", iSetup);
+ printf("Total time: %15llu cycles\n",
+ prepTime + runTime + finalizeTime + iSetup);
+
+#if !defined(_MSC_VER)
+ printf("\n");
+ printf("Total user CPU time: %15.3g secs\n", (tmsEnd.tms_utime - tmsStart.tms_utime)/(double)CLOCKS_PER_SEC );
+ printf("Total system CPU time: %15.3g secs\n", (tmsEnd.tms_stime - tmsStart.tms_stime)/(double)CLOCKS_PER_SEC );
+ printf("Total real time: %15.3g secs\n", (clkEnd -clkStart)/(double)CLOCKS_PER_SEC );
+#endif
+
+#ifdef HAVE_OSINST
+ if( pVfs ){
+ sqlite3_instvfs_destroy(pVfs);
+ printf("vfs log written to %s\n", argv[0]);
+ }
+#endif
+
+ return 0;
+}
diff --git a/tool/speedtest8inst1.c b/tool/speedtest8inst1.c
new file mode 100644
index 0000000..ceaeca0
--- /dev/null
+++ b/tool/speedtest8inst1.c
@@ -0,0 +1,218 @@
+/*
+** Performance test for SQLite.
+**
+** This program reads ASCII text from a file named on the command-line
+** and submits that text to SQLite for evaluation. A new database
+** is created at the beginning of the program. All statements are
+** timed using the high-resolution timer built into Intel-class processors.
+**
+** To compile this program, first compile the SQLite library separately
+** will full optimizations. For example:
+**
+** gcc -c -O6 -DSQLITE_THREADSAFE=0 sqlite3.c
+**
+** Then link against this program. But to do optimize this program
+** because that defeats the hi-res timer.
+**
+** gcc speedtest8.c sqlite3.o -ldl -I../src
+**
+** Then run this program with a single argument which is the name of
+** a file containing SQL script that you want to test:
+**
+** ./a.out test.db test.sql
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include "sqlite3.h"
+
+#define ISSPACE(X) isspace((unsigned char)(X))
+
+#include "test_osinst.c"
+
+/*
+** Prepare and run a single statement of SQL.
+*/
+static void prepareAndRun(sqlite3_vfs *pInstVfs, sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ const char *stmtTail;
+ int rc;
+ char zMessage[1024];
+ zMessage[1023] = '\0';
+
+ sqlite3_uint64 iTime;
+
+ sqlite3_snprintf(1023, zMessage, "sqlite3_prepare_v2: %s", zSql);
+ sqlite3_instvfs_binarylog_marker(pInstVfs, zMessage);
+
+ iTime = sqlite3Hwtime();
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &stmtTail);
+ iTime = sqlite3Hwtime() - iTime;
+ sqlite3_instvfs_binarylog_call(pInstVfs,BINARYLOG_PREPARE_V2,iTime,rc,zSql);
+
+ if( rc==SQLITE_OK ){
+ int nRow = 0;
+
+ sqlite3_snprintf(1023, zMessage, "sqlite3_step loop: %s", zSql);
+ sqlite3_instvfs_binarylog_marker(pInstVfs, zMessage);
+ iTime = sqlite3Hwtime();
+ while( (rc=sqlite3_step(pStmt))==SQLITE_ROW ){ nRow++; }
+ iTime = sqlite3Hwtime() - iTime;
+ sqlite3_instvfs_binarylog_call(pInstVfs, BINARYLOG_STEP, iTime, rc, zSql);
+
+ sqlite3_snprintf(1023, zMessage, "sqlite3_finalize: %s", zSql);
+ sqlite3_instvfs_binarylog_marker(pInstVfs, zMessage);
+ iTime = sqlite3Hwtime();
+ rc = sqlite3_finalize(pStmt);
+ iTime = sqlite3Hwtime() - iTime;
+ sqlite3_instvfs_binarylog_call(pInstVfs, BINARYLOG_FINALIZE, iTime, rc, zSql);
+ }
+}
+
+static int stringcompare(const char *zLeft, const char *zRight){
+ int ii;
+ for(ii=0; zLeft[ii] && zRight[ii]; ii++){
+ if( zLeft[ii]!=zRight[ii] ) return 0;
+ }
+ return( zLeft[ii]==zRight[ii] );
+}
+
+static char *readScriptFile(const char *zFile, int *pnScript){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+ sqlite3_file *p;
+ int rc;
+ sqlite3_int64 nByte;
+ char *zData = 0;
+ int flags = SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_DB;
+
+ p = (sqlite3_file *)malloc(pVfs->szOsFile);
+ rc = pVfs->xOpen(pVfs, zFile, p, flags, &flags);
+ if( rc!=SQLITE_OK ){
+ goto error_out;
+ }
+
+ rc = p->pMethods->xFileSize(p, &nByte);
+ if( rc!=SQLITE_OK ){
+ goto close_out;
+ }
+
+ zData = (char *)malloc(nByte+1);
+ rc = p->pMethods->xRead(p, zData, nByte, 0);
+ if( rc!=SQLITE_OK ){
+ goto close_out;
+ }
+ zData[nByte] = '\0';
+
+ p->pMethods->xClose(p);
+ free(p);
+ *pnScript = nByte;
+ return zData;
+
+close_out:
+ p->pMethods->xClose(p);
+
+error_out:
+ free(p);
+ free(zData);
+ return 0;
+}
+
+int main(int argc, char **argv){
+
+ const char zUsageMsg[] =
+ "Usage: %s options...\n"
+ " where available options are:\n"
+ "\n"
+ " -db DATABASE-FILE (database file to operate on)\n"
+ " -script SCRIPT-FILE (script file to read sql from)\n"
+ " -log LOG-FILE (log file to create)\n"
+ " -logdata (log all data to log file)\n"
+ "\n"
+ " Options -db, -script and -log are compulsory\n"
+ "\n"
+ ;
+
+ const char *zDb = 0;
+ const char *zScript = 0;
+ const char *zLog = 0;
+ int logdata = 0;
+
+ int ii;
+ int i, j;
+ int rc;
+
+ sqlite3_vfs *pInstVfs; /* Instrumentation VFS */
+
+ char *zSql = 0;
+ int nSql;
+
+ sqlite3 *db;
+
+ for(ii=1; ii<argc; ii++){
+ if( stringcompare("-db", argv[ii]) && (ii+1)<argc ){
+ zDb = argv[++ii];
+ }
+
+ else if( stringcompare("-script", argv[ii]) && (ii+1)<argc ){
+ zScript = argv[++ii];
+ }
+
+ else if( stringcompare("-log", argv[ii]) && (ii+1)<argc ){
+ zLog = argv[++ii];
+ }
+
+ else if( stringcompare("-logdata", argv[ii]) ){
+ logdata = 1;
+ }
+
+ else {
+ goto usage;
+ }
+ }
+ if( !zDb || !zScript || !zLog ) goto usage;
+
+ zSql = readScriptFile(zScript, &nSql);
+ if( !zSql ){
+ fprintf(stderr, "Failed to read script file\n");
+ return -1;
+ }
+
+ pInstVfs = sqlite3_instvfs_binarylog("logging", 0, zLog, logdata);
+
+ rc = sqlite3_open_v2(
+ zDb, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "logging"
+ );
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr, "Failed to open db: %s\n", sqlite3_errmsg(db));
+ return -2;
+ }
+
+ for(i=j=0; j<nSql; j++){
+ if( zSql[j]==';' ){
+ int isComplete;
+ char c = zSql[j+1];
+ zSql[j+1] = 0;
+ isComplete = sqlite3_complete(&zSql[i]);
+ zSql[j+1] = c;
+ if( isComplete ){
+ zSql[j] = 0;
+ while( i<j && ISSPACE(zSql[i]) ){ i++; }
+ if( i<j ){
+ prepareAndRun(pInstVfs, db, &zSql[i]);
+ }
+ zSql[j] = ';';
+ i = j+1;
+ }
+ }
+ }
+
+ sqlite3_instvfs_destroy(pInstVfs);
+ return 0;
+
+usage:
+ fprintf(stderr, zUsageMsg, argv[0]);
+ return -3;
+}
diff --git a/tool/spellsift.tcl b/tool/spellsift.tcl
new file mode 100755
index 0000000..4e67c3e
--- /dev/null
+++ b/tool/spellsift.tcl
@@ -0,0 +1,74 @@
+#!/usr/bin/tclsh
+
+set usage {
+ Usage: spellsift.tcl <source_filenames>
+ The named .c and .h source files comment blocks are spell-checked.
+}
+
+if {[llength $argv] == 0} {
+ puts stderr $usage
+ exit 0
+}
+
+# Want a Tcl version with 3-argument close.
+package require Tcl 8.6
+
+set ::spellchk "aspell --extra-dicts ./custom.rws list"
+
+# Run text through aspell with custom dictionary, return finds.
+proc misspelled {text} {
+ set spellerr [open "|$::spellchk" r+]
+ puts $spellerr $text
+ flush $spellerr
+ close $spellerr write
+ set huhq [regsub {\s*$} [read $spellerr] {}]
+ close $spellerr read
+ return [split $huhq "\n"]
+}
+
+# Eliminate some common patterns that need not be well spelled.
+proc decruft {text} {
+ set nopp [regsub -all "\n *#\[^\n\]*\n" $text "\n\n" ]
+ set noticket [regsub -all {Ticket \[?[0-9a-f]+\]?} $nopp "" ]
+ return $noticket
+}
+
+# Sift out common variable spellings not in normal dictionaries.
+proc varsift {words} {
+ set rv [list]
+ foreach w $words {
+ set n [string length $w]
+ set cr [string range $w 1 end]
+ if {[string tolower $cr] ne $cr} continue
+ lappend rv $w;
+ }
+ return $rv
+}
+
+foreach fname $argv {
+ set ich [open $fname r]
+ set dtext [decruft [read $ich]]
+ close $ich
+ set cbounds [regexp -indices -inline -all {(/\*)|(\*/)} $dtext]
+ set ccb -1
+ set cblocks [list]
+ foreach {ap cb ce} $cbounds {
+ set cib [lindex $cb 1]
+ set cie [lindex $ce 0]
+ if {$cie != -1} {
+ if {$ccb != -1} {
+ set cce [expr $cie - 1]
+ set destar [string map [list * " "] [string range $dtext $ccb $cce]]
+ lappend cblocks $destar
+ set ccb -1
+ } else continue
+ } elseif {$cib != -1} {
+ set ccb [expr $cib + 1]
+ }
+ }
+ set oddspells [varsift [misspelled [join $cblocks "\n"]]]
+ if {[llength $oddspells] > 0} {
+ puts "!? Misspellings from $fname:"
+ puts [join [lsort -nocase -unique $oddspells] "\n"]
+ }
+}
diff --git a/tool/split-sqlite3c.tcl b/tool/split-sqlite3c.tcl
new file mode 100644
index 0000000..0308431
--- /dev/null
+++ b/tool/split-sqlite3c.tcl
@@ -0,0 +1,96 @@
+#!/usr/bin/tclsh
+#
+# This script splits the sqlite3.c amalgamated source code files into
+# several smaller files such that no single files is more than a fixed
+# number of lines in length (32k or 64k). Each of the split out files
+# is #include-ed by the master file.
+#
+# Splitting files up this way allows them to be used with older compilers
+# that cannot handle really long source files.
+#
+set MAX 32768 ;# Maximum number of lines per file.
+
+set BEGIN {^/\*+ Begin file ([a-zA-Z0-9_.]+) \*+/}
+set END {^/\*+ End of %s \*+/}
+
+set in [open sqlite3.c]
+set out1 [open sqlite3-all.c w]
+fconfigure $out1 -translation lf
+
+# Copy the header from sqlite3.c into sqlite3-all.c
+#
+while {[gets $in line]} {
+ if {[regexp $BEGIN $line]} break
+ puts $out1 $line
+}
+
+# Gather the complete content of a file into memory. Store the
+# content in $bufout. Store the number of lines is $nout
+#
+proc gather_one_file {firstline bufout nout} {
+ regexp $::BEGIN $firstline all filename
+ set end [format $::END $filename]
+ upvar $bufout buf $nout n
+ set buf $firstline\n
+ global in
+ set n 0
+ while {[gets $in line]>=0} {
+ incr n
+ append buf $line\n
+ if {[regexp $end $line]} break
+ }
+}
+
+# Write a big chunk of text in to an auxiliary file "sqlite3-NNN.c".
+# Also add an appropriate #include to sqlite3-all.c
+#
+set filecnt 0
+proc write_one_file {content} {
+ global filecnt
+ incr filecnt
+ set label $filecnt
+ if {$filecnt>9} {
+ set label [string index ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop \
+ [expr {$filecnt-10}]]
+ } else {
+ set label $filecnt
+ }
+ set out [open sqlite3-$label.c w]
+ fconfigure $out -translation lf
+ puts -nonewline $out $content
+ close $out
+ puts $::out1 "#include \"sqlite3-$filecnt.c\""
+}
+
+# Continue reading input. Store chunks in separate files and add
+# the #includes to the main sqlite3-all.c file as necessary to reference
+# the extra chunks.
+#
+set all {}
+set N 0
+while {[regexp $BEGIN $line]} {
+ set buf {}
+ set n 0
+ gather_one_file $line buf n
+ if {$n+$N>=$MAX} {
+ write_one_file $all
+ set all {}
+ set N 0
+ }
+ append all $buf
+ incr N $n
+ while {[gets $in line]>=0} {
+ if {[regexp $BEGIN $line]} break
+ if {$N>0} {
+ write_one_file $all
+ set N 0
+ set all {}
+ }
+ puts $out1 $line
+ }
+}
+if {$N>0} {
+ write_one_file $all
+}
+close $out1
+close $in
diff --git a/tool/sqldiff.c b/tool/sqldiff.c
new file mode 100644
index 0000000..cbdfc35
--- /dev/null
+++ b/tool/sqldiff.c
@@ -0,0 +1,2055 @@
+/*
+** 2015-04-06
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This is a utility program that computes the differences in content
+** between two SQLite databases.
+**
+** To compile, simply link against SQLite. (Windows builds must also link
+** against ext/consio/console_io.c.)
+**
+** See the showHelp() routine below for a brief description of how to
+** run the utility.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#include "sqlite3.h"
+
+/* Output function substitutions that cause UTF8 characters to be rendered
+** correctly on Windows:
+**
+** fprintf() -> Wfprintf()
+**
+*/
+#if defined(_WIN32)
+# include "console_io.h"
+# define Wfprintf fPrintfUtf8
+#else
+# define Wfprintf fprintf
+#endif
+
+/*
+** All global variables are gathered into the "g" singleton.
+*/
+struct GlobalVars {
+ const char *zArgv0; /* Name of program */
+ int bSchemaOnly; /* Only show schema differences */
+ int bSchemaPK; /* Use the schema-defined PK, not the true PK */
+ int bHandleVtab; /* Handle fts3, fts4, fts5 and rtree vtabs */
+ unsigned fDebug; /* Debug flags */
+ int bSchemaCompare; /* Doing single-table sqlite_schema compare */
+ sqlite3 *db; /* The database connection */
+} g;
+
+/*
+** Allowed values for g.fDebug
+*/
+#define DEBUG_COLUMN_NAMES 0x000001
+#define DEBUG_DIFF_SQL 0x000002
+
+/*
+** Clear and free an sqlite3_str object
+*/
+static void strFree(sqlite3_str *pStr){
+ sqlite3_free(sqlite3_str_finish(pStr));
+}
+
+/*
+** Print an error resulting from faulting command-line arguments and
+** abort the program.
+*/
+static void cmdlineError(const char *zFormat, ...){
+ sqlite3_str *pOut = sqlite3_str_new(0);
+ va_list ap;
+ va_start(ap, zFormat);
+ sqlite3_str_vappendf(pOut, zFormat, ap);
+ va_end(ap);
+ Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut));
+ strFree(pOut);
+ Wfprintf(stderr, "\"%s --help\" for more help\n", g.zArgv0);
+ exit(1);
+}
+
+/*
+** Print an error message for an error that occurs at runtime, then
+** abort the program.
+*/
+static void runtimeError(const char *zFormat, ...){
+ sqlite3_str *pOut = sqlite3_str_new(0);
+ va_list ap;
+ va_start(ap, zFormat);
+ sqlite3_str_vappendf(pOut, zFormat, ap);
+ va_end(ap);
+ Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut));
+ strFree(pOut);
+ exit(1);
+}
+
+
+/* Safely quote an SQL identifier. Use the minimum amount of transformation
+** necessary to allow the string to be used with %s.
+**
+** Space to hold the returned string is obtained from sqlite3_malloc(). The
+** caller is responsible for ensuring this space is freed when no longer
+** needed.
+*/
+static char *safeId(const char *zId){
+ int i, x;
+ char c;
+ if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
+ for(i=x=0; (c = zId[i])!=0; i++){
+ if( !isalpha(c) && c!='_' ){
+ if( i>0 && isdigit(c) ){
+ x++;
+ }else{
+ return sqlite3_mprintf("\"%w\"", zId);
+ }
+ }
+ }
+ if( x || !sqlite3_keyword_check(zId,i) ){
+ return sqlite3_mprintf("%s", zId);
+ }
+ return sqlite3_mprintf("\"%w\"", zId);
+}
+
+/*
+** Prepare a new SQL statement. Print an error and abort if anything
+** goes wrong.
+*/
+static sqlite3_stmt *db_vprepare(const char *zFormat, va_list ap){
+ char *zSql;
+ int rc;
+ sqlite3_stmt *pStmt;
+
+ zSql = sqlite3_vmprintf(zFormat, ap);
+ if( zSql==0 ) runtimeError("out of memory");
+ rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0);
+ if( rc ){
+ runtimeError("SQL statement error: %s\n\"%s\"", sqlite3_errmsg(g.db),
+ zSql);
+ }
+ sqlite3_free(zSql);
+ return pStmt;
+}
+static sqlite3_stmt *db_prepare(const char *zFormat, ...){
+ va_list ap;
+ sqlite3_stmt *pStmt;
+ va_start(ap, zFormat);
+ pStmt = db_vprepare(zFormat, ap);
+ va_end(ap);
+ return pStmt;
+}
+
+/*
+** Free a list of strings
+*/
+static void namelistFree(char **az){
+ if( az ){
+ int i;
+ for(i=0; az[i]; i++) sqlite3_free(az[i]);
+ sqlite3_free(az);
+ }
+}
+
+/*
+** Return a list of column names [a] for the table zDb.zTab. Space to
+** hold the list is obtained from sqlite3_malloc() and should released
+** using namelistFree() when no longer needed.
+**
+** Primary key columns are listed first, followed by data columns.
+** The number of columns in the primary key is returned in *pnPkey.
+**
+** Normally [a], the "primary key" in the previous sentence is the true
+** primary key - the rowid or INTEGER PRIMARY KEY for ordinary tables
+** or the declared PRIMARY KEY for WITHOUT ROWID tables. However, if
+** the g.bSchemaPK flag is set, then the schema-defined PRIMARY KEY is
+** used in all cases. In that case, entries that have NULL values in
+** any of their primary key fields will be excluded from the analysis.
+**
+** If the primary key for a table is the rowid but rowid is inaccessible,
+** then this routine returns a NULL pointer.
+**
+** [a. If the lone, named table is "sqlite_schema", "rootpage" column is
+** omitted and the "type" and "name" columns are made to be the PK.]
+**
+** Examples:
+** CREATE TABLE t1(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(c));
+** *pnPKey = 1;
+** az = { "rowid", "a", "b", "c", 0 } // Normal case
+** az = { "c", "a", "b", 0 } // g.bSchemaPK==1
+**
+** CREATE TABLE t2(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(b));
+** *pnPKey = 1;
+** az = { "b", "a", "c", 0 }
+**
+** CREATE TABLE t3(x,y,z,PRIMARY KEY(y,z));
+** *pnPKey = 1 // Normal case
+** az = { "rowid", "x", "y", "z", 0 } // Normal case
+** *pnPKey = 2 // g.bSchemaPK==1
+** az = { "y", "x", "z", 0 } // g.bSchemaPK==1
+**
+** CREATE TABLE t4(x,y,z,PRIMARY KEY(y,z)) WITHOUT ROWID;
+** *pnPKey = 2
+** az = { "y", "z", "x", 0 }
+**
+** CREATE TABLE t5(rowid,_rowid_,oid);
+** az = 0 // The rowid is not accessible
+*/
+static char **columnNames(
+ const char *zDb, /* Database ("main" or "aux") to query */
+ const char *zTab, /* Name of table to return details of */
+ int *pnPKey, /* OUT: Number of PK columns */
+ int *pbRowid /* OUT: True if PK is an implicit rowid */
+){
+ char **az = 0; /* List of column names to be returned */
+ int naz = 0; /* Number of entries in az[] */
+ sqlite3_stmt *pStmt; /* SQL statement being run */
+ char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */
+ int truePk = 0; /* PRAGMA table_info indentifies the PK to use */
+ int nPK = 0; /* Number of PRIMARY KEY columns */
+ int i, j; /* Loop counters */
+
+ if( g.bSchemaPK==0 ){
+ /* Normal case: Figure out what the true primary key is for the table.
+ ** * For WITHOUT ROWID tables, the true primary key is the same as
+ ** the schema PRIMARY KEY, which is guaranteed to be present.
+ ** * For rowid tables with an INTEGER PRIMARY KEY, the true primary
+ ** key is the INTEGER PRIMARY KEY.
+ ** * For all other rowid tables, the rowid is the true primary key.
+ */
+ pStmt = db_prepare("PRAGMA %s.index_list=%Q", zDb, zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,3),"pk")==0 ){
+ zPkIdxName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
+ break;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( zPkIdxName ){
+ int nKey = 0;
+ int nCol = 0;
+ truePk = 0;
+ pStmt = db_prepare("PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ nCol++;
+ if( sqlite3_column_int(pStmt,5) ){ nKey++; continue; }
+ if( sqlite3_column_int(pStmt,1)>=0 ) truePk = 1;
+ }
+ if( nCol==nKey ) truePk = 1;
+ if( truePk ){
+ nPK = nKey;
+ }else{
+ nPK = 1;
+ }
+ sqlite3_finalize(pStmt);
+ sqlite3_free(zPkIdxName);
+ }else{
+ truePk = 1;
+ nPK = 1;
+ }
+ pStmt = db_prepare("PRAGMA %s.table_info=%Q", zDb, zTab);
+ }else{
+ /* The g.bSchemaPK==1 case: Use whatever primary key is declared
+ ** in the schema. The "rowid" will still be used as the primary key
+ ** if the table definition does not contain a PRIMARY KEY.
+ */
+ nPK = 0;
+ pStmt = db_prepare("PRAGMA %s.table_info=%Q", zDb, zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( sqlite3_column_int(pStmt,5)>0 ) nPK++;
+ }
+ sqlite3_reset(pStmt);
+ if( nPK==0 ) nPK = 1;
+ truePk = 1;
+ }
+ if( g.bSchemaCompare ){
+ assert( sqlite3_stricmp(zTab,"sqlite_schema")==0
+ || sqlite3_stricmp(zTab,"sqlite_master")==0 );
+ /* For sqlite_schema, will use type and name as the PK. */
+ nPK = 2;
+ truePk = 0;
+ }
+ *pnPKey = nPK;
+ naz = nPK;
+ az = sqlite3_malloc( sizeof(char*)*(nPK+1) );
+ if( az==0 ) runtimeError("out of memory");
+ memset(az, 0, sizeof(char*)*(nPK+1));
+ if( g.bSchemaCompare ){
+ az[0] = sqlite3_mprintf("%s", "type");
+ az[1] = sqlite3_mprintf("%s", "name");
+ }
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ char * sid = safeId((char*)sqlite3_column_text(pStmt,1));
+ int iPKey;
+ if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){
+ az[iPKey-1] = sid;
+ }else{
+ if( !g.bSchemaCompare
+ || !(strcmp(sid,"rootpage")==0
+ ||strcmp(sid,"name")==0
+ ||strcmp(sid,"type")==0)){
+ az = sqlite3_realloc(az, sizeof(char*)*(naz+2) );
+ if( az==0 ) runtimeError("out of memory");
+ az[naz++] = sid;
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( az ) az[naz] = 0;
+
+ /* If it is non-NULL, set *pbRowid to indicate whether or not the PK of
+ ** this table is an implicit rowid (*pbRowid==1) or not (*pbRowid==0). */
+ if( pbRowid ) *pbRowid = (az[0]==0);
+
+ /* If this table has an implicit rowid for a PK, figure out how to refer
+ ** to it. There are usually three options - "rowid", "_rowid_" and "oid".
+ ** Any of these will work, unless the table has an explicit column of the
+ ** same name or the sqlite_schema tables are to be compared. In the latter
+ ** case, pretend that the "true" primary key is the name column, which
+ ** avoids extraneous diffs against the schemas due to rowid variance. */
+ if( az[0]==0 ){
+ const char *azRowid[] = { "rowid", "_rowid_", "oid" };
+ for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
+ for(j=1; j<naz; j++){
+ if( sqlite3_stricmp(az[j], azRowid[i])==0 ) break;
+ }
+ if( j>=naz ){
+ az[0] = sqlite3_mprintf("%s", azRowid[i]);
+ break;
+ }
+ }
+ if( az[0]==0 ){
+ for(i=1; i<naz; i++) sqlite3_free(az[i]);
+ sqlite3_free(az);
+ az = 0;
+ }
+ }
+ return az;
+}
+
+/*
+** Print the sqlite3_value X as an SQL literal.
+*/
+static void printQuoted(FILE *out, sqlite3_value *X){
+ switch( sqlite3_value_type(X) ){
+ case SQLITE_FLOAT: {
+ double r1;
+ char zBuf[50];
+ r1 = sqlite3_value_double(X);
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
+ fprintf(out, "%s", zBuf);
+ break;
+ }
+ case SQLITE_INTEGER: {
+ fprintf(out, "%lld", sqlite3_value_int64(X));
+ break;
+ }
+ case SQLITE_BLOB: {
+ const unsigned char *zBlob = sqlite3_value_blob(X);
+ int nBlob = sqlite3_value_bytes(X);
+ if( zBlob ){
+ int i;
+ fprintf(out, "x'");
+ for(i=0; i<nBlob; i++){
+ fprintf(out, "%02x", zBlob[i]);
+ }
+ fprintf(out, "'");
+ }else{
+ /* Could be an OOM, could be a zero-byte blob */
+ fprintf(out, "X''");
+ }
+ break;
+ }
+ case SQLITE_TEXT: {
+ const unsigned char *zArg = sqlite3_value_text(X);
+
+ if( zArg==0 ){
+ fprintf(out, "NULL");
+ }else{
+ int inctl = 0;
+ int i, j;
+ fprintf(out, "'");
+ for(i=j=0; zArg[i]; i++){
+ char c = zArg[i];
+ int ctl = iscntrl(c);
+ if( ctl>inctl ){
+ inctl = ctl;
+ fprintf(out, "%.*s'||X'%02x", i-j, &zArg[j], c);
+ j = i+1;
+ }else if( ctl ){
+ fprintf(out, "%02x", c);
+ j = i+1;
+ }else{
+ if( inctl ){
+ inctl = 0;
+ fprintf(out, "'\n||'");
+ }
+ if( c=='\'' ){
+ fprintf(out, "%.*s'", i-j+1, &zArg[j]);
+ j = i+1;
+ }
+ }
+ }
+ fprintf(out, "%s'", &zArg[j]);
+ }
+ break;
+ }
+ case SQLITE_NULL: {
+ fprintf(out, "NULL");
+ break;
+ }
+ }
+}
+
+/*
+** Output SQL that will recreate the aux.zTab table.
+*/
+static void dump_table(const char *zTab, FILE *out){
+ char *zId = safeId(zTab); /* Name of the table */
+ char **az = 0; /* List of columns */
+ int nPk; /* Number of true primary key columns */
+ int nCol; /* Number of data columns */
+ int i; /* Loop counter */
+ sqlite3_stmt *pStmt; /* SQL statement */
+ const char *zSep; /* Separator string */
+ sqlite3_str *pIns; /* Beginning of the INSERT statement */
+
+ pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema WHERE name=%Q", zTab);
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
+ }
+ sqlite3_finalize(pStmt);
+ if( !g.bSchemaOnly ){
+ az = columnNames("aux", zTab, &nPk, 0);
+ pIns = sqlite3_str_new(0);
+ if( az==0 ){
+ pStmt = db_prepare("SELECT * FROM aux.%s", zId);
+ sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId);
+ }else{
+ sqlite3_str *pSql = sqlite3_str_new(0);
+ zSep = "SELECT";
+ for(i=0; az[i]; i++){
+ sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
+ zSep = ",";
+ }
+ sqlite3_str_appendf(pSql," FROM aux.%s", zId);
+ zSep = " ORDER BY";
+ for(i=1; i<=nPk; i++){
+ sqlite3_str_appendf(pSql, "%s %d", zSep, i);
+ zSep = ",";
+ }
+ pStmt = db_prepare("%s", sqlite3_str_value(pSql));
+ strFree(pSql);
+ sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
+ zSep = "(";
+ for(i=0; az[i]; i++){
+ sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
+ zSep = ",";
+ }
+ sqlite3_str_appendf(pIns,") VALUES");
+ namelistFree(az);
+ }
+ nCol = sqlite3_column_count(pStmt);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ Wfprintf(out, "%s",sqlite3_str_value(pIns));
+ zSep = "(";
+ for(i=0; i<nCol; i++){
+ Wfprintf(out, "%s",zSep);
+ printQuoted(out, sqlite3_column_value(pStmt,i));
+ zSep = ",";
+ }
+ Wfprintf(out, ");\n");
+ }
+ sqlite3_finalize(pStmt);
+ strFree(pIns);
+ } /* endif !g.bSchemaOnly */
+ pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema"
+ " WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
+ zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ Wfprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
+ }
+ sqlite3_finalize(pStmt);
+ sqlite3_free(zId);
+}
+
+
+/*
+** Compute all differences for a single table, except if the
+** table name is sqlite_schema, ignore the rootpage column.
+*/
+static void diff_one_table(const char *zTab, FILE *out){
+ char *zId = safeId(zTab); /* Name of table (translated for us in SQL) */
+ char **az = 0; /* Columns in main */
+ char **az2 = 0; /* Columns in aux */
+ int nPk; /* Primary key columns in main */
+ int nPk2; /* Primary key columns in aux */
+ int n = 0; /* Number of columns in main */
+ int n2; /* Number of columns in aux */
+ int nQ; /* Number of output columns in the diff query */
+ int i; /* Loop counter */
+ const char *zSep; /* Separator string */
+ sqlite3_str *pSql; /* Comparison query */
+ sqlite3_stmt *pStmt; /* Query statement to do the diff */
+ const char *zLead = /* Becomes line-comment for sqlite_schema */
+ (g.bSchemaCompare)? "-- " : "";
+
+ pSql = sqlite3_str_new(0);
+ if( g.fDebug==DEBUG_COLUMN_NAMES ){
+ /* Simply run columnNames() on all tables of the origin
+ ** database and show the results. This is used for testing
+ ** and debugging of the columnNames() function.
+ */
+ az = columnNames("aux",zTab, &nPk, 0);
+ if( az==0 ){
+ Wfprintf(stdout, "Rowid not accessible for %s\n", zId);
+ }else{
+ Wfprintf(stdout, "%s:", zId);
+ for(i=0; az[i]; i++){
+ Wfprintf(stdout, " %s", az[i]);
+ if( i+1==nPk ) Wfprintf(stdout, " *");
+ }
+ Wfprintf(stdout, "\n");
+ }
+ goto end_diff_one_table;
+ }
+
+ if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){
+ if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
+ /* Table missing from second database. */
+ if( g.bSchemaCompare )
+ Wfprintf(out, "-- 2nd DB has no %s table\n", zTab);
+ else
+ Wfprintf(out, "DROP TABLE %s;\n", zId);
+ }
+ goto end_diff_one_table;
+ }
+
+ if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
+ /* Table missing from source */
+ if( g.bSchemaCompare ){
+ Wfprintf(out, "-- 1st DB has no %s table\n", zTab);
+ }else{
+ dump_table(zTab, out);
+ }
+ goto end_diff_one_table;
+ }
+
+ az = columnNames("main", zTab, &nPk, 0);
+ az2 = columnNames("aux", zTab, &nPk2, 0);
+ if( az && az2 ){
+ for(n=0; az[n] && az2[n]; n++){
+ if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
+ }
+ }
+ if( az==0
+ || az2==0
+ || nPk!=nPk2
+ || az[n]
+ ){
+ /* Schema mismatch */
+ Wfprintf(out, "%sDROP TABLE %s; -- due to schema mismatch\n", zLead, zId);
+ dump_table(zTab, out);
+ goto end_diff_one_table;
+ }
+
+ /* Build the comparison query */
+ for(n2=n; az2[n2]; n2++){
+ char *zNTab = safeId(az2[n2]);
+ Wfprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
+ sqlite3_free(zNTab);
+ }
+ nQ = nPk2+1+2*(n2-nPk2);
+ if( n2>nPk2 ){
+ zSep = "SELECT ";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
+ zSep = ", ";
+ }
+ sqlite3_str_appendf(pSql, ", 1 /* changed row */");
+ while( az[i] ){
+ sqlite3_str_appendf(pSql, ", A.%s IS NOT B.%s, B.%s",
+ az[i], az2[i], az2[i]);
+ i++;
+ }
+ while( az2[i] ){
+ sqlite3_str_appendf(pSql, ", B.%s IS NOT NULL, B.%s",
+ az2[i], az2[i]);
+ i++;
+ }
+ sqlite3_str_appendf(pSql, "\n FROM main.%s A, aux.%s B\n", zId, zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ zSep = "\n AND (";
+ while( az[i] ){
+ sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s%s\n",
+ zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
+ zSep = " OR ";
+ i++;
+ }
+ while( az2[i] ){
+ sqlite3_str_appendf(pSql, "%sB.%s IS NOT NULL%s\n",
+ zSep, az2[i], az2[i+1]==0 ? ")" : "");
+ zSep = " OR ";
+ i++;
+ }
+ sqlite3_str_appendf(pSql, " UNION ALL\n");
+ }
+ zSep = "SELECT ";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
+ zSep = ", ";
+ }
+ sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
+ while( az2[i] ){
+ sqlite3_str_appendf(pSql, ", NULL, NULL");
+ i++;
+ }
+ sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n");
+ zSep = " UNION ALL\nSELECT ";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
+ zSep = ", ";
+ }
+ sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
+ while( az2[i] ){
+ sqlite3_str_appendf(pSql, ", 1, B.%s", az2[i]);
+ i++;
+ }
+ sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n ORDER BY");
+ zSep = " ";
+ for(i=1; i<=nPk; i++){
+ sqlite3_str_appendf(pSql, "%s%d", zSep, i);
+ zSep = ", ";
+ }
+ sqlite3_str_appendf(pSql, ";\n");
+
+ if( g.fDebug & DEBUG_DIFF_SQL ){
+ printf("SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
+ goto end_diff_one_table;
+ }
+
+ /* Drop indexes that are missing in the destination */
+ pStmt = db_prepare(
+ "SELECT name FROM main.sqlite_schema"
+ " WHERE type='index' AND tbl_name=%Q"
+ " AND sql IS NOT NULL"
+ " AND sql NOT IN (SELECT sql FROM aux.sqlite_schema"
+ " WHERE type='index' AND tbl_name=%Q"
+ " AND sql IS NOT NULL)",
+ zTab, zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ char *z = safeId((const char*)sqlite3_column_text(pStmt,0));
+ fprintf(out, "DROP INDEX %s;\n", z);
+ sqlite3_free(z);
+ }
+ sqlite3_finalize(pStmt);
+
+ /* Run the query and output differences */
+ if( !g.bSchemaOnly ){
+ pStmt = db_prepare("%s", sqlite3_str_value(pSql));
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int iType = sqlite3_column_int(pStmt, nPk);
+ if( iType==1 || iType==2 ){
+ if( iType==1 ){ /* Change the content of a row */
+ fprintf(out, "%sUPDATE %s", zLead, zId);
+ zSep = " SET";
+ for(i=nPk+1; i<nQ; i+=2){
+ if( sqlite3_column_int(pStmt,i)==0 ) continue;
+ fprintf(out, "%s %s=", zSep, az2[(i+nPk-1)/2]);
+ zSep = ",";
+ printQuoted(out, sqlite3_column_value(pStmt,i+1));
+ }
+ }else{ /* Delete a row */
+ fprintf(out, "%sDELETE FROM %s", zLead, zId);
+ }
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ fprintf(out, "%s %s=", zSep, az2[i]);
+ printQuoted(out, sqlite3_column_value(pStmt,i));
+ zSep = " AND";
+ }
+ fprintf(out, ";\n");
+ }else{ /* Insert a row */
+ fprintf(out, "%sINSERT INTO %s(%s", zLead, zId, az2[0]);
+ for(i=1; az2[i]; i++) fprintf(out, ",%s", az2[i]);
+ fprintf(out, ") VALUES");
+ zSep = "(";
+ for(i=0; i<nPk2; i++){
+ fprintf(out, "%s", zSep);
+ zSep = ",";
+ printQuoted(out, sqlite3_column_value(pStmt,i));
+ }
+ for(i=nPk2+2; i<nQ; i+=2){
+ fprintf(out, ",");
+ printQuoted(out, sqlite3_column_value(pStmt,i));
+ }
+ fprintf(out, ");\n");
+ }
+ }
+ sqlite3_finalize(pStmt);
+ } /* endif !g.bSchemaOnly */
+
+ /* Create indexes that are missing in the source */
+ pStmt = db_prepare(
+ "SELECT sql FROM aux.sqlite_schema"
+ " WHERE type='index' AND tbl_name=%Q"
+ " AND sql IS NOT NULL"
+ " AND sql NOT IN (SELECT sql FROM main.sqlite_schema"
+ " WHERE type='index' AND tbl_name=%Q"
+ " AND sql IS NOT NULL)",
+ zTab, zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
+ }
+ sqlite3_finalize(pStmt);
+
+end_diff_one_table:
+ strFree(pSql);
+ sqlite3_free(zId);
+ namelistFree(az);
+ namelistFree(az2);
+ return;
+}
+
+/*
+** Check that table zTab exists and has the same schema in both the "main"
+** and "aux" databases currently opened by the global db handle. If they
+** do not, output an error message on stderr and exit(1). Otherwise, if
+** the schemas do match, return control to the caller.
+*/
+static void checkSchemasMatch(const char *zTab){
+ sqlite3_stmt *pStmt = db_prepare(
+ "SELECT A.sql=B.sql FROM main.sqlite_schema A, aux.sqlite_schema B"
+ " WHERE A.name=%Q AND B.name=%Q", zTab, zTab
+ );
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( sqlite3_column_int(pStmt,0)==0 ){
+ runtimeError("schema changes for table %s", safeId(zTab));
+ }
+ }else{
+ runtimeError("table %s missing from one or both databases", safeId(zTab));
+ }
+ sqlite3_finalize(pStmt);
+}
+
+/**************************************************************************
+** The following code is copied from fossil. It is used to generate the
+** fossil delta blobs sometimes used in RBU update records.
+*/
+
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned char u8;
+
+/*
+** The width of a hash window in bytes. The algorithm only works if this
+** is a power of 2.
+*/
+#define NHASH 16
+
+/*
+** The current state of the rolling hash.
+**
+** z[] holds the values that have been hashed. z[] is a circular buffer.
+** z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of
+** the window.
+**
+** Hash.a is the sum of all elements of hash.z[]. Hash.b is a weighted
+** sum. Hash.b is z[i]*NHASH + z[i+1]*(NHASH-1) + ... + z[i+NHASH-1]*1.
+** (Each index for z[] should be module NHASH, of course. The %NHASH operator
+** is omitted in the prior expression for brevity.)
+*/
+typedef struct hash hash;
+struct hash {
+ u16 a, b; /* Hash values */
+ u16 i; /* Start of the hash window */
+ char z[NHASH]; /* The values that have been hashed */
+};
+
+/*
+** Initialize the rolling hash using the first NHASH characters of z[]
+*/
+static void hash_init(hash *pHash, const char *z){
+ u16 a, b, i;
+ a = b = 0;
+ for(i=0; i<NHASH; i++){
+ a += z[i];
+ b += (NHASH-i)*z[i];
+ pHash->z[i] = z[i];
+ }
+ pHash->a = a & 0xffff;
+ pHash->b = b & 0xffff;
+ pHash->i = 0;
+}
+
+/*
+** Advance the rolling hash by a single character "c"
+*/
+static void hash_next(hash *pHash, int c){
+ u16 old = pHash->z[pHash->i];
+ pHash->z[pHash->i] = (char)c;
+ pHash->i = (pHash->i+1)&(NHASH-1);
+ pHash->a = pHash->a - old + (char)c;
+ pHash->b = pHash->b - NHASH*old + pHash->a;
+}
+
+/*
+** Return a 32-bit hash value
+*/
+static u32 hash_32bit(hash *pHash){
+ return (pHash->a & 0xffff) | (((u32)(pHash->b & 0xffff))<<16);
+}
+
+/*
+** Write an base-64 integer into the given buffer.
+*/
+static void putInt(unsigned int v, char **pz){
+ static const char zDigits[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
+ /* 123456789 123456789 123456789 123456789 123456789 123456789 123 */
+ int i, j;
+ char zBuf[20];
+ if( v==0 ){
+ *(*pz)++ = '0';
+ return;
+ }
+ for(i=0; v>0; i++, v>>=6){
+ zBuf[i] = zDigits[v&0x3f];
+ }
+ for(j=i-1; j>=0; j--){
+ *(*pz)++ = zBuf[j];
+ }
+}
+
+/*
+** Return the number digits in the base-64 representation of a positive integer
+*/
+static int digit_count(int v){
+ unsigned int i, x;
+ for(i=1, x=64; (unsigned int)v>=x; i++, x <<= 6){}
+ return i;
+}
+
+/*
+** Compute a 32-bit checksum on the N-byte buffer. Return the result.
+*/
+static unsigned int checksum(const char *zIn, size_t N){
+ const unsigned char *z = (const unsigned char *)zIn;
+ unsigned sum0 = 0;
+ unsigned sum1 = 0;
+ unsigned sum2 = 0;
+ unsigned sum3 = 0;
+ while(N >= 16){
+ sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
+ sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
+ sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
+ sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
+ z += 16;
+ N -= 16;
+ }
+ while(N >= 4){
+ sum0 += z[0];
+ sum1 += z[1];
+ sum2 += z[2];
+ sum3 += z[3];
+ z += 4;
+ N -= 4;
+ }
+ sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
+ switch(N){
+ case 3: sum3 += (z[2] << 8);
+ case 2: sum3 += (z[1] << 16);
+ case 1: sum3 += (z[0] << 24);
+ default: ;
+ }
+ return sum3;
+}
+
+/*
+** Create a new delta.
+**
+** The delta is written into a preallocated buffer, zDelta, which
+** should be at least 60 bytes longer than the target file, zOut.
+** The delta string will be NUL-terminated, but it might also contain
+** embedded NUL characters if either the zSrc or zOut files are
+** binary. This function returns the length of the delta string
+** in bytes, excluding the final NUL terminator character.
+**
+** Output Format:
+**
+** The delta begins with a base64 number followed by a newline. This
+** number is the number of bytes in the TARGET file. Thus, given a
+** delta file z, a program can compute the size of the output file
+** simply by reading the first line and decoding the base-64 number
+** found there. The delta_output_size() routine does exactly this.
+**
+** After the initial size number, the delta consists of a series of
+** literal text segments and commands to copy from the SOURCE file.
+** A copy command looks like this:
+**
+** NNN@MMM,
+**
+** where NNN is the number of bytes to be copied and MMM is the offset
+** into the source file of the first byte (both base-64). If NNN is 0
+** it means copy the rest of the input file. Literal text is like this:
+**
+** NNN:TTTTT
+**
+** where NNN is the number of bytes of text (base-64) and TTTTT is the text.
+**
+** The last term is of the form
+**
+** NNN;
+**
+** In this case, NNN is a 32-bit bigendian checksum of the output file
+** that can be used to verify that the delta applied correctly. All
+** numbers are in base-64.
+**
+** Pure text files generate a pure text delta. Binary files generate a
+** delta that may contain some binary data.
+**
+** Algorithm:
+**
+** The encoder first builds a hash table to help it find matching
+** patterns in the source file. 16-byte chunks of the source file
+** sampled at evenly spaced intervals are used to populate the hash
+** table.
+**
+** Next we begin scanning the target file using a sliding 16-byte
+** window. The hash of the 16-byte window in the target is used to
+** search for a matching section in the source file. When a match
+** is found, a copy command is added to the delta. An effort is
+** made to extend the matching section to regions that come before
+** and after the 16-byte hash window. A copy command is only issued
+** if the result would use less space that just quoting the text
+** literally. Literal text is added to the delta for sections that
+** do not match or which can not be encoded efficiently using copy
+** commands.
+*/
+static int rbuDeltaCreate(
+ const char *zSrc, /* The source or pattern file */
+ unsigned int lenSrc, /* Length of the source file */
+ const char *zOut, /* The target file */
+ unsigned int lenOut, /* Length of the target file */
+ char *zDelta /* Write the delta into this buffer */
+){
+ unsigned int i, base;
+ char *zOrigDelta = zDelta;
+ hash h;
+ int nHash; /* Number of hash table entries */
+ int *landmark; /* Primary hash table */
+ int *collide; /* Collision chain */
+ int lastRead = -1; /* Last byte of zSrc read by a COPY command */
+
+ /* Add the target file size to the beginning of the delta
+ */
+ putInt(lenOut, &zDelta);
+ *(zDelta++) = '\n';
+
+ /* If the source file is very small, it means that we have no
+ ** chance of ever doing a copy command. Just output a single
+ ** literal segment for the entire target and exit.
+ */
+ if( lenSrc<=NHASH ){
+ putInt(lenOut, &zDelta);
+ *(zDelta++) = ':';
+ memcpy(zDelta, zOut, lenOut);
+ zDelta += lenOut;
+ putInt(checksum(zOut, lenOut), &zDelta);
+ *(zDelta++) = ';';
+ return (int)(zDelta - zOrigDelta);
+ }
+
+ /* Compute the hash table used to locate matching sections in the
+ ** source file.
+ */
+ nHash = lenSrc/NHASH;
+ collide = sqlite3_malloc( nHash*2*sizeof(int) );
+ landmark = &collide[nHash];
+ memset(landmark, -1, nHash*sizeof(int));
+ memset(collide, -1, nHash*sizeof(int));
+ for(i=0; i<lenSrc-NHASH; i+=NHASH){
+ int hv;
+ hash_init(&h, &zSrc[i]);
+ hv = hash_32bit(&h) % nHash;
+ collide[i/NHASH] = landmark[hv];
+ landmark[hv] = i/NHASH;
+ }
+
+ /* Begin scanning the target file and generating copy commands and
+ ** literal sections of the delta.
+ */
+ base = 0; /* We have already generated everything before zOut[base] */
+ while( base+NHASH<lenOut ){
+ int iSrc, iBlock;
+ int bestCnt, bestOfst=0, bestLitsz=0;
+ hash_init(&h, &zOut[base]);
+ i = 0; /* Trying to match a landmark against zOut[base+i] */
+ bestCnt = 0;
+ while( 1 ){
+ int hv;
+ int limit = 250;
+
+ hv = hash_32bit(&h) % nHash;
+ iBlock = landmark[hv];
+ while( iBlock>=0 && (limit--)>0 ){
+ /*
+ ** The hash window has identified a potential match against
+ ** landmark block iBlock. But we need to investigate further.
+ **
+ ** Look for a region in zOut that matches zSrc. Anchor the search
+ ** at zSrc[iSrc] and zOut[base+i]. Do not include anything prior to
+ ** zOut[base] or after zOut[outLen] nor anything after zSrc[srcLen].
+ **
+ ** Set cnt equal to the length of the match and set ofst so that
+ ** zSrc[ofst] is the first element of the match. litsz is the number
+ ** of characters between zOut[base] and the beginning of the match.
+ ** sz will be the overhead (in bytes) needed to encode the copy
+ ** command. Only generate copy command if the overhead of the
+ ** copy command is less than the amount of literal text to be copied.
+ */
+ int cnt, ofst, litsz;
+ int j, k, x, y;
+ int sz;
+
+ /* Beginning at iSrc, match forwards as far as we can. j counts
+ ** the number of characters that match */
+ iSrc = iBlock*NHASH;
+ for(
+ j=0, x=iSrc, y=base+i;
+ (unsigned int)x<lenSrc && (unsigned int)y<lenOut;
+ j++, x++, y++
+ ){
+ if( zSrc[x]!=zOut[y] ) break;
+ }
+ j--;
+
+ /* Beginning at iSrc-1, match backwards as far as we can. k counts
+ ** the number of characters that match */
+ for(k=1; k<iSrc && (unsigned int)k<=i; k++){
+ if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
+ }
+ k--;
+
+ /* Compute the offset and size of the matching region */
+ ofst = iSrc-k;
+ cnt = j+k+1;
+ litsz = i-k; /* Number of bytes of literal text before the copy */
+ /* sz will hold the number of bytes needed to encode the "insert"
+ ** command and the copy command, not counting the "insert" text */
+ sz = digit_count(i-k)+digit_count(cnt)+digit_count(ofst)+3;
+ if( cnt>=sz && cnt>bestCnt ){
+ /* Remember this match only if it is the best so far and it
+ ** does not increase the file size */
+ bestCnt = cnt;
+ bestOfst = iSrc-k;
+ bestLitsz = litsz;
+ }
+
+ /* Check the next matching block */
+ iBlock = collide[iBlock];
+ }
+
+ /* We have a copy command that does not cause the delta to be larger
+ ** than a literal insert. So add the copy command to the delta.
+ */
+ if( bestCnt>0 ){
+ if( bestLitsz>0 ){
+ /* Add an insert command before the copy */
+ putInt(bestLitsz,&zDelta);
+ *(zDelta++) = ':';
+ memcpy(zDelta, &zOut[base], bestLitsz);
+ zDelta += bestLitsz;
+ base += bestLitsz;
+ }
+ base += bestCnt;
+ putInt(bestCnt, &zDelta);
+ *(zDelta++) = '@';
+ putInt(bestOfst, &zDelta);
+ *(zDelta++) = ',';
+ if( bestOfst + bestCnt -1 > lastRead ){
+ lastRead = bestOfst + bestCnt - 1;
+ }
+ bestCnt = 0;
+ break;
+ }
+
+ /* If we reach this point, it means no match is found so far */
+ if( base+i+NHASH>=lenOut ){
+ /* We have reached the end of the file and have not found any
+ ** matches. Do an "insert" for everything that does not match */
+ putInt(lenOut-base, &zDelta);
+ *(zDelta++) = ':';
+ memcpy(zDelta, &zOut[base], lenOut-base);
+ zDelta += lenOut-base;
+ base = lenOut;
+ break;
+ }
+
+ /* Advance the hash by one character. Keep looking for a match */
+ hash_next(&h, zOut[base+i+NHASH]);
+ i++;
+ }
+ }
+ /* Output a final "insert" record to get all the text at the end of
+ ** the file that does not match anything in the source file.
+ */
+ if( base<lenOut ){
+ putInt(lenOut-base, &zDelta);
+ *(zDelta++) = ':';
+ memcpy(zDelta, &zOut[base], lenOut-base);
+ zDelta += lenOut-base;
+ }
+ /* Output the final checksum record. */
+ putInt(checksum(zOut, lenOut), &zDelta);
+ *(zDelta++) = ';';
+ sqlite3_free(collide);
+ return (int)(zDelta - zOrigDelta);
+}
+
+/*
+** End of code copied from fossil.
+**************************************************************************/
+
+static void strPrintfArray(
+ sqlite3_str *pStr, /* String object to append to */
+ const char *zSep, /* Separator string */
+ const char *zFmt, /* Format for each entry */
+ char **az, int n /* Array of strings & its size (or -1) */
+){
+ int i;
+ for(i=0; az[i] && (i<n || n<0); i++){
+ if( i!=0 ) sqlite3_str_appendf(pStr, "%s", zSep);
+ sqlite3_str_appendf(pStr, zFmt, az[i], az[i], az[i]);
+ }
+}
+
+static void getRbudiffQuery(
+ const char *zTab,
+ char **azCol,
+ int nPK,
+ int bOtaRowid,
+ sqlite3_str *pSql
+){
+ int i;
+
+ /* First the newly inserted rows: **/
+ sqlite3_str_appendf(pSql, "SELECT ");
+ strPrintfArray(pSql, ", ", "%s", azCol, -1);
+ sqlite3_str_appendf(pSql, ", 0, "); /* Set ota_control to 0 for an insert */
+ strPrintfArray(pSql, ", ", "NULL", azCol, -1);
+ sqlite3_str_appendf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab);
+ sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
+ sqlite3_str_appendf(pSql, " main.%Q AS o WHERE ", zTab);
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
+ sqlite3_str_appendf(pSql, "\n) AND ");
+ strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
+
+ /* Deleted rows: */
+ sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
+ strPrintfArray(pSql, ", ", "%s", azCol, nPK);
+ if( azCol[nPK] ){
+ sqlite3_str_appendf(pSql, ", ");
+ strPrintfArray(pSql, ", ", "NULL", &azCol[nPK], -1);
+ }
+ sqlite3_str_appendf(pSql, ", 1, "); /* Set ota_control to 1 for a delete */
+ strPrintfArray(pSql, ", ", "NULL", azCol, -1);
+ sqlite3_str_appendf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab);
+ sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
+ sqlite3_str_appendf(pSql, " aux.%Q AS o WHERE ", zTab);
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
+ sqlite3_str_appendf(pSql, "\n) AND ");
+ strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
+
+ /* Updated rows. If all table columns are part of the primary key, there
+ ** can be no updates. In this case this part of the compound SELECT can
+ ** be omitted altogether. */
+ if( azCol[nPK] ){
+ sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
+ strPrintfArray(pSql, ", ", "n.%s", azCol, nPK);
+ sqlite3_str_appendf(pSql, ",\n");
+ strPrintfArray(pSql, " ,\n",
+ " CASE WHEN n.%s IS o.%s THEN NULL ELSE n.%s END", &azCol[nPK], -1
+ );
+
+ if( bOtaRowid==0 ){
+ sqlite3_str_appendf(pSql, ", '");
+ strPrintfArray(pSql, "", ".", azCol, nPK);
+ sqlite3_str_appendf(pSql, "' ||\n");
+ }else{
+ sqlite3_str_appendf(pSql, ",\n");
+ }
+ strPrintfArray(pSql, " ||\n",
+ " CASE WHEN n.%s IS o.%s THEN '.' ELSE 'x' END", &azCol[nPK], -1
+ );
+ sqlite3_str_appendf(pSql, "\nAS ota_control, ");
+ strPrintfArray(pSql, ", ", "NULL", azCol, nPK);
+ sqlite3_str_appendf(pSql, ",\n");
+ strPrintfArray(pSql, " ,\n",
+ " CASE WHEN n.%s IS o.%s THEN NULL ELSE o.%s END", &azCol[nPK], -1
+ );
+
+ sqlite3_str_appendf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ",
+ zTab, zTab);
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
+ sqlite3_str_appendf(pSql, " AND ota_control LIKE '%%x%%'");
+ }
+
+ /* Now add an ORDER BY clause to sort everything by PK. */
+ sqlite3_str_appendf(pSql, "\nORDER BY ");
+ for(i=1; i<=nPK; i++) sqlite3_str_appendf(pSql, "%s%d", ((i>1)?", ":""), i);
+}
+
+static void rbudiff_one_table(const char *zTab, FILE *out){
+ int bOtaRowid; /* True to use an ota_rowid column */
+ int nPK; /* Number of primary key columns in table */
+ char **azCol; /* NULL terminated array of col names */
+ int i;
+ int nCol;
+ sqlite3_str *pCt; /* The "CREATE TABLE data_xxx" statement */
+ sqlite3_str *pSql; /* Query to find differences */
+ sqlite3_str *pInsert; /* First part of output INSERT statement */
+ sqlite3_stmt *pStmt = 0;
+ int nRow = 0; /* Total rows in data_xxx table */
+
+ /* --rbu mode must use real primary keys. */
+ g.bSchemaPK = 1;
+ pCt = sqlite3_str_new(0);
+ pSql = sqlite3_str_new(0);
+ pInsert = sqlite3_str_new(0);
+
+ /* Check that the schemas of the two tables match. Exit early otherwise. */
+ checkSchemasMatch(zTab);
+
+ /* Grab the column names and PK details for the table(s). If no usable PK
+ ** columns are found, bail out early. */
+ azCol = columnNames("main", zTab, &nPK, &bOtaRowid);
+ if( azCol==0 ){
+ runtimeError("table %s has no usable PK columns", zTab);
+ }
+ for(nCol=0; azCol[nCol]; nCol++);
+
+ /* Build and output the CREATE TABLE statement for the data_xxx table */
+ sqlite3_str_appendf(pCt, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab);
+ if( bOtaRowid ) sqlite3_str_appendf(pCt, "rbu_rowid, ");
+ strPrintfArray(pCt, ", ", "%s", &azCol[bOtaRowid], -1);
+ sqlite3_str_appendf(pCt, ", rbu_control);");
+
+ /* Get the SQL for the query to retrieve data from the two databases */
+ getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, pSql);
+
+ /* Build the first part of the INSERT statement output for each row
+ ** in the data_xxx table. */
+ sqlite3_str_appendf(pInsert, "INSERT INTO 'data_%q' (", zTab);
+ if( bOtaRowid ) sqlite3_str_appendf(pInsert, "rbu_rowid, ");
+ strPrintfArray(pInsert, ", ", "%s", &azCol[bOtaRowid], -1);
+ sqlite3_str_appendf(pInsert, ", rbu_control) VALUES(");
+
+ pStmt = db_prepare("%s", sqlite3_str_value(pSql));
+
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+
+ /* If this is the first row output, print out the CREATE TABLE
+ ** statement first. And reset pCt so that it will not be
+ ** printed again. */
+ if( sqlite3_str_length(pCt) ){
+ fprintf(out, "%s\n", sqlite3_str_value(pCt));
+ sqlite3_str_reset(pCt);
+ }
+
+ /* Output the first part of the INSERT statement */
+ fprintf(out, "%s", sqlite3_str_value(pInsert));
+ nRow++;
+
+ if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){
+ for(i=0; i<=nCol; i++){
+ if( i>0 ) fprintf(out, ", ");
+ printQuoted(out, sqlite3_column_value(pStmt, i));
+ }
+ }else{
+ char *zOtaControl;
+ int nOtaControl = sqlite3_column_bytes(pStmt, nCol);
+
+ zOtaControl = (char*)sqlite3_malloc(nOtaControl+1);
+ memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1);
+
+ for(i=0; i<nCol; i++){
+ int bDone = 0;
+ if( i>=nPK
+ && sqlite3_column_type(pStmt, i)==SQLITE_BLOB
+ && sqlite3_column_type(pStmt, nCol+1+i)==SQLITE_BLOB
+ ){
+ const char *aSrc = sqlite3_column_blob(pStmt, nCol+1+i);
+ int nSrc = sqlite3_column_bytes(pStmt, nCol+1+i);
+ const char *aFinal = sqlite3_column_blob(pStmt, i);
+ int nFinal = sqlite3_column_bytes(pStmt, i);
+ char *aDelta;
+ int nDelta;
+
+ aDelta = sqlite3_malloc(nFinal + 60);
+ nDelta = rbuDeltaCreate(aSrc, nSrc, aFinal, nFinal, aDelta);
+ if( nDelta<nFinal ){
+ int j;
+ fprintf(out, "x'");
+ for(j=0; j<nDelta; j++) fprintf(out, "%02x", (u8)aDelta[j]);
+ fprintf(out, "'");
+ zOtaControl[i-bOtaRowid] = 'f';
+ bDone = 1;
+ }
+ sqlite3_free(aDelta);
+ }
+
+ if( bDone==0 ){
+ printQuoted(out, sqlite3_column_value(pStmt, i));
+ }
+ fprintf(out, ", ");
+ }
+ fprintf(out, "'%s'", zOtaControl);
+ sqlite3_free(zOtaControl);
+ }
+
+ /* And the closing bracket of the insert statement */
+ fprintf(out, ");\n");
+ }
+
+ sqlite3_finalize(pStmt);
+ if( nRow>0 ){
+ sqlite3_str *pCnt = sqlite3_str_new(0);
+ sqlite3_str_appendf(pCnt,
+ "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow);
+ fprintf(out, "%s\n", sqlite3_str_value(pCnt));
+ strFree(pCnt);
+ }
+
+ strFree(pCt);
+ strFree(pSql);
+ strFree(pInsert);
+}
+
+/*
+** Display a summary of differences between two versions of the same
+** table table.
+**
+** * Number of rows changed
+** * Number of rows added
+** * Number of rows deleted
+** * Number of identical rows
+*/
+static void summarize_one_table(const char *zTab, FILE *out){
+ char *zId = safeId(zTab); /* Name of table (translated for us in SQL) */
+ char **az = 0; /* Columns in main */
+ char **az2 = 0; /* Columns in aux */
+ int nPk; /* Primary key columns in main */
+ int nPk2; /* Primary key columns in aux */
+ int n = 0; /* Number of columns in main */
+ int n2; /* Number of columns in aux */
+ int i; /* Loop counter */
+ const char *zSep; /* Separator string */
+ sqlite3_str *pSql; /* Comparison query */
+ sqlite3_stmt *pStmt; /* Query statement to do the diff */
+ sqlite3_int64 nUpdate; /* Number of updated rows */
+ sqlite3_int64 nUnchanged; /* Number of unmodified rows */
+ sqlite3_int64 nDelete; /* Number of deleted rows */
+ sqlite3_int64 nInsert; /* Number of inserted rows */
+
+ pSql = sqlite3_str_new(0);
+ if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){
+ if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
+ /* Table missing from second database. */
+ Wfprintf(out, "%s: missing from second database\n", zTab);
+ }
+ goto end_summarize_one_table;
+ }
+
+ if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
+ /* Table missing from source */
+ Wfprintf(out, "%s: missing from first database\n", zTab);
+ goto end_summarize_one_table;
+ }
+
+ az = columnNames("main", zTab, &nPk, 0);
+ az2 = columnNames("aux", zTab, &nPk2, 0);
+ if( az && az2 ){
+ for(n=0; az[n]; n++){
+ if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
+ }
+ }
+ if( az==0
+ || az2==0
+ || nPk!=nPk2
+ || az[n]
+ ){
+ /* Schema mismatch */
+ Wfprintf(out, "%s: incompatible schema\n", zTab);
+ goto end_summarize_one_table;
+ }
+
+ /* Build the comparison query */
+ for(n2=n; az[n2]; n2++){}
+ sqlite3_str_appendf(pSql, "SELECT 1, count(*)");
+ if( n2==nPk2 ){
+ sqlite3_str_appendf(pSql, ", 0\n");
+ }else{
+ zSep = ", sum(";
+ for(i=nPk; az[i]; i++){
+ sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]);
+ zSep = " OR ";
+ }
+ sqlite3_str_appendf(pSql, ")\n");
+ }
+ sqlite3_str_appendf(pSql, " FROM main.%s A, aux.%s B\n", zId, zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, " UNION ALL\n");
+ sqlite3_str_appendf(pSql, "SELECT 2, count(*), 0\n");
+ sqlite3_str_appendf(pSql, " FROM main.%s A\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B ", zId);
+ zSep = "WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n");
+ sqlite3_str_appendf(pSql, " UNION ALL\n");
+ sqlite3_str_appendf(pSql, "SELECT 3, count(*), 0\n");
+ sqlite3_str_appendf(pSql, " FROM aux.%s B\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A ", zId);
+ zSep = "WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n ORDER BY 1;\n");
+
+ if( (g.fDebug & DEBUG_DIFF_SQL)!=0 ){
+ Wfprintf(stdout, "SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
+ goto end_summarize_one_table;
+ }
+
+ /* Run the query and output difference summary */
+ pStmt = db_prepare("%s", sqlite3_str_value(pSql));
+ nUpdate = 0;
+ nInsert = 0;
+ nDelete = 0;
+ nUnchanged = 0;
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ switch( sqlite3_column_int(pStmt,0) ){
+ case 1:
+ nUpdate = sqlite3_column_int64(pStmt,2);
+ nUnchanged = sqlite3_column_int64(pStmt,1) - nUpdate;
+ break;
+ case 2:
+ nDelete = sqlite3_column_int64(pStmt,1);
+ break;
+ case 3:
+ nInsert = sqlite3_column_int64(pStmt,1);
+ break;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ Wfprintf(out,
+ "%s: %lld changes, %lld inserts, %lld deletes, %lld unchanged\n",
+ zTab, nUpdate, nInsert, nDelete, nUnchanged);
+
+end_summarize_one_table:
+ strFree(pSql);
+ sqlite3_free(zId);
+ namelistFree(az);
+ namelistFree(az2);
+ return;
+}
+
+/*
+** Write a 64-bit signed integer as a varint onto out
+*/
+static void putsVarint(FILE *out, sqlite3_uint64 v){
+ int i, n;
+ unsigned char p[12];
+ if( v & (((sqlite3_uint64)0xff000000)<<32) ){
+ p[8] = (unsigned char)v;
+ v >>= 8;
+ for(i=7; i>=0; i--){
+ p[i] = (unsigned char)((v & 0x7f) | 0x80);
+ v >>= 7;
+ }
+ fwrite(p, 8, 1, out);
+ }else{
+ n = 9;
+ do{
+ p[n--] = (unsigned char)((v & 0x7f) | 0x80);
+ v >>= 7;
+ }while( v!=0 );
+ p[9] &= 0x7f;
+ fwrite(p+n+1, 9-n, 1, out);
+ }
+}
+
+/*
+** Write an SQLite value onto out.
+*/
+static void putValue(FILE *out, sqlite3_stmt *pStmt, int k){
+ int iDType = sqlite3_column_type(pStmt, k);
+ sqlite3_int64 iX;
+ double rX;
+ sqlite3_uint64 uX;
+ int j;
+
+ putc(iDType, out);
+ switch( iDType ){
+ case SQLITE_INTEGER:
+ iX = sqlite3_column_int64(pStmt, k);
+ memcpy(&uX, &iX, 8);
+ for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out);
+ break;
+ case SQLITE_FLOAT:
+ rX = sqlite3_column_double(pStmt, k);
+ memcpy(&uX, &rX, 8);
+ for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out);
+ break;
+ case SQLITE_TEXT:
+ iX = sqlite3_column_bytes(pStmt, k);
+ putsVarint(out, (sqlite3_uint64)iX);
+ fwrite(sqlite3_column_text(pStmt, k),1,(size_t)iX,out);
+ break;
+ case SQLITE_BLOB:
+ iX = sqlite3_column_bytes(pStmt, k);
+ putsVarint(out, (sqlite3_uint64)iX);
+ fwrite(sqlite3_column_blob(pStmt, k),1,(size_t)iX,out);
+ break;
+ case SQLITE_NULL:
+ break;
+ }
+}
+
+/*
+** Generate a CHANGESET for all differences from main.zTab to aux.zTab.
+*/
+static void changeset_one_table(const char *zTab, FILE *out){
+ sqlite3_stmt *pStmt; /* SQL statment */
+ char *zId = safeId(zTab); /* Escaped name of the table */
+ char **azCol = 0; /* List of escaped column names */
+ int nCol = 0; /* Number of columns */
+ int *aiFlg = 0; /* 0 if column is not part of PK */
+ int *aiPk = 0; /* Column numbers for each PK column */
+ int nPk = 0; /* Number of PRIMARY KEY columns */
+ sqlite3_str *pSql; /* SQL for the diff query */
+ int i, k; /* Loop counters */
+ const char *zSep; /* List separator */
+
+ /* Check that the schemas of the two tables match. Exit early otherwise. */
+ checkSchemasMatch(zTab);
+ pSql = sqlite3_str_new(0);
+
+ pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab);
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ nCol++;
+ azCol = sqlite3_realloc(azCol, sizeof(char*)*nCol);
+ if( azCol==0 ) runtimeError("out of memory");
+ aiFlg = sqlite3_realloc(aiFlg, sizeof(int)*nCol);
+ if( aiFlg==0 ) runtimeError("out of memory");
+ azCol[nCol-1] = safeId((const char*)sqlite3_column_text(pStmt,1));
+ aiFlg[nCol-1] = i = sqlite3_column_int(pStmt,5);
+ if( i>0 ){
+ if( i>nPk ){
+ nPk = i;
+ aiPk = sqlite3_realloc(aiPk, sizeof(int)*nPk);
+ if( aiPk==0 ) runtimeError("out of memory");
+ }
+ aiPk[i-1] = nCol-1;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( nPk==0 ) goto end_changeset_one_table;
+ if( nCol>nPk ){
+ sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_UPDATE);
+ for(i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
+ }else{
+ sqlite3_str_appendf(pSql, ",\n A.%s IS NOT B.%s, A.%s, B.%s",
+ azCol[i], azCol[i], azCol[i], azCol[i]);
+ }
+ }
+ sqlite3_str_appendf(pSql,"\n FROM main.%s A, aux.%s B\n", zId, zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
+ zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
+ zSep = " AND";
+ }
+ zSep = "\n AND (";
+ for(i=0; i<nCol; i++){
+ if( aiFlg[i] ) continue;
+ sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, azCol[i], azCol[i]);
+ zSep = " OR\n ";
+ }
+ sqlite3_str_appendf(pSql,")\n UNION ALL\n");
+ }
+ sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_DELETE);
+ for(i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
+ }else{
+ sqlite3_str_appendf(pSql, ",\n 1, A.%s, NULL", azCol[i]);
+ }
+ }
+ sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
+ zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n UNION ALL\n");
+ sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_INSERT);
+ for(i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ sqlite3_str_appendf(pSql, ",\n B.%s", azCol[i]);
+ }else{
+ sqlite3_str_appendf(pSql, ",\n 1, NULL, B.%s", azCol[i]);
+ }
+ }
+ sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
+ zSep = " WHERE";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
+ zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
+ zSep = " AND";
+ }
+ sqlite3_str_appendf(pSql, ")\n");
+ sqlite3_str_appendf(pSql, " ORDER BY");
+ zSep = " ";
+ for(i=0; i<nPk; i++){
+ sqlite3_str_appendf(pSql, "%s %d", zSep, aiPk[i]+2);
+ zSep = ",";
+ }
+ sqlite3_str_appendf(pSql, ";\n");
+
+ if( g.fDebug & DEBUG_DIFF_SQL ){
+ Wfprintf(stdout, "SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
+ goto end_changeset_one_table;
+ }
+
+ putc('T', out);
+ putsVarint(out, (sqlite3_uint64)nCol);
+ for(i=0; i<nCol; i++) putc(aiFlg[i], out);
+ fwrite(zTab, 1, strlen(zTab), out);
+ putc(0, out);
+
+ pStmt = db_prepare("%s", sqlite3_str_value(pSql));
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int iType = sqlite3_column_int(pStmt,0);
+ putc(iType, out);
+ putc(0, out);
+ switch( sqlite3_column_int(pStmt,0) ){
+ case SQLITE_UPDATE: {
+ for(k=1, i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ putValue(out, pStmt, k);
+ k++;
+ }else if( sqlite3_column_int(pStmt,k) ){
+ putValue(out, pStmt, k+1);
+ k += 3;
+ }else{
+ putc(0, out);
+ k += 3;
+ }
+ }
+ for(k=1, i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ putc(0, out);
+ k++;
+ }else if( sqlite3_column_int(pStmt,k) ){
+ putValue(out, pStmt, k+2);
+ k += 3;
+ }else{
+ putc(0, out);
+ k += 3;
+ }
+ }
+ break;
+ }
+ case SQLITE_INSERT: {
+ for(k=1, i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ putValue(out, pStmt, k);
+ k++;
+ }else{
+ putValue(out, pStmt, k+2);
+ k += 3;
+ }
+ }
+ break;
+ }
+ case SQLITE_DELETE: {
+ for(k=1, i=0; i<nCol; i++){
+ if( aiFlg[i] ){
+ putValue(out, pStmt, k);
+ k++;
+ }else{
+ putValue(out, pStmt, k+1);
+ k += 3;
+ }
+ }
+ break;
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+
+end_changeset_one_table:
+ while( nCol>0 ) sqlite3_free(azCol[--nCol]);
+ sqlite3_free(azCol);
+ sqlite3_free(aiPk);
+ sqlite3_free(zId);
+ sqlite3_free(aiFlg);
+ strFree(pSql);
+}
+
+/*
+** Return true if the ascii character passed as the only argument is a
+** whitespace character. Otherwise return false.
+*/
+static int is_whitespace(char x){
+ return (x==' ' || x=='\t' || x=='\n' || x=='\r');
+}
+
+/*
+** Extract the next SQL keyword or quoted string from buffer zIn and copy it
+** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes.
+** Return a pointer to the character within zIn immediately following
+** the token or quoted string just extracted.
+*/
+static const char *gobble_token(const char *zIn, char *zBuf, int nBuf){
+ const char *p = zIn;
+ char *pOut = zBuf;
+ char *pEnd = &pOut[nBuf-1];
+ char q = 0; /* quote character, if any */
+
+ if( p==0 ) return 0;
+ while( is_whitespace(*p) ) p++;
+ switch( *p ){
+ case '"': q = '"'; break;
+ case '\'': q = '\''; break;
+ case '`': q = '`'; break;
+ case '[': q = ']'; break;
+ }
+
+ if( q ){
+ p++;
+ while( *p && pOut<pEnd ){
+ if( *p==q ){
+ p++;
+ if( *p!=q ) break;
+ }
+ if( pOut<pEnd ) *pOut++ = *p;
+ p++;
+ }
+ }else{
+ while( *p && !is_whitespace(*p) && *p!='(' ){
+ if( pOut<pEnd ) *pOut++ = *p;
+ p++;
+ }
+ }
+
+ *pOut = '\0';
+ return p;
+}
+
+/*
+** This function is the implementation of SQL scalar function "module_name":
+**
+** module_name(SQL)
+**
+** The only argument should be an SQL statement of the type that may appear
+** in the sqlite_schema table. If the statement is a "CREATE VIRTUAL TABLE"
+** statement, then the value returned is the name of the module that it
+** uses. Otherwise, if the statement is not a CVT, NULL is returned.
+*/
+static void module_name_func(
+ sqlite3_context *pCtx,
+ int nVal, sqlite3_value **apVal
+){
+ const char *zSql;
+ char zToken[32];
+
+ assert( nVal==1 );
+ zSql = (const char*)sqlite3_value_text(apVal[0]);
+
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+ if( zSql==0 || sqlite3_stricmp(zToken, "create") ) return;
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+ if( zSql==0 || sqlite3_stricmp(zToken, "virtual") ) return;
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+ if( zSql==0 || sqlite3_stricmp(zToken, "table") ) return;
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+ if( zSql==0 ) return;
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+ if( zSql==0 || sqlite3_stricmp(zToken, "using") ) return;
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
+
+ sqlite3_result_text(pCtx, zToken, -1, SQLITE_TRANSIENT);
+}
+
+/*
+** Return the text of an SQL statement that itself returns the list of
+** tables to process within the database.
+*/
+const char *all_tables_sql(){
+ if( g.bHandleVtab ){
+ int rc;
+
+ rc = sqlite3_exec(g.db,
+ "CREATE TEMP TABLE tblmap(module COLLATE nocase, postfix);"
+ "INSERT INTO temp.tblmap VALUES"
+ "('fts3', '_content'), ('fts3', '_segments'), ('fts3', '_segdir'),"
+
+ "('fts4', '_content'), ('fts4', '_segments'), ('fts4', '_segdir'),"
+ "('fts4', '_docsize'), ('fts4', '_stat'),"
+
+ "('fts5', '_data'), ('fts5', '_idx'), ('fts5', '_content'),"
+ "('fts5', '_docsize'), ('fts5', '_config'),"
+
+ "('rtree', '_node'), ('rtree', '_rowid'), ('rtree', '_parent');"
+ , 0, 0, 0
+ );
+ assert( rc==SQLITE_OK );
+
+ rc = sqlite3_create_function(
+ g.db, "module_name", 1, SQLITE_UTF8, 0, module_name_func, 0, 0
+ );
+ assert( rc==SQLITE_OK );
+
+ return
+ "SELECT name FROM main.sqlite_schema\n"
+ " WHERE type='table' AND (\n"
+ " module_name(sql) IS NULL OR \n"
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
+ " ) AND name NOT IN (\n"
+ " SELECT a.name || b.postfix \n"
+ "FROM main.sqlite_schema AS a, temp.tblmap AS b \n"
+ "WHERE module_name(a.sql) = b.module\n"
+ " )\n"
+ "UNION \n"
+ "SELECT name FROM aux.sqlite_schema\n"
+ " WHERE type='table' AND (\n"
+ " module_name(sql) IS NULL OR \n"
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
+ " ) AND name NOT IN (\n"
+ " SELECT a.name || b.postfix \n"
+ "FROM aux.sqlite_schema AS a, temp.tblmap AS b \n"
+ "WHERE module_name(a.sql) = b.module\n"
+ " )\n"
+ " ORDER BY name";
+ }else{
+ return
+ "SELECT name FROM main.sqlite_schema\n"
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
+ " UNION\n"
+ "SELECT name FROM aux.sqlite_schema\n"
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
+ " ORDER BY name";
+ }
+}
+
+/*
+** Print sketchy documentation for this utility program
+*/
+static void showHelp(void){
+ Wfprintf(stdout, "Usage: %s [options] DB1 DB2\n", g.zArgv0);
+ Wfprintf(stdout,
+"Output SQL text that would transform DB1 into DB2.\n"
+"Options:\n"
+" --changeset FILE Write a CHANGESET into FILE\n"
+" -L|--lib LIBRARY Load an SQLite extension library\n"
+" --primarykey Use schema-defined PRIMARY KEYs\n"
+" --rbu Output SQL to create/populate RBU table(s)\n"
+" --schema Show only differences in the schema\n"
+" --summary Show only a summary of the differences\n"
+" --table TAB Show only differences in table TAB\n"
+" --transaction Show SQL output inside a transaction\n"
+" --vtab Handle fts3, fts4, fts5 and rtree tables\n"
+"See https://sqlite.org/sqldiff.html for detailed explanation.\n"
+ );
+}
+
+int main(int argc, char **argv){
+ const char *zDb1 = 0;
+ const char *zDb2 = 0;
+ int i;
+ int rc;
+ char *zErrMsg = 0;
+ char *zSql;
+ sqlite3_stmt *pStmt;
+ char *zTab = 0;
+ FILE *out = stdout;
+ void (*xDiff)(const char*,FILE*) = diff_one_table;
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+ int nExt = 0;
+ char **azExt = 0;
+#endif
+ int useTransaction = 0;
+ int neverUseTransaction = 0;
+
+ g.zArgv0 = argv[0];
+ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"changeset")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ out = fopen(argv[++i], "wb");
+ if( out==0 ) cmdlineError("cannot open: %s", argv[i]);
+ xDiff = changeset_one_table;
+ neverUseTransaction = 1;
+ }else
+ if( strcmp(z,"debug")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ g.fDebug = strtol(argv[++i], 0, 0);
+ }else
+ if( strcmp(z,"help")==0 ){
+ showHelp();
+ return 0;
+ }else
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+ if( strcmp(z,"lib")==0 || strcmp(z,"L")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ azExt = realloc(azExt, sizeof(azExt[0])*(nExt+1));
+ if( azExt==0 ) cmdlineError("out of memory");
+ azExt[nExt++] = argv[++i];
+ }else
+#endif
+ if( strcmp(z,"primarykey")==0 ){
+ g.bSchemaPK = 1;
+ }else
+ if( strcmp(z,"rbu")==0 ){
+ xDiff = rbudiff_one_table;
+ }else
+ if( strcmp(z,"schema")==0 ){
+ g.bSchemaOnly = 1;
+ }else
+ if( strcmp(z,"summary")==0 ){
+ xDiff = summarize_one_table;
+ }else
+ if( strcmp(z,"table")==0 ){
+ if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
+ zTab = argv[++i];
+ g.bSchemaCompare =
+ sqlite3_stricmp(zTab, "sqlite_schema")==0
+ || sqlite3_stricmp(zTab, "sqlite_master")==0;
+ }else
+ if( strcmp(z,"transaction")==0 ){
+ useTransaction = 1;
+ }else
+ if( strcmp(z,"vtab")==0 ){
+ g.bHandleVtab = 1;
+ }else
+ {
+ cmdlineError("unknown option: %s", argv[i]);
+ }
+ }else if( zDb1==0 ){
+ zDb1 = argv[i];
+ }else if( zDb2==0 ){
+ zDb2 = argv[i];
+ }else{
+ cmdlineError("unknown argument: %s", argv[i]);
+ }
+ }
+ if( zDb2==0 ){
+ cmdlineError("two database arguments required");
+ }
+ if( g.bSchemaOnly && g.bSchemaCompare ){
+ cmdlineError("The --schema option is useless with --table %s .", zTab);
+ }
+ rc = sqlite3_open(zDb1, &g.db);
+ if( rc ){
+ cmdlineError("cannot open database file \"%s\"", zDb1);
+ }
+ rc = sqlite3_exec(g.db, "SELECT * FROM sqlite_schema", 0, 0, &zErrMsg);
+ if( rc || zErrMsg ){
+ cmdlineError("\"%s\" does not appear to be a valid SQLite database", zDb1);
+ }
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+ sqlite3_enable_load_extension(g.db, 1);
+ for(i=0; i<nExt; i++){
+ rc = sqlite3_load_extension(g.db, azExt[i], 0, &zErrMsg);
+ if( rc || zErrMsg ){
+ cmdlineError("error loading %s: %s", azExt[i], zErrMsg);
+ }
+ }
+ free(azExt);
+#endif
+ zSql = sqlite3_mprintf("ATTACH %Q as aux;", zDb2);
+ rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg);
+ sqlite3_free(zSql);
+ zSql = 0;
+ if( rc || zErrMsg ){
+ cmdlineError("cannot attach database \"%s\"", zDb2);
+ }
+ rc = sqlite3_exec(g.db, "SELECT * FROM aux.sqlite_schema", 0, 0, &zErrMsg);
+ if( rc || zErrMsg ){
+ cmdlineError("\"%s\" does not appear to be a valid SQLite database", zDb2);
+ }
+
+ if( neverUseTransaction ) useTransaction = 0;
+ if( useTransaction ) Wfprintf(out, "BEGIN TRANSACTION;\n");
+ if( xDiff==rbudiff_one_table ){
+ Wfprintf(out, "CREATE TABLE IF NOT EXISTS rbu_count"
+ "(tbl TEXT PRIMARY KEY COLLATE NOCASE, cnt INTEGER) "
+ "WITHOUT ROWID;\n"
+ );
+ }
+ if( zTab ){
+ xDiff(zTab, out);
+ }else{
+ /* Handle tables one by one */
+ pStmt = db_prepare("%s", all_tables_sql() );
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ xDiff((const char*)sqlite3_column_text(pStmt,0), out);
+ }
+ sqlite3_finalize(pStmt);
+ }
+ if( useTransaction ) Wfprintf(stdout,"COMMIT;\n");
+
+ /* TBD: Handle trigger differences */
+ /* TBD: Handle view differences */
+ sqlite3_close(g.db);
+ return 0;
+}
diff --git a/tool/sqlite3_analyzer.c.in b/tool/sqlite3_analyzer.c.in
new file mode 100644
index 0000000..2d799ed
--- /dev/null
+++ b/tool/sqlite3_analyzer.c.in
@@ -0,0 +1,76 @@
+/*
+** Read an SQLite database file and analyze its space utilization. Generate
+** text on standard output.
+*/
+#define TCLSH_INIT_PROC sqlite3_analyzer_init_proc
+#define SQLITE_ENABLE_DBSTAT_VTAB 1
+#undef SQLITE_THREADSAFE
+#define SQLITE_THREADSAFE 0
+#undef SQLITE_ENABLE_COLUMN_METADATA
+#define SQLITE_OMIT_DECLTYPE 1
+#define SQLITE_OMIT_DEPRECATED 1
+#define SQLITE_OMIT_PROGRESS_CALLBACK 1
+#define SQLITE_OMIT_SHARED_CACHE 1
+#define SQLITE_DEFAULT_MEMSTATUS 0
+#define SQLITE_MAX_EXPR_DEPTH 0
+#define SQLITE_OMIT_LOAD_EXTENSION 1
+#if !defined(SQLITE_AMALGAMATION) && !defined(USE_EXTERNAL_SQLITE)
+INCLUDE sqlite3.c
+#endif
+INCLUDE $ROOT/src/tclsqlite.c
+
+#if defined(_WIN32)
+INCLUDE $ROOT/ext/consio/console_io.h
+INCLUDE $ROOT/ext/consio/console_io.c
+
+/* Substitute "puts" command. Only these forms recognized:
+**
+** puts STRING
+** puts stderr STRING
+** puts -nonewline STRING
+*/
+static int subst_puts(
+ void *NotUsed,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const*objv
+){
+ FILE *pOut = stdout;
+ const char *zOut;
+ int addNewLine = 1;
+ if( objc==2 ){
+ zOut = Tcl_GetString(objv[1]);
+ }else if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?stderr|-nonewline? STRING");
+ return TCL_ERROR;
+ }else{
+ const char *zArg = Tcl_GetString(objv[1]);
+ if( zArg==0 ) return TCL_ERROR;
+ zOut = Tcl_GetString(objv[2]);
+ if( strcmp(zArg, "stderr")==0 ){
+ pOut = stderr;
+ }else if( strcmp(zArg, "-nonewline")==0 ){
+ addNewLine = 0;
+ }else{
+ Tcl_AppendResult(interp, "bad argument: ", zArg, 0);
+ return TCL_ERROR;
+ }
+ }
+ fPutsUtf8(zOut, pOut);
+ if( addNewLine ) fPutsUtf8("\n", pOut);
+ return TCL_OK;
+}
+#endif /* defined(_WIN32) */
+
+const char *sqlite3_analyzer_init_proc(Tcl_Interp *interp){
+#if defined(_WIN32)
+ Tcl_CreateObjCommand(interp, "puts", subst_puts, 0, 0);
+#else
+ (void)interp;
+#endif
+ return
+BEGIN_STRING
+INCLUDE $ROOT/tool/spaceanal.tcl
+END_STRING
+;
+}
diff --git a/tool/sqltclsh.c.in b/tool/sqltclsh.c.in
new file mode 100644
index 0000000..da354ee
--- /dev/null
+++ b/tool/sqltclsh.c.in
@@ -0,0 +1,51 @@
+/*
+** This is the source code to a "tclsh" that has SQLite built-in.
+**
+** The startup script is located as follows:
+**
+** (1) Open the executable as an appended SQLite database and try to
+** read the startup script out of that database.
+**
+** (2) If the first argument is a readable file, try to open that file
+** as an SQLite database and read the startup script out of that
+** database.
+**
+** (3) If the first argument is a readable file with a ".tcl" extension,
+** then try to run that script directly.
+**
+** If none of the above steps work, then the program runs as an interactive
+** tclsh.
+*/
+#define TCLSH_INIT_PROC sqlite3_tclapp_init_proc
+#define SQLITE_ENABLE_DBSTAT_VTAB 1
+#undef SQLITE_THREADSAFE
+#define SQLITE_THREADSAFE 0
+#undef SQLITE_ENABLE_COLUMN_METADATA
+#define SQLITE_OMIT_DECLTYPE 1
+#define SQLITE_OMIT_DEPRECATED 1
+#define SQLITE_OMIT_PROGRESS_CALLBACK 1
+#define SQLITE_OMIT_SHARED_CACHE 1
+#define SQLITE_DEFAULT_MEMSTATUS 0
+#define SQLITE_MAX_EXPR_DEPTH 0
+INCLUDE sqlite3.c
+INCLUDE $ROOT/ext/misc/appendvfs.c
+#ifdef SQLITE_HAVE_ZLIB
+INCLUDE $ROOT/ext/misc/zipfile.c
+INCLUDE $ROOT/ext/misc/sqlar.c
+#endif
+INCLUDE $ROOT/src/tclsqlite.c
+
+const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){
+ (void)interp;
+ sqlite3_appendvfs_init(0,0,0);
+#ifdef SQLITE_HAVE_ZLIB
+ sqlite3_auto_extension((void(*)(void))sqlite3_sqlar_init);
+ sqlite3_auto_extension((void(*)(void))sqlite3_zipfile_init);
+#endif
+
+ return
+BEGIN_STRING
+INCLUDE $ROOT/tool/sqltclsh.tcl
+END_STRING
+;
+}
diff --git a/tool/sqltclsh.tcl b/tool/sqltclsh.tcl
new file mode 100644
index 0000000..6a4b1fe
--- /dev/null
+++ b/tool/sqltclsh.tcl
@@ -0,0 +1,71 @@
+# Try to open the executable as a database and read the "scripts.data"
+# field where "scripts.name" is 'main.tcl'
+#
+catch {
+ if {![file exists $argv0] && [file exists $argv0.exe]} {
+ append argv0 .exe
+ }
+ sqlite3 db $argv0 -vfs apndvfs -create 0
+ set mainscript [db one {
+ SELECT sqlar_uncompress(data,sz) FROM sqlar WHERE name='main.tcl'
+ }]
+}
+if {[info exists mainscript]} {
+ eval $mainscript
+ return
+} else {
+ catch {db close}
+}
+
+# Try to open file named in the first argument as a database and
+# read the "scripts.data" field where "scripts.name" is 'main.tcl'
+#
+if {[llength $argv]>0 && [file readable [lindex $argv 0]]} {
+ catch {
+ sqlite3 db [lindex $argv 0] -vfs apndvfs -create 0
+ set mainscript [db one {SELECT data FROM scripts WHERE name='main.tcl'}]
+ set argv0 [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+ }
+ if {[info exists mainscript]} {
+ eval $mainscript
+ return
+ } else {
+ catch {db close}
+ }
+ if {[string match *.tcl [lindex $argv 0]]} {
+ set fd [open [lindex $argv 0] rb]
+ set mainscript [read $fd]
+ close $fd
+ unset fd
+ set argv0 [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+ }
+ if {[info exists mainscript]} {
+ eval $mainscript
+ return
+ }
+}
+
+# If all else fails, do an interactive loop
+#
+set line {}
+while {![eof stdin]} {
+ if {$line!=""} {
+ puts -nonewline "> "
+ } else {
+ puts -nonewline "% "
+ }
+ flush stdout
+ append line [gets stdin]
+ if {[info complete $line]} {
+ if {[catch {uplevel #0 $line} result]} {
+ puts stderr "Error: $result"
+ } elseif {$result!=""} {
+ puts $result
+ }
+ set line {}
+ } else {
+ append line \\n"
+ }
+}
diff --git a/tool/src-verify.c b/tool/src-verify.c
new file mode 100644
index 0000000..7629046
--- /dev/null
+++ b/tool/src-verify.c
@@ -0,0 +1,956 @@
+/*
+** This utility program reads the "manifest" and "manifest.uuid" files
+** in a Fossil-generated source tree (where the repository has the
+** "manifest" setting turned on - this is true for SQLite and Fossil itself)
+** and verifies that the source code files are complete and unaltered by
+** checking the SHA1 and SHA3 hashes of the source files contained in the
+** "manifest" file.
+**
+** On success it prints: "OK $HASH" where $HASH is the SHA3-256 hash of
+** the check-in for the source tree. If it finds any discrepencies, it
+** prints "Derived from $HASH with changes to:" followed by a list of files
+** which have been altered.
+**
+** USAGE:
+**
+** src-verify [-x] [-v] $(ROOT)
+**
+** Where ROOT is the root of the source tree - the directory that contains
+** the "manifest" and "manifest.uuid" files. Add the "-v" option for
+** some debugging output. With the -x option, the output is in a format
+** that is intended to be read by a script rather by a human. The -x output
+** format always has the SHA3 hash of the source check-in on the first line
+** and lists files that have changed on subsequent lines.
+**
+** Additional debugging options:
+**
+** src-verify --sha1 FILE ...
+** src-verify --sha3 FILE ...
+**
+** Compute the SHA1 or SHA3-256 hashes for all of the FILEs named
+**
+** COMPILING:
+**
+** This utility is self-contained. It uses only the standard library.
+** There are no other dependencies. Just compile it and run it.
+**
+** LIMITATIONS:
+**
+** * This utility assumes that the check-in hash uses SHA3-256.
+** It is ok for individual file hashes to be SHA1, but the
+** check-in itself must use a SHA3-256 hash.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#if !defined(_WIN32)
+# include <unistd.h>
+#else
+# include <io.h>
+# ifndef R_OK
+# define R_OK 04
+# endif
+# ifndef access
+# define access(f,m) _access((f),(m))
+# endif
+#endif
+typedef unsigned long long int u64;
+
+/*
+** The SHA1 implementation below is adapted from:
+**
+** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $
+** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $
+**
+** SHA-1 in C
+** By Steve Reid <steve@edmweb.com>
+** 100% Public Domain
+*/
+typedef struct SHA1Context SHA1Context;
+struct SHA1Context {
+ unsigned int state[5];
+ unsigned int count[2];
+ unsigned char buffer[64];
+};
+
+/*
+ * blk0() and blk() perform the initial expand.
+ * I got the idea of expanding during the round function from SSLeay
+ *
+ * blk0le() for little-endian and blk0be() for big-endian.
+ */
+#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r))
+#define rol(x,k) SHA_ROT(x,k,32-(k))
+#define ror(x,k) SHA_ROT(x,32-(k),k)
+#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \
+ |(rol(block[i],8)&0x00FF00FF))
+#define blk0be(i) block[i]
+#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \
+ ^block[(i+2)&15]^block[i&15],1))
+
+/*
+ * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
+ *
+ * Rl0() for little-endian and Rb0() for big-endian. Endianness is
+ * determined at run-time.
+ */
+#define Rl0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define Rb0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R1(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2);
+#define R2(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2);
+#define R3(v,w,x,y,z,i) \
+ z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2);
+#define R4(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2);
+
+/*
+ * Hash a single 512-bit block. This is the core of the algorithm.
+ */
+#define a qq[0]
+#define b qq[1]
+#define c qq[2]
+#define d qq[3]
+#define e qq[4]
+
+void SHA1Transform(unsigned int state[5], const unsigned char buffer[64])
+{
+ unsigned int qq[5]; /* a, b, c, d, e; */
+ static int one = 1;
+ unsigned int block[16];
+ memcpy(block, buffer, 64);
+ memcpy(qq,state,5*sizeof(unsigned int));
+
+ /* Copy context->state[] to working vars */
+ /*
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ */
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ if( 1 == *(unsigned char*)&one ){
+ Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3);
+ Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7);
+ Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11);
+ Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15);
+ }else{
+ Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3);
+ Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7);
+ Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11);
+ Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15);
+ }
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+}
+
+/*
+ * SHA1Init - Initialize new context
+ */
+static void SHA1Init(SHA1Context *context){
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/*
+ * Run your data through this.
+ */
+static void SHA1Update(
+ SHA1Context *context,
+ const unsigned char *data,
+ unsigned int len
+){
+ unsigned int i, j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1] += (len>>29)+1;
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ (void)memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64)
+ SHA1Transform(context->state, &data[i]);
+ j = 0;
+ } else {
+ i = 0;
+ }
+ (void)memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/*
+ * Add padding and return the message digest.
+ */
+static void SHA1Final(unsigned char *digest, SHA1Context *context){
+ unsigned int i;
+ unsigned char finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (const unsigned char *)"\200", 1);
+ while ((context->count[0] & 504) != 448)
+ SHA1Update(context, (const unsigned char *)"\0", 1);
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+
+ if (digest) {
+ for (i = 0; i < 20; i++)
+ digest[i] = (unsigned char)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+}
+
+
+/*
+** Macros to determine whether the machine is big or little endian,
+** and whether or not that determination is run-time or compile-time.
+**
+** For best performance, an attempt is made to guess at the byte-order
+** using C-preprocessor macros. If that is unsuccessful, or if
+** -DSHA3_BYTEORDER=0 is set, then byte-order is determined
+** at run-time.
+*/
+#ifndef SHA3_BYTEORDER
+# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__arm__)
+# define SHA3_BYTEORDER 1234
+# elif defined(sparc) || defined(__ppc__)
+# define SHA3_BYTEORDER 4321
+# else
+# define SHA3_BYTEORDER 0
+# endif
+#endif
+
+
+/*
+** State structure for a SHA3 hash in progress
+*/
+typedef struct SHA3Context SHA3Context;
+struct SHA3Context {
+ union {
+ u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */
+ unsigned char x[1600]; /* ... or 1600 bytes */
+ } u;
+ unsigned nRate; /* Bytes of input accepted per Keccak iteration */
+ unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */
+ unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */
+};
+
+/*
+** A single step of the Keccak mixing function for a 1600-bit state
+*/
+static void KeccakF1600Step(SHA3Context *p){
+ int i;
+ u64 B0, B1, B2, B3, B4;
+ u64 C0, C1, C2, C3, C4;
+ u64 D0, D1, D2, D3, D4;
+ static const u64 RC[] = {
+ 0x0000000000000001ULL, 0x0000000000008082ULL,
+ 0x800000000000808aULL, 0x8000000080008000ULL,
+ 0x000000000000808bULL, 0x0000000080000001ULL,
+ 0x8000000080008081ULL, 0x8000000000008009ULL,
+ 0x000000000000008aULL, 0x0000000000000088ULL,
+ 0x0000000080008009ULL, 0x000000008000000aULL,
+ 0x000000008000808bULL, 0x800000000000008bULL,
+ 0x8000000000008089ULL, 0x8000000000008003ULL,
+ 0x8000000000008002ULL, 0x8000000000000080ULL,
+ 0x000000000000800aULL, 0x800000008000000aULL,
+ 0x8000000080008081ULL, 0x8000000000008080ULL,
+ 0x0000000080000001ULL, 0x8000000080008008ULL
+ };
+# define A00 (p->u.s[0])
+# define A01 (p->u.s[1])
+# define A02 (p->u.s[2])
+# define A03 (p->u.s[3])
+# define A04 (p->u.s[4])
+# define A10 (p->u.s[5])
+# define A11 (p->u.s[6])
+# define A12 (p->u.s[7])
+# define A13 (p->u.s[8])
+# define A14 (p->u.s[9])
+# define A20 (p->u.s[10])
+# define A21 (p->u.s[11])
+# define A22 (p->u.s[12])
+# define A23 (p->u.s[13])
+# define A24 (p->u.s[14])
+# define A30 (p->u.s[15])
+# define A31 (p->u.s[16])
+# define A32 (p->u.s[17])
+# define A33 (p->u.s[18])
+# define A34 (p->u.s[19])
+# define A40 (p->u.s[20])
+# define A41 (p->u.s[21])
+# define A42 (p->u.s[22])
+# define A43 (p->u.s[23])
+# define A44 (p->u.s[24])
+# define ROL64(a,x) ((a<<x)|(a>>(64-x)))
+
+ for(i=0; i<24; i+=4){
+ C0 = A00^A10^A20^A30^A40;
+ C1 = A01^A11^A21^A31^A41;
+ C2 = A02^A12^A22^A32^A42;
+ C3 = A03^A13^A23^A33^A43;
+ C4 = A04^A14^A24^A34^A44;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A11^D1), 44);
+ B2 = ROL64((A22^D2), 43);
+ B3 = ROL64((A33^D3), 21);
+ B4 = ROL64((A44^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i];
+ A11 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A20^D0), 3);
+ B3 = ROL64((A31^D1), 45);
+ B4 = ROL64((A42^D2), 61);
+ B0 = ROL64((A03^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A20 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A40^D0), 18);
+ B0 = ROL64((A01^D1), 1);
+ B1 = ROL64((A12^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A34^D4), 8);
+ A40 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A10^D0), 36);
+ B2 = ROL64((A21^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A43^D3), 56);
+ B0 = ROL64((A04^D4), 27);
+ A10 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A30^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A02^D2), 62);
+ B1 = ROL64((A13^D3), 55);
+ B2 = ROL64((A24^D4), 39);
+ A30 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A20^A40^A10^A30;
+ C1 = A11^A31^A01^A21^A41;
+ C2 = A22^A42^A12^A32^A02;
+ C3 = A33^A03^A23^A43^A13;
+ C4 = A44^A14^A34^A04^A24;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A31^D1), 44);
+ B2 = ROL64((A12^D2), 43);
+ B3 = ROL64((A43^D3), 21);
+ B4 = ROL64((A24^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+1];
+ A31 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A40^D0), 3);
+ B3 = ROL64((A21^D1), 45);
+ B4 = ROL64((A02^D2), 61);
+ B0 = ROL64((A33^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A40 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A30^D0), 18);
+ B0 = ROL64((A11^D1), 1);
+ B1 = ROL64((A42^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A04^D4), 8);
+ A30 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A20^D0), 36);
+ B2 = ROL64((A01^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A13^D3), 56);
+ B0 = ROL64((A44^D4), 27);
+ A20 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A10^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A22^D2), 62);
+ B1 = ROL64((A03^D3), 55);
+ B2 = ROL64((A34^D4), 39);
+ A10 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A40^A30^A20^A10;
+ C1 = A31^A21^A11^A01^A41;
+ C2 = A12^A02^A42^A32^A22;
+ C3 = A43^A33^A23^A13^A03;
+ C4 = A24^A14^A04^A44^A34;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A21^D1), 44);
+ B2 = ROL64((A42^D2), 43);
+ B3 = ROL64((A13^D3), 21);
+ B4 = ROL64((A34^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+2];
+ A21 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A30^D0), 3);
+ B3 = ROL64((A01^D1), 45);
+ B4 = ROL64((A22^D2), 61);
+ B0 = ROL64((A43^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A30 = B0 ^((~B1)& B2 );
+ A01 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A10^D0), 18);
+ B0 = ROL64((A31^D1), 1);
+ B1 = ROL64((A02^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A44^D4), 8);
+ A10 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A40^D0), 36);
+ B2 = ROL64((A11^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A03^D3), 56);
+ B0 = ROL64((A24^D4), 27);
+ A40 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A20^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A12^D2), 62);
+ B1 = ROL64((A33^D3), 55);
+ B2 = ROL64((A04^D4), 39);
+ A20 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ C0 = A00^A30^A10^A40^A20;
+ C1 = A21^A01^A31^A11^A41;
+ C2 = A42^A22^A02^A32^A12;
+ C3 = A13^A43^A23^A03^A33;
+ C4 = A34^A14^A44^A24^A04;
+ D0 = C4^ROL64(C1, 1);
+ D1 = C0^ROL64(C2, 1);
+ D2 = C1^ROL64(C3, 1);
+ D3 = C2^ROL64(C4, 1);
+ D4 = C3^ROL64(C0, 1);
+
+ B0 = (A00^D0);
+ B1 = ROL64((A01^D1), 44);
+ B2 = ROL64((A02^D2), 43);
+ B3 = ROL64((A03^D3), 21);
+ B4 = ROL64((A04^D4), 14);
+ A00 = B0 ^((~B1)& B2 );
+ A00 ^= RC[i+3];
+ A01 = B1 ^((~B2)& B3 );
+ A02 = B2 ^((~B3)& B4 );
+ A03 = B3 ^((~B4)& B0 );
+ A04 = B4 ^((~B0)& B1 );
+
+ B2 = ROL64((A10^D0), 3);
+ B3 = ROL64((A11^D1), 45);
+ B4 = ROL64((A12^D2), 61);
+ B0 = ROL64((A13^D3), 28);
+ B1 = ROL64((A14^D4), 20);
+ A10 = B0 ^((~B1)& B2 );
+ A11 = B1 ^((~B2)& B3 );
+ A12 = B2 ^((~B3)& B4 );
+ A13 = B3 ^((~B4)& B0 );
+ A14 = B4 ^((~B0)& B1 );
+
+ B4 = ROL64((A20^D0), 18);
+ B0 = ROL64((A21^D1), 1);
+ B1 = ROL64((A22^D2), 6);
+ B2 = ROL64((A23^D3), 25);
+ B3 = ROL64((A24^D4), 8);
+ A20 = B0 ^((~B1)& B2 );
+ A21 = B1 ^((~B2)& B3 );
+ A22 = B2 ^((~B3)& B4 );
+ A23 = B3 ^((~B4)& B0 );
+ A24 = B4 ^((~B0)& B1 );
+
+ B1 = ROL64((A30^D0), 36);
+ B2 = ROL64((A31^D1), 10);
+ B3 = ROL64((A32^D2), 15);
+ B4 = ROL64((A33^D3), 56);
+ B0 = ROL64((A34^D4), 27);
+ A30 = B0 ^((~B1)& B2 );
+ A31 = B1 ^((~B2)& B3 );
+ A32 = B2 ^((~B3)& B4 );
+ A33 = B3 ^((~B4)& B0 );
+ A34 = B4 ^((~B0)& B1 );
+
+ B3 = ROL64((A40^D0), 41);
+ B4 = ROL64((A41^D1), 2);
+ B0 = ROL64((A42^D2), 62);
+ B1 = ROL64((A43^D3), 55);
+ B2 = ROL64((A44^D4), 39);
+ A40 = B0 ^((~B1)& B2 );
+ A41 = B1 ^((~B2)& B3 );
+ A42 = B2 ^((~B3)& B4 );
+ A43 = B3 ^((~B4)& B0 );
+ A44 = B4 ^((~B0)& B1 );
+ }
+}
+
+/*
+** Initialize a new hash. iSize determines the size of the hash
+** in bits and should be one of 224, 256, 384, or 512. Or iSize
+** can be zero to use the default hash size of 256 bits.
+*/
+static void SHA3Init(SHA3Context *p, int iSize){
+ memset(p, 0, sizeof(*p));
+ if( iSize>=128 && iSize<=512 ){
+ p->nRate = (1600 - ((iSize + 31)&~31)*2)/8;
+ }else{
+ p->nRate = (1600 - 2*256)/8;
+ }
+#if SHA3_BYTEORDER==1234
+ /* Known to be little-endian at compile-time. No-op */
+#elif SHA3_BYTEORDER==4321
+ p->ixMask = 7; /* Big-endian */
+#else
+ {
+ static unsigned int one = 1;
+ if( 1==*(unsigned char*)&one ){
+ /* Little endian. No byte swapping. */
+ p->ixMask = 0;
+ }else{
+ /* Big endian. Byte swap. */
+ p->ixMask = 7;
+ }
+ }
+#endif
+}
+
+/*
+** Make consecutive calls to the SHA3Update function to add new content
+** to the hash
+*/
+static void SHA3Update(
+ SHA3Context *p,
+ const unsigned char *aData,
+ unsigned int nData
+){
+ unsigned int i = 0;
+#if SHA3_BYTEORDER==1234
+ if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){
+ for(; i+7<nData; i+=8){
+ p->u.s[p->nLoaded/8] ^= *(u64*)&aData[i];
+ p->nLoaded += 8;
+ if( p->nLoaded>=p->nRate ){
+ KeccakF1600Step(p);
+ p->nLoaded = 0;
+ }
+ }
+ }
+#endif
+ for(; i<nData; i++){
+#if SHA3_BYTEORDER==1234
+ p->u.x[p->nLoaded] ^= aData[i];
+#elif SHA3_BYTEORDER==4321
+ p->u.x[p->nLoaded^0x07] ^= aData[i];
+#else
+ p->u.x[p->nLoaded^p->ixMask] ^= aData[i];
+#endif
+ p->nLoaded++;
+ if( p->nLoaded==p->nRate ){
+ KeccakF1600Step(p);
+ p->nLoaded = 0;
+ }
+ }
+}
+
+/*
+** After all content has been added, invoke SHA3Final() to compute
+** the final hash. The function returns a pointer to the binary
+** hash value.
+*/
+static unsigned char *SHA3Final(SHA3Context *p){
+ unsigned int i;
+ if( p->nLoaded==p->nRate-1 ){
+ const unsigned char c1 = 0x86;
+ SHA3Update(p, &c1, 1);
+ }else{
+ const unsigned char c2 = 0x06;
+ const unsigned char c3 = 0x80;
+ SHA3Update(p, &c2, 1);
+ p->nLoaded = p->nRate - 1;
+ SHA3Update(p, &c3, 1);
+ }
+ for(i=0; i<p->nRate; i++){
+ p->u.x[i+p->nRate] = p->u.x[i^p->ixMask];
+ }
+ return &p->u.x[p->nRate];
+}
+
+/*
+** Convert a digest into base-16.
+*/
+static void DigestToBase16(unsigned char *digest, char *zBuf, int nByte){
+ static const char zEncode[] = "0123456789abcdef";
+ int ix;
+
+ for(ix=0; ix<nByte; ix++){
+ *zBuf++ = zEncode[(*digest>>4)&0xf];
+ *zBuf++ = zEncode[*digest++ & 0xf];
+ }
+ *zBuf = '\0';
+}
+
+/*
+** Compute the SHA3-256 checksum of a file on disk. Store the resulting
+** checksum in the zCksum.
+**
+** Return the number of errors.
+*/
+void sha3sum_file(const char *zFilename, char *zCksum){
+ FILE *in;
+ SHA3Context ctx;
+ char zBuf[10240];
+
+ in = fopen(zFilename,"rb");
+ if( in==0 ){
+ zCksum[0] = 0;
+ return;
+ }
+ SHA3Init(&ctx, 256);
+ for(;;){
+ size_t n;
+ n = fread(zBuf, 1, sizeof(zBuf), in);
+ if( n<=0 ) break;
+ SHA3Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
+ }
+ fclose(in);
+ DigestToBase16(SHA3Final(&ctx), zCksum, 32);
+}
+
+/*
+** Compute the SHA1 checksum of a file on disk. Store the resulting
+** checksum in the zCksum.
+**
+** Return the number of errors.
+*/
+void sha1sum_file(const char *zFilename, char *zCksum){
+ FILE *in;
+ SHA1Context ctx;
+ unsigned char zResult[20];
+ char zBuf[10240];
+
+ in = fopen(zFilename,"rb");
+ if( in==0 ){
+ zCksum[0] = 0;
+ return;
+ }
+ SHA1Init(&ctx);
+ for(;;){
+ size_t n;
+ n = fread(zBuf, 1, sizeof(zBuf), in);
+ if( n<=0 ) break;
+ SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
+ }
+ fclose(in);
+ SHA1Final(zResult, &ctx);
+ DigestToBase16(zResult, zCksum, 20);
+}
+
+/*
+** Decode a fossilized string in-place.
+*/
+void defossilize(char *z){
+ int i, j, cc;
+ char *zSlash = strchr(z, '\\');
+ if( zSlash==0 ) return;
+ i = zSlash - z;
+ for(j=i; (cc=z[i])!=0; i++){
+ if( cc=='\\' && z[i+1] ){
+ i++;
+ switch( z[i] ){
+ case 'n': cc = '\n'; break;
+ case 's': cc = ' '; break;
+ case 't': cc = '\t'; break;
+ case 'r': cc = '\r'; break;
+ case 'v': cc = '\v'; break;
+ case 'f': cc = '\f'; break;
+ case '0': cc = 0; break;
+ case '\\': cc = '\\'; break;
+ default: cc = z[i]; break;
+ }
+ }
+ z[j++] = cc;
+ }
+ if( z[j] ) z[j] = 0;
+}
+
+/*
+** Report that a single file is incorrect.
+*/
+static void errorMsg(int *pnErr, const char *zVers, const char *zFile){
+ if( *pnErr==0 ){
+ printf("Derived from %.25s with changes to:\n", zVers);
+ }
+ printf(" %s\n", zFile);
+ (*pnErr)++;
+}
+static void errorMsgNH(int *pnErr, const char *zVers, const char *zFile){
+ if( *pnErr==0 ){
+ printf("%s\n", zVers);
+ }
+ printf("%s\n", zFile);
+ (*pnErr)++;
+}
+
+int main(int argc, char **argv){
+ int i, j;
+ int nDir;
+ FILE *in;
+ int bDebug = 0;
+ int bNonHuman = 0;
+ int bSeenManifestErr = 0;
+ int nErr = 0;
+ SHA3Context ctx3;
+ const char *zDir = 0;
+ void (*xErr)(int*,const char*,const char*);
+ char zHash[100];
+ char zCk[100];
+ char zVers[100];
+ char zLine[40000];
+ char zFile[40000];
+ xErr = errorMsg;
+ for(i=1; i<argc; i++){
+ const char *z = argv[i];
+ if( z[0]!='-' ){
+ if( zDir!=0 ){
+ fprintf(stderr, "bad argument: %s\n", z);
+ return 1;
+ }
+ zDir = z;
+ continue;
+ }
+ if( z[1]=='-' && z[2]!=0 ) z++;
+ if( strcmp(argv[1],"-sha1")==0 ){
+ /* For testing purposes, if the first argument is --sha1, then simply
+ ** compute and print the SHA1 checksum of all subsequent arguments. */
+ for(i++; i<argc; i++){
+ sha1sum_file(argv[i], zHash);
+ printf("%s %s\n", zHash, argv[i]);
+ }
+ return 0;
+ }
+ if( strcmp(argv[1], "-sha3")==0 ){
+ /* For testing purposes, if the first argument is --sha3, then simply
+ ** compute and print the SHA3-256 checksum of all subsequent arguments. */
+ for(i++; i<argc; i++){
+ sha3sum_file(argv[i], zHash);
+ printf("%s %s\n", zHash, argv[i]);
+ }
+ return 0;
+ }
+ if( strcmp(z,"-v")==0 ){
+ bDebug = 1;
+ continue;
+ }
+ if( strcmp(z,"-x")==0 ){
+ bNonHuman = 1;
+ xErr = errorMsgNH;
+ continue;
+ }
+ fprintf(stderr, "Usage: %s DIRECTORY\n"
+ " or: %s --sha1 FILE ...\n"
+ " or: %s --sha3 FILE ...\n",
+ argv[0], argv[0], argv[0]);
+ return 1;
+ }
+ if( strlen(zDir)>1000 ){
+ fprintf(stderr, "Directory argument too big: [%s]\n", zDir);
+ return 1;
+ }
+ nDir = (int)strlen(zDir);
+ if( nDir<0 ){
+ fprintf(stderr, "Directory argument too short.\n");
+ return 1;
+ }
+ memcpy(zFile, zDir, nDir);
+ if( zFile[nDir-1]!='/' ){
+ zFile[nDir++] = '/';
+ }
+ memcpy(&zFile[nDir], "manifest", 9);
+ if( bDebug ){
+ printf("manifest file: [%s]\n", zFile);
+ }
+ in = fopen(zFile, "rb");
+ if( in==0 ){
+ fprintf(stderr, "missing manifest: \"%s\"\n", zFile);
+ return 1;
+ }
+ SHA3Init(&ctx3, 256);
+ while( fgets(zLine, sizeof(zLine), in) ){
+ if( zLine[0]=='#' ) break;
+ SHA3Update(&ctx3, (unsigned char*)zLine, (int)strlen(zLine));
+ }
+ DigestToBase16(SHA3Final(&ctx3), zVers, 32);
+
+ rewind(in);
+ while( fgets(zLine, sizeof(zLine), in) ){
+ if( zLine[0]!='F' ) continue;
+ if( zLine[1]!=' ' ) continue;
+ for(i=2, j=nDir; zLine[i]!=0 && zLine[i]!=' '; i++, j++){
+ if( j<sizeof(zFile) ) zFile[j] = zLine[i];
+ }
+ if( j<sizeof(zFile) ) zFile[j] = 0;
+ zFile[sizeof(zFile)-1] = 0;
+ defossilize(&zFile[nDir]);
+ if( zLine[i]!=' ' ){
+ bSeenManifestErr = 1;
+ continue;
+ }
+ for(i++, j=0; zLine[i]>='0' && zLine[i]<='f'; i++, j++){
+ if( j<sizeof(zHash) ) zHash[j] = zLine[i];
+ }
+ if( j<sizeof(zHash) ) zHash[j] = 0;
+ zHash[sizeof(zHash)-1] = 0;
+ if( bDebug ){
+ printf("%s %s\n", zFile, zHash);
+ }
+ if( access(zFile, R_OK)!=0 ){
+ xErr(&nErr, zVers, &zFile[nDir]);
+ continue;
+ }
+ if( strlen(zHash)==40 ){
+ sha1sum_file(zFile, zCk);
+ if( strcmp(zHash, zCk)!=0 ){
+ xErr(&nErr, zVers, &zFile[nDir]);
+ }
+ }else if( strlen(zHash)==64 ){
+ sha3sum_file(zFile, zCk);
+ if( strcmp(zHash, zCk)!=0 ){
+ xErr(&nErr, zVers, &zFile[nDir]);
+ }
+ }else{
+ bSeenManifestErr = 1;
+ xErr(&nErr, zVers, &zFile[nDir]);
+ }
+ }
+ fclose(in);
+ in = 0;
+ if( bSeenManifestErr ) xErr(&nErr, zVers, "manifest");
+ memcpy(&zFile[nDir], "manifest.uuid", 14);
+ if( access(zFile, R_OK)!=0
+ || (in = fopen(zFile,"rb"))==0
+ || fgets(zLine, sizeof(zLine), in)==0
+ || strlen(zLine)!=65
+ || zLine[64]!='\n'
+ || memcmp(zLine, zVers, 64)!=0
+ ){
+ xErr(&nErr, zVers, &zFile[nDir]);
+ }
+ if( in ) fclose(in);
+
+ if( bNonHuman ){
+ if( nErr ) return 0;
+ printf("%s\n", zVers);
+ }else{
+ if( nErr ) return nErr;
+ printf("OK %.25s\n", zVers);
+ }
+ return 0;
+}
diff --git a/tool/srcck1.c b/tool/srcck1.c
new file mode 100644
index 0000000..20084ac
--- /dev/null
+++ b/tool/srcck1.c
@@ -0,0 +1,158 @@
+/*
+** The program does some simple static analysis of the sqlite3.c source
+** file looking for mistakes.
+**
+** Usage:
+**
+** ./srcck1 sqlite3.c
+**
+** This program looks for instances of assert(), ALWAYS(), NEVER() or
+** testcase() that contain side-effects and reports errors if any such
+** instances are found.
+**
+** The aim of this utility is to prevent recurrences of errors such
+** as the one fixed at:
+**
+** https://www.sqlite.org/src/info/a2952231ac7abe16
+**
+** Note that another similar error was found by this utility when it was
+** first written. That other error was fixed by the same check-in that
+** committed the first version of this utility program.
+*/
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Read the complete text of a file into memory. Return a pointer to
+** the result. Panic if unable to read the file or allocate memory.
+*/
+static char *readFile(const char *zFilename){
+ FILE *in;
+ char *z;
+ long n;
+ size_t got;
+
+ in = fopen(zFilename, "rb");
+ if( in==0 ){
+ fprintf(stderr, "unable to open '%s' for reading\n", zFilename);
+ exit(1);
+ }
+ fseek(in, 0, SEEK_END);
+ n = ftell(in);
+ rewind(in);
+ z = malloc( n+1 );
+ if( z==0 ){
+ fprintf(stderr, "cannot allocate %d bytes to store '%s'\n",
+ (int)(n+1), zFilename);
+ exit(1);
+ }
+ got = fread(z, 1, n, in);
+ fclose(in);
+ if( got!=(size_t)n ){
+ fprintf(stderr, "only read %d of %d bytes from '%s'\n",
+ (int)got, (int)n, zFilename);
+ exit(1);
+ }
+ z[n] = 0;
+ return z;
+}
+
+/* Check the C code in the argument to see if it might have
+** side effects. The only accurate way to know this is to do a full
+** parse of the C code, which this routine does not do. This routine
+** uses a simple heuristic of looking for:
+**
+** * '=' not immediately after '>', '<', '!', or '='.
+** * '++'
+** * '--'
+**
+** If the code contains the phrase "side-effects-ok" is inside a
+** comment, then always return false. This is used to disable checking
+** for assert()s with deliberate side-effects, such as used by
+** SQLITE_TESTCTRL_ASSERT - a facility that allows applications to
+** determine at runtime whether or not assert()s are enabled.
+** Obviously, that determination cannot be made unless the assert()
+** has some side-effect.
+**
+** Return true if a side effect is seen. Return false if not.
+*/
+static int hasSideEffect(const char *z, unsigned int n){
+ unsigned int i;
+ for(i=0; i<n; i++){
+ if( z[i]=='/' && strncmp(&z[i], "/*side-effects-ok*/", 19)==0 ) return 0;
+ if( z[i]=='=' && i>0 && z[i-1]!='=' && z[i-1]!='>'
+ && z[i-1]!='<' && z[i-1]!='!' && z[i+1]!='=' ) return 1;
+ if( z[i]=='+' && z[i+1]=='+' ) return 1;
+ if( z[i]=='-' && z[i+1]=='-' ) return 1;
+ }
+ return 0;
+}
+
+/* Return the number of bytes in string z[] prior to the first unmatched ')'
+** character.
+*/
+static unsigned int findCloseParen(const char *z){
+ unsigned int nOpen = 0;
+ unsigned i;
+ for(i=0; z[i]; i++){
+ if( z[i]=='(' ) nOpen++;
+ if( z[i]==')' ){
+ if( nOpen==0 ) break;
+ nOpen--;
+ }
+ }
+ return i;
+}
+
+/* Search for instances of assert(...), ALWAYS(...), NEVER(...), and/or
+** testcase(...) where the argument contains side effects.
+**
+** Print error messages whenever a side effect is found. Return the number
+** of problems seen.
+*/
+static unsigned int findAllSideEffects(const char *z){
+ unsigned int lineno = 1; /* Line number */
+ unsigned int i;
+ unsigned int nErr = 0;
+ char c, prevC = 0;
+ for(i=0; (c = z[i])!=0; prevC=c, i++){
+ if( c=='\n' ){ lineno++; continue; }
+ if( isalpha(c) && !isalpha(prevC) ){
+ if( strncmp(&z[i],"assert(",7)==0
+ || strncmp(&z[i],"ALWAYS(",7)==0
+ || strncmp(&z[i],"NEVER(",6)==0
+ || strncmp(&z[i],"testcase(",9)==0
+ ){
+ unsigned int n;
+ const char *z2 = &z[i+5];
+ while( z2[0]!='(' ){ z2++; }
+ z2++;
+ n = findCloseParen(z2);
+ if( hasSideEffect(z2, n) ){
+ nErr++;
+ fprintf(stderr, "side-effect line %u: %.*s\n", lineno,
+ (int)(&z2[n+1] - &z[i]), &z[i]);
+ }
+ }
+ }
+ }
+ return nErr;
+}
+
+int main(int argc, char **argv){
+ char *z;
+ unsigned int nErr = 0;
+ if( argc!=2 ){
+ fprintf(stderr, "Usage: %s FILENAME\n", argv[0]);
+ return 1;
+ }
+ z = readFile(argv[1]);
+ nErr = findAllSideEffects(z);
+ free(z);
+ if( nErr ){
+ fprintf(stderr, "Found %u undesirable side-effects\n", nErr);
+ return 1;
+ }
+ return 0;
+}
diff --git a/tool/srctree-check.tcl b/tool/srctree-check.tcl
new file mode 100644
index 0000000..51226cd
--- /dev/null
+++ b/tool/srctree-check.tcl
@@ -0,0 +1,103 @@
+#!/usr/bin/tclsh
+#
+# Run this script from the top of the source tree in order to confirm that
+# various aspects of the source tree are up-to-date. Items checked include:
+#
+# * Makefile.msc and autoconf/Makefile.msc agree
+# * src/ctime.tcl is consistent with tool/mkctimec.tcl
+# * VERSION agrees with autoconf/tea/configure.ac
+# * src/pragma.h agrees with tool/mkpragmatab.tcl
+#
+# Other tests might be added later.
+#
+# Error messages are printed and the process exists non-zero if problems
+# are found. If everything is ok, no output is generated and the process
+# exits with 0.
+#
+
+# Read an entire file.
+#
+proc readfile {filename} {
+ set fd [open $filename rb]
+ set txt [read $fd]
+ close $fd
+ return $txt
+}
+
+# Find the root of the tree.
+#
+set ROOT [file dir [file dir [file normalize $argv0]]]
+
+# Name of the TCL interpreter
+#
+set TCLSH [info nameofexe]
+
+# Number of errors seen.
+#
+set NERR 0
+
+######################### configure ###########################################
+
+set conf [readfile $ROOT/configure]
+set vers [readfile $ROOT/VERSION]
+if {[string first $vers $conf]<=0} {
+ puts "ERROR: ./configure does not agree with ./VERSION"
+ puts "...... Fix: run autoconf"
+ incr NERR
+}
+unset conf
+
+######################### autoconf/tea/configure.ac ###########################
+
+set confac [readfile $ROOT/autoconf/tea/configure.ac]
+set vers [readfile $ROOT/VERSION]
+set pattern {AC_INIT([sqlite],[}
+append pattern [string trim $vers]
+append pattern {])}
+if {[string first $pattern $confac]<=0} {
+ puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION"
+ puts "...... Fix: manually edit ./autoconf/tea/configure.ac and put the"
+ puts "...... correct version number in AC_INIT()"
+ incr NERR
+}
+unset confac
+
+######################### autoconf/Makefile.msc ###############################
+
+set f1 [readfile $ROOT/autoconf/Makefile.msc]
+exec $TCLSH $ROOT/tool/mkmsvcmin.tcl $ROOT/Makefile.msc tmp1.txt
+set f2 [readfile tmp1.txt]
+file delete tmp1.txt
+if {$f1 != $f2} {
+ puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc"
+ puts "...... Fix: tclsh tool/mkmsvcmin.tcl"
+ incr NERR
+}
+
+######################### src/pragma.h ########################################
+
+set f1 [readfile $ROOT/src/pragma.h]
+exec $TCLSH $ROOT/tool/mkpragmatab.tcl tmp2.txt
+set f2 [readfile tmp2.txt]
+file delete tmp2.txt
+if {$f1 != $f2} {
+ puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl"
+ puts "...... Fix: tclsh tool/mkpragmatab.tcl"
+ incr NERR
+}
+
+######################### src/ctime.c ########################################
+
+set f1 [readfile $ROOT/src/ctime.c]
+exec $TCLSH $ROOT/tool/mkctimec.tcl tmp3.txt
+set f2 [readfile tmp3.txt]
+file delete tmp3.txt
+if {$f1 != $f2} {
+ puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl"
+ puts "..... Fix: tclsh tool/mkctimec.tcl"
+ incr NERR
+}
+
+# If any errors are seen, exit 1 so that the build will fail.
+#
+if {$NERR>0} {exit 1}
diff --git a/tool/stack_usage.tcl b/tool/stack_usage.tcl
new file mode 100644
index 0000000..b3574f0
--- /dev/null
+++ b/tool/stack_usage.tcl
@@ -0,0 +1,98 @@
+#!/usr/bin/tclsh
+#
+# Parse the output of
+#
+# objdump -d sqlite3.o
+#
+# for x64 and generate a report showing:
+#
+# (1) Stack used by each function
+# (2) Recursion paths and their aggregate stack depth
+#
+set getStack 0
+while {![eof stdin]} {
+ set line [gets stdin]
+ if {[regexp {^[0-9a-f]+ <([^>]+)>:\s*$} $line all procname]} {
+ set curfunc $procname
+ set root($curfunc) 1
+ set calls($curfunc) {}
+ set calledby($curfunc) {}
+ set recursive($curfunc) {}
+ set stkdepth($curfunc) 0
+ set getStack 1
+ continue
+ }
+ if {[regexp {callq? +[0-9a-z]+ <([^>]+)>} $line all other]} {
+ set key [list $curfunc $other]
+ set callpair($key) 1
+ unset -nocomplain root($curfunc)
+ continue
+ }
+ if {[regexp {sub +\$(0x[0-9a-z]+),%[er]sp} $line all xdepth]} {
+ if {$getStack} {
+ scan $xdepth %x depth
+ set stkdepth($curfunc) $depth
+ set getStack 0
+ }
+ continue
+ }
+}
+
+puts "****************** Stack Usage By Function ********************"
+set sdlist {}
+foreach f [array names stkdepth] {
+ lappend sdlist [list $stkdepth($f) $f]
+}
+foreach sd [lsort -integer -decr -index 0 $sdlist] {
+ foreach {depth fname} $sd break
+ puts [format {%6d %s} $depth $fname]
+}
+
+puts "****************** Stack Usage By Recursion *******************"
+foreach key [array names callpair] {
+ foreach {from to} $key break
+ lappend calls($from) $to
+ # lappend calledby($to) $from
+}
+proc all_descendents {root} {
+ global calls recursive
+ set todo($root) $root
+ set go 1
+ while {$go} {
+ set go 0
+ foreach f [array names todo] {
+ set path $todo($f)
+ unset todo($f)
+ if {![info exists calls($f)]} continue
+ foreach x $calls($f) {
+ if {$x==$root} {
+ lappend recursive($root) [concat $path $root]
+ } elseif {![info exists d($x)]} {
+ set go 1
+ set todo($x) [concat $path $x]
+ set d($x) 1
+ }
+ }
+ }
+ }
+ return [array names d]
+}
+set pathlist {}
+foreach f [array names recursive] {
+ all_descendents $f
+ foreach m $recursive($f) {
+ set depth 0
+ foreach b [lrange $m 0 end-1] {
+ set depth [expr {$depth+$stkdepth($b)}]
+ }
+ lappend pathlist [list $depth $m]
+ }
+}
+foreach path [lsort -integer -decr -index 0 $pathlist] {
+ foreach {depth m} $path break
+ set first [lindex $m 0]
+ puts [format {%6d %s %d} $depth $first $stkdepth($first)]
+ foreach b [lrange $m 1 end] {
+ puts " $b $stkdepth($b)"
+ }
+}
diff --git a/tool/stripccomments.c b/tool/stripccomments.c
new file mode 100644
index 0000000..53933c0
--- /dev/null
+++ b/tool/stripccomments.c
@@ -0,0 +1,228 @@
+/**
+ Strips C- and C++-style comments from stdin, sending the results to
+ stdout. It assumes that its input is legal C-like code, and does
+ only little error handling.
+
+ It treats string literals as anything starting and ending with
+ matching double OR single quotes OR backticks (for use with
+ scripting languages which use those). It assumes that a quote
+ character within a string which uses the same quote type is escaped
+ by a backslash. It should not be used on any code which might
+ contain C/C++ comments inside heredocs, and similar constructs, as
+ it will strip those out.
+
+ Usage: $0 [--keep-first|-k] < input > output
+
+ The --keep-first (-k) flag tells it to retain the first comment in the
+ input stream (which is often a license or attribution block). It
+ may be given repeatedly, each one incrementing the number of
+ retained comments by one.
+
+ License: Public Domain
+ Author: Stephan Beal (stephan@wanderinghorse.net)
+*/
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#if 1
+#define MARKER(pfexp) \
+ do{ printf("MARKER: %s:%d:\t",__FILE__,__LINE__); \
+ printf pfexp; \
+ } while(0)
+#else
+#define MARKER(exp) if(0) printf
+#endif
+
+struct {
+ FILE * input;
+ FILE * output;
+ int rc;
+ int keepFirst;
+} App = {
+ 0/*input*/,
+ 0/*output*/,
+ 0/*rc*/,
+ 0/*keepFirst*/
+};
+
+void do_it_all(void){
+ enum states {
+ S_NONE = 0 /* not in comment */,
+ S_SLASH1 = 1 /* slash - possibly comment prefix */,
+ S_CPP = 2 /* in C++ comment */,
+ S_C = 3 /* in C comment */
+ };
+ int ch, prev = EOF;
+ FILE * out = App.output;
+ int const slash = '/';
+ int const star = '*';
+ int line = 1;
+ int col = 0;
+ enum states state = S_NONE /* current state */;
+ int elide = 0 /* true if currently eliding output */;
+ int state3Col = -99
+ /* huge kludge for odd corner case: */
+ /*/ <--- here. state3Col marks the source column in which a C-style
+ comment starts, so that it can tell if star-slash inside a
+ C-style comment is the end of the comment or is the weird corner
+ case marked at the start of _this_ comment block. */;
+ for( ; EOF != (ch = fgetc(App.input)); prev = ch,
+ ++col){
+ switch(state){
+ case S_NONE:
+ if('\''==ch || '"'==ch || '`'==ch){
+ /* Read string literal...
+ needed to properly catch comments in strings. */
+ int const quote = ch,
+ startLine = line, startCol = col;
+ int ch2, escaped = 0, endOfString = 0;
+ fputc(ch, out);
+ for( ++col; !endOfString && EOF != (ch2 = fgetc(App.input));
+ ++col ){
+ switch(ch2){
+ case '\\': escaped = !escaped;
+ break;
+ case '`':
+ case '\'':
+ case '"':
+ if(!escaped && quote == ch2) endOfString = 1;
+ escaped = 0;
+ break;
+ default:
+ escaped = 0;
+ break;
+ }
+ if('\n'==ch2){
+ ++line;
+ col = 0;
+ }
+ fputc(ch2, out);
+ }
+ if(EOF == ch2){
+ fprintf(stderr, "Unexpected EOF while reading %s literal "
+ "on line %d column %d.\n",
+ ('\''==ch) ? "char" : "string",
+ startLine, startCol);
+ App.rc = 1;
+ return;
+ }
+ break;
+ }
+ else if(slash == ch){
+ /* MARKER(("state 0 ==> 1 @ %d:%d\n", line, col)); */
+ state = S_SLASH1;
+ break;
+ }
+ fputc(ch, out);
+ break;
+ case S_SLASH1: /* 1 slash */
+ /* MARKER(("SLASH1 @ %d:%d App.keepFirst=%d\n",
+ line, col, App.keepFirst)); */
+ switch(ch){
+ case '*':
+ /* Enter C comment */
+ if(App.keepFirst>0){
+ elide = 0;
+ --App.keepFirst;
+ }else{
+ elide = 1;
+ }
+ /*MARKER(("state 1 ==> 3 @ %d:%d\n", line, col));*/
+ state = S_C;
+ state3Col = col-1;
+ if(!elide){
+ fputc(prev, out);
+ fputc(ch, out);
+ }
+ break;
+ case '/':
+ /* Enter C++ comment */
+ if(App.keepFirst>0){
+ elide = 0;
+ --App.keepFirst;
+ }else{
+ elide = 1;
+ }
+ /*MARKER(("state 1 ==> 2 @ %d:%d\n", line, col));*/
+ state = S_CPP;
+ if(!elide){
+ fputc(prev, out);
+ fputc(ch, out);
+ }
+ break;
+ default:
+ /* It wasn't a comment after all. */
+ state = S_NONE;
+ if(!elide){
+ fputc(prev, out);
+ fputc(ch, out);
+ }
+ }
+ break;
+ case S_CPP: /* C++ comment */
+ if('\n' == ch){
+ /* MARKER(("state 2 ==> 0 @ %d:%d\n", line, col)); */
+ state = S_NONE;
+ elide = 0;
+ }
+ if(!elide){
+ fputc(ch, out);
+ }
+ break;
+ case S_C: /* C comment */
+ if(!elide){
+ fputc(ch, out);
+ }
+ if(slash == ch){
+ if(star == prev){
+ /* MARKER(("state 3 ==> 0 @ %d:%d\n", line, col)); */
+ /* Corner case which breaks this: */
+ /*/ <-- slash there */
+ /* That shows up twice in a piece of 3rd-party
+ code i use. */
+ /* And thus state3Col was introduced :/ */
+ if(col!=state3Col+2){
+ state = S_NONE;
+ elide = 0;
+ state3Col = -99;
+ }
+ }
+ }
+ break;
+ default:
+ assert(!"impossible!");
+ break;
+ }
+ if('\n' == ch){
+ ++line;
+ col = 0;
+ state3Col = -99;
+ }
+ }
+}
+
+static void usage(char const *zAppName){
+ fprintf(stderr, "Strips C- and C++-style comments from stdin and sends "
+ "the results to stdout.\n");
+ fprintf(stderr, "Usage: %s [--keep-first|-k] < input > output\n", zAppName);
+}
+
+int main( int argc, char const * const * argv ){
+ int i;
+ for(i = 1; i < argc; ++i){
+ char const * zArg = argv[i];
+ while( '-'==*zArg ) ++zArg;
+ if( 0==strcmp(zArg,"k")
+ || 0==strcmp(zArg,"keep-first") ){
+ ++App.keepFirst;
+ }else{
+ usage(argv[0]);
+ return 1;
+ }
+ }
+ App.input = stdin;
+ App.output = stdout;
+ do_it_all();
+ return App.rc ? 1 : 0;
+}
diff --git a/tool/symbols-mingw.sh b/tool/symbols-mingw.sh
new file mode 100644
index 0000000..bf93eec
--- /dev/null
+++ b/tool/symbols-mingw.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Run this script in a directory that contains a valid SQLite makefile in
+# order to verify that unintentionally exported symbols.
+#
+make sqlite3.c
+
+echo '****** Exported symbols from a build including RTREE && FTS4 ******'
+gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ sqlite3.c
+nm sqlite3.o | grep " [TD] "
+
+echo '****** Surplus symbols from a build including RTREE & FTS4 ******'
+nm sqlite3.o | grep " [TD] " | grep -v " .*sqlite3_"
+
+echo '****** Dependencies of the core. No extensions. No OS interface *******'
+gcc -c -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ -DSQLITE_OS_OTHER -DSQLITE_THREADSAFE=0 \
+ sqlite3.c
+nm sqlite3.o | grep " U "
+
+echo '****** Dependencies including RTREE & FTS4 *******'
+gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ sqlite3.c
+nm sqlite3.o | grep " U "
diff --git a/tool/symbols.sh b/tool/symbols.sh
new file mode 100644
index 0000000..67d578e
--- /dev/null
+++ b/tool/symbols.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Run this script in a directory that contains a valid SQLite makefile in
+# order to verify that unintentionally exported symbols.
+#
+make sqlite3.c
+
+echo '****** Exported symbols from a build including RTREE, FTS4 & FTS5 ******'
+gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ -DSQLITE_ENABLE_PREUPDATE_HOOK -DSQLITE_ENABLE_SESSION \
+ -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_GEOPOLY \
+ sqlite3.c
+nm sqlite3.o | grep ' [TD] ' | sort -k 3
+
+echo '****** Surplus symbols from a build including RTREE, FTS4 & FTS5 ******'
+nm sqlite3.o | grep ' [TD] ' |
+ egrep -v ' .*sqlite3(session|rebaser|changeset|changegroup)?_'
+
+echo '****** Dependencies of the core. No extensions. No OS interface *******'
+gcc -c -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ -DSQLITE_OS_OTHER -DSQLITE_THREADSAFE=0 \
+ sqlite3.c
+nm sqlite3.o | grep ' U ' | sort -k 3
+
+echo '****** Dependencies including RTREE & FTS4 *******'
+gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
+ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
+ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
+ sqlite3.c
+nm sqlite3.o | grep ' U ' | sort -k 3
diff --git a/tool/varint.c b/tool/varint.c
new file mode 100644
index 0000000..f4a5111
--- /dev/null
+++ b/tool/varint.c
@@ -0,0 +1,123 @@
+/*
+** A utility program to translate SQLite varints into decimal and decimal
+** integers into varints.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+ typedef __int64 i64;
+ typedef unsigned __int64 u64;
+#else
+ typedef long long int i64;
+ typedef unsigned long long int u64;
+#endif
+
+static int hexValue(char c){
+ if( c>='0' && c<='9' ) return c - '0';
+ if( c>='a' && c<='f' ) return c - 'a' + 10;
+ if( c>='A' && c<='F' ) return c - 'A' + 10;
+ return -1;
+}
+
+static char toHex(unsigned char c){
+ return "0123456789abcdef"[c&0xf];
+}
+
+static int putVarint(unsigned char *p, u64 v){
+ int i, j, n;
+ unsigned char buf[10];
+ if( v & (((u64)0xff000000)<<32) ){
+ p[8] = (unsigned char)v;
+ v >>= 8;
+ for(i=7; i>=0; i--){
+ p[i] = (unsigned char)((v & 0x7f) | 0x80);
+ v >>= 7;
+ }
+ return 9;
+ }
+ n = 0;
+ do{
+ buf[n++] = (unsigned char)((v & 0x7f) | 0x80);
+ v >>= 7;
+ }while( v!=0 );
+ buf[0] &= 0x7f;
+ for(i=0, j=n-1; j>=0; j--, i++){
+ p[i] = buf[j];
+ }
+ return n;
+}
+
+
+int main(int argc, char **argv){
+ int i;
+ u64 x;
+ u64 uX = 0;
+ i64 iX;
+ int n;
+ unsigned char zHex[20];
+
+ if( argc==1 ){
+ fprintf(stderr,
+ "Usage:\n"
+ " %s HH HH HH ... Convert varint to decimal\n"
+ " %s DDDDD Convert decimal to varint\n"
+ " Add '+' or '-' before DDDDD to disambiguate.\n",
+ argv[0], argv[0]);
+ exit(1);
+ }
+ if( argc>2
+ || (strlen(argv[1])==2 && hexValue(argv[1][0])>=0 && hexValue(argv[1][1])>=0)
+ ){
+ /* Hex to decimal */
+ for(i=1; i<argc && i<9; i++){
+ if( strlen(argv[i])!=2 ){
+ fprintf(stderr, "Not a hex byte: %s\n", argv[i]);
+ exit(1);
+ }
+ x = (hexValue(argv[i][0])<<4) + hexValue(argv[i][1]);
+ uX = (uX<<7) + (x&0x7f);
+ if( (x&0x80)==0 ) break;
+ }
+ if( i==9 && i<argc ){
+ if( strlen(argv[i])!=2 ){
+ fprintf(stderr, "Not a hex byte: %s\n", argv[i]);
+ exit(1);
+ }
+ x = (hexValue(argv[i][0])<<4) + hexValue(argv[i][1]);
+ uX = (uX<<8) + x;
+ }
+ i++;
+ if( i<argc ){
+ fprintf(stderr, "Extra arguments: %s...\n", argv[i]);
+ exit(1);
+ }
+ }else{
+ char *z = argv[1];
+ int sign = 1;
+ if( z[0]=='+' ) z++;
+ else if( z[0]=='-' ){ z++; sign = -1; }
+ uX = 0;
+ while( z[0] ){
+ if( z[0]<'0' || z[0]>'9' ){
+ fprintf(stderr, "Not a decimal number: %s", argv[1]);
+ exit(1);
+ }
+ uX = uX*10 + z[0] - '0';
+ z++;
+ }
+ if( sign<0 ){
+ memcpy(&iX, &uX, 8);
+ iX = -iX;
+ memcpy(&uX, &iX, 8);
+ }
+ }
+ n = putVarint(zHex, uX);
+ printf("%lld =", (i64)uX);
+ for(i=0; i<n; i++){
+ printf(" %c%c", toHex(zHex[i]>>4), toHex(zHex[i]&0x0f));
+ }
+ printf("\n");
+ return 0;
+}
diff --git a/tool/vdbe-compress.tcl b/tool/vdbe-compress.tcl
new file mode 100644
index 0000000..5e63c96
--- /dev/null
+++ b/tool/vdbe-compress.tcl
@@ -0,0 +1,143 @@
+#!/usr/bin/tcl
+#
+# This script makes modifications to the vdbe.c source file which reduce
+# the amount of stack space required by the sqlite3VdbeExec() routine.
+#
+# The modifications performed by this script are optional. The vdbe.c
+# source file will compile correctly with and without the modifications
+# performed by this script. And all routines within vdbe.c will compute
+# the same result. The modifications made by this script merely help
+# the C compiler to generate code for sqlite3VdbeExec() that uses less
+# stack space.
+#
+# Script usage:
+#
+# mv vdbe.c vdbe.c.template
+# tclsh vdbe-compress.tcl $CFLAGS <vdbe.c.template >vdbe.c
+#
+# Modifications made:
+#
+# All modifications are within the sqlite3VdbeExec() function. The
+# modifications seek to reduce the amount of stack space allocated by
+# this routine by moving local variable declarations out of individual
+# opcode implementations and into a single large union. The union contains
+# a separate structure for each opcode and that structure contains the
+# local variables used by that opcode. In this way, the total amount
+# of stack space required by sqlite3VdbeExec() is reduced from the
+# sum of all local variables to the maximum of the local variable space
+# required for any single opcode.
+#
+# In order to be recognized by this script, local variables must appear
+# on the first line after the open curly-brace that begins a new opcode
+# implementation. Local variables must not have initializers, though they
+# may be commented.
+#
+# The union definition is inserted in place of a special marker comment
+# in the preamble to the sqlite3VdbeExec() implementation.
+#
+#############################################################################
+#
+set beforeUnion {} ;# C code before union
+set unionDef {} ;# C code of the union
+set afterUnion {} ;# C code after the union
+set sCtr 0 ;# Context counter
+
+# If the SQLITE_SMALL_STACK compile-time option is missing, then
+# this transformation becomes a no-op.
+#
+if {![regexp {SQLITE_SMALL_STACK} $argv]} {
+ while {![eof stdin]} {
+ puts [gets stdin]
+ }
+ exit
+}
+
+# Read program text up to the spot where the union should be
+# inserted.
+#
+while {![eof stdin]} {
+ set line [gets stdin]
+ if {[regexp {INSERT STACK UNION HERE} $line]} break
+ append beforeUnion $line\n
+}
+
+# Process the remaining text. Build up the union definition as we go.
+#
+set vlist {}
+set seenDecl 0
+set namechars {abcefghjklmnopqrstuvwxyz}
+set nnc [string length $namechars]
+while {![eof stdin]} {
+ set line [gets stdin]
+ if {[regexp "^case (OP_\\w+): \173" $line all operator]} {
+ append afterUnion $line\n
+ set vlist {}
+ while {![eof stdin]} {
+ set line [gets stdin]
+ if {[regexp {^ +(const )?\w+ \**(\w+)(\[.*\])?;} $line \
+ all constKeyword vname notused1]} {
+ if {!$seenDecl} {
+ set sname {}
+ append sname [string index $namechars [expr {$sCtr/$nnc}]]
+ append sname [string index $namechars [expr {$sCtr%$nnc}]]
+ incr sCtr
+ append unionDef " struct ${operator}_stack_vars \173\n"
+ append afterUnion \
+ "#if 0 /* local variables moved into u.$sname */\n"
+ set seenDecl 1
+ }
+ append unionDef " $line\n"
+ append afterUnion $line\n
+ lappend vlist $vname
+ } elseif {[regexp {^#(if|endif)} $line] && [llength $vlist]>0} {
+ append unionDef "$line\n"
+ append afterUnion $line\n
+ } else {
+ break
+ }
+ }
+ if {$seenDecl} {
+ append unionDef " \175 $sname;\n"
+ append afterUnion "#endif /* local variables moved into u.$sname */\n"
+ }
+ set seenDecl 0
+ }
+ if {[regexp "^\175" $line]} {
+ append afterUnion $line\n
+ set vlist {}
+ } elseif {[llength $vlist]>0} {
+ append line " "
+ foreach v $vlist {
+ regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line
+ regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line
+
+ # The expressions above fail to catch instance of variable "abc" in
+ # expressions like (32>abc). The following expression makes those
+ # substitutions.
+ regsub -all "(\[^-\])>${v}(\\W)" $line "\\1>u.$sname.$v\\2" line
+ }
+ append afterUnion [string trimright $line]\n
+ } elseif {$line=="" && [eof stdin]} {
+ # no-op
+ } else {
+ append afterUnion $line\n
+ }
+}
+
+# Output the resulting text.
+#
+puts -nonewline $beforeUnion
+puts " /********************************************************************"
+puts " ** Automatically generated code"
+puts " **"
+puts " ** The following union is automatically generated by the"
+puts " ** vdbe-compress.tcl script. The purpose of this union is to"
+puts " ** reduce the amount of stack space required by this function."
+puts " ** See comments in the vdbe-compress.tcl script for details."
+puts " */"
+puts " union vdbeExecUnion \173"
+puts -nonewline $unionDef
+puts " \175 u;"
+puts " /* End automatically generated code"
+puts " ********************************************************************/"
+puts -nonewline $afterUnion
diff --git a/tool/vdbe_profile.tcl b/tool/vdbe_profile.tcl
new file mode 100644
index 0000000..b7240e3
--- /dev/null
+++ b/tool/vdbe_profile.tcl
@@ -0,0 +1,96 @@
+#!/bin/tclsh
+#
+# SUMMARY:
+# Run this script in the same directory as the "vdbe_profile.out" file.
+# This script summarizes the results contained in that file.
+#
+# DETAILS:
+# Compile SQLite using the -DVDBE_PROFILE option on Linux. This causes
+# performance information about individual VDBE operations to be appended
+# to the "vdbe_profile.out" file. After content has been accumulated in
+# vdbe_profile.out, run this script to analyze the output and generate a
+# report.
+#
+if {![file readable vdbe_profile.out]} {
+ error "run this script in the same directory as the vdbe_profile.out file"
+}
+set in [open vdbe_profile.out r]
+set stmt {}
+set allstmt {}
+while {![eof $in]} {
+ set line [gets $in]
+ if {$line==""} continue
+ if {[regexp {^---- } $line]} {
+ set stmt [lindex $line 1]
+ if {[info exists cnt($stmt)]} {
+ incr cnt($stmt)
+ set firsttime 0
+ } else {
+ set cnt($stmt) 1
+ set sql($stmt) {}
+ set firsttime 1
+ lappend allstmt $stmt
+ }
+ continue;
+ }
+ if {[regexp {^-- } $line]} {
+ if {$firsttime} {
+ append sql($stmt) [string range $line 3 end]\n
+ }
+ continue
+ }
+ if {![regexp {^ *\d+ *\d+ *\d+ *\d+ ([A-Z].*)} $line all detail]} continue
+ set c [lindex $line 0]
+ set t [lindex $line 1]
+ set addr [lindex $line 3]
+ set op [lindex $line 4]
+ if {[info exists opcnt($op)]} {
+ incr opcnt($op) $c
+ incr opcycle($op) $t
+ } else {
+ set opcnt($op) $c
+ set opcycle($op) $t
+ }
+ if {[info exists stat($stmt,$addr)]} {
+ foreach {cx tx detail} $stat($stmt,$addr) break
+ incr cx $c
+ incr tx $t
+ set stat($stmt,$addr) [list $cx $tx $detail]
+ } else {
+ set stat($stmt,$addr) [list $c $t $detail]
+ }
+}
+close $in
+
+foreach stmt $allstmt {
+ puts "********************************************************************"
+ puts [string trim $sql($stmt)]
+ puts "Execution count: $cnt($stmt)"
+ set tcx 0
+ set ttx 0
+ for {set i 0} {[info exists stat($stmt,$i)]} {incr i} {
+ foreach {cx tx detail} $stat($stmt,$i) break
+ if {$cx==0} {
+ set ax 0
+ } else {
+ set ax [expr {$tx/$cx}]
+ }
+ puts [format {%8d %12d %12d %4d %s} $cx $tx $ax $i $detail]
+ incr tcx $cx
+ incr ttx $tx
+ }
+ set tax [expr {$tcx>0?$ttx/$tcx:0}]
+ puts [format {%8d %12d %12d TOTAL} $tcx $ttx $tax]
+}
+puts "********************************************************************"
+puts "OPCODES:"
+foreach op [lsort [array names opcnt]] {
+ set cx $opcnt($op)
+ set tx $opcycle($op)
+ if {$cx==0} {
+ set ax 0
+ } else {
+ set ax [expr {$tx/$cx}]
+ }
+ puts [format {%8d %12d %12d %s} $cx $tx $ax $op]
+}
diff --git a/tool/version-info.c b/tool/version-info.c
new file mode 100644
index 0000000..62fcd63
--- /dev/null
+++ b/tool/version-info.c
@@ -0,0 +1,106 @@
+/*
+** 2022-10-16
+**
+** The author disclaims copyright to this source code. In place of a
+** legal notice, here is a blessing:
+**
+** * May you do good and not evil.
+** * May you find forgiveness for yourself and forgive others.
+** * May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file simply outputs sqlite3 version information in JSON form,
+** intended for embedding in the sqlite3 JS API build.
+*/
+#ifdef TEST_VERSION
+/*3029003 3039012*/
+#define SQLITE_VERSION "X.Y.Z"
+#define SQLITE_VERSION_NUMBER TEST_VERSION
+#define SQLITE_SOURCE_ID "dummy"
+#else
+#include "sqlite3.h"
+#endif
+#include <stdio.h>
+#include <string.h>
+static void usage(const char *zAppName){
+ puts("Emits version info about the sqlite3 it is built against.");
+ printf("Usage: %s [--quote] --INFO-FLAG:\n\n", zAppName);
+ puts(" --version Emit SQLITE_VERSION (3.X.Y)");
+ puts(" --version-number Emit SQLITE_VERSION_NUMBER (30XXYYZZ)");
+ puts(" --download-version Emit /download.html version number (3XXYYZZ)");
+ puts(" --source-id Emit SQLITE_SOURCE_ID");
+ puts(" --json Emit all info in JSON form");
+ puts("\nThe non-JSON formats may be modified by:\n");
+ puts(" --quote Add double quotes around output.");
+}
+
+int main(int argc, char const * const * argv){
+ int fJson = 0;
+ int fVersion = 0;
+ int fVersionNumber = 0;
+ int fDlVersion = 0;
+ int dlVersion = 0;
+ int fSourceInfo = 0;
+ int fQuote = 0;
+ int nFlags = 0;
+ int i;
+
+ for( i = 1; i < argc; ++i ){
+ const char * zArg = argv[i];
+ while('-'==*zArg) ++zArg;
+ if( 0==strcmp("version", zArg) ){
+ fVersion = 1;
+ }else if( 0==strcmp("version-number", zArg) ){
+ fVersionNumber = 1;
+ }else if( 0==strcmp("download-version", zArg) ){
+ fDlVersion = 1;
+ }else if( 0==strcmp("source-id", zArg) ){
+ fSourceInfo = 1;
+ }else if( 0==strcmp("json", zArg) ){
+ fJson = 1;
+ }else if( 0==strcmp("quote", zArg) ){
+ fQuote = 1;
+ --nFlags;
+ }else{
+ printf("Unhandled flag: %s\n", argv[i]);
+ usage(argv[0]);
+ return 1;
+ }
+ ++nFlags;
+ }
+
+ if( 0==nFlags ) fJson = 1;
+
+ {
+ const int v = SQLITE_VERSION_NUMBER;
+ int ver[4] = {0,0,0,0};
+ ver[0] = (v / 1000000) * 1000000;
+ ver[1] = v % 1000000 / 100 * 1000;
+ ver[2] = v % 100 * 100;
+ dlVersion = ver[0] + ver[1] + ver[2] + ver[3];
+ }
+ if( fJson ){
+ printf("{\"libVersion\": \"%s\", "
+ "\"libVersionNumber\": %d, "
+ "\"sourceId\": \"%s\","
+ "\"downloadVersion\": %d}"/*missing newline is intentional*/,
+ SQLITE_VERSION,
+ SQLITE_VERSION_NUMBER,
+ SQLITE_SOURCE_ID,
+ dlVersion);
+ }else{
+ if(fQuote) printf("%c", '"');
+ if( fVersion ){
+ printf("%s", SQLITE_VERSION);
+ }else if( fVersionNumber ){
+ printf("%d", SQLITE_VERSION_NUMBER);
+ }else if( fSourceInfo ){
+ printf("%s", SQLITE_SOURCE_ID);
+ }else if( fDlVersion ){
+ printf("%d", dlVersion);
+ }
+ if(fQuote) printf("%c", '"');
+ puts("");
+ }
+ return 0;
+}
diff --git a/tool/warnings-clang.sh b/tool/warnings-clang.sh
new file mode 100644
index 0000000..6dcc086
--- /dev/null
+++ b/tool/warnings-clang.sh
@@ -0,0 +1,14 @@
+#/bin/sh
+#
+# Run this script in a directory with a working makefile to check for
+# compiler warnings in SQLite.
+#
+rm -f sqlite3.c shell.c
+make sqlite3.c shell.c
+echo '************* FTS4 and RTREE ****************'
+scan-build gcc -c -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_DEBUG -DSQLITE_ENABLE_STAT3 sqlite3.c 2>&1 | grep -v 'ANALYZE:'
+echo '********** ENABLE_STAT3. THREADSAFE=0 *******'
+scan-build gcc -c -I. -DSQLITE_ENABLE_STAT3 -DSQLITE_THREADSAFE=0 \
+ -DSQLITE_DEBUG \
+ sqlite3.c shell.c -ldl 2>&1 | grep -v 'ANALYZE:'
diff --git a/tool/warnings.sh b/tool/warnings.sh
new file mode 100644
index 0000000..2b962d1
--- /dev/null
+++ b/tool/warnings.sh
@@ -0,0 +1,65 @@
+#/bin/sh
+#
+# Run this script in a directory with a working makefile to check for
+# compiler warnings in SQLite.
+#
+
+if uname | grep -i openbsd ; then
+ # Use these for testing on OpenBSD:
+ WARNING_OPTS=-Wall
+ WARNING_ANDROID_OPTS=-Wall
+else
+ # Use these for testing on Linux and Mac OSX:
+ WARNING_OPTS="-Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long"
+ gccvers=`gcc -v 2>&1 | grep '^gcc version'`
+ if test "$gccvers" '<' 'gcc version 6'
+ then
+ WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra"
+ else
+ WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra -Wimplicit-fallthrough=0"
+ fi
+fi
+
+rm -f sqlite3.c
+make sqlite3.c
+echo '********** No optimizations. Includes FTS4/5, GEOPOLY, JSON1 ***'
+echo '********** ' Options: $WARNING_OPTS
+gcc -c $WARNING_OPTS -std=c89 \
+ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_GEOPOLY \
+ -DSQLITE_ENABLE_FTS5 \
+ sqlite3.c
+if test x`uname` = 'xLinux'; then
+echo '********** Android configuration ******************************'
+echo '********** ' Options: $WARNING_ANDROID_OPTS
+gcc -c \
+ -DSQLITE_HAVE_ISNAN \
+ -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 \
+ -DSQLITE_THREADSAFE=2 \
+ -DSQLITE_TEMP_STORE=3 \
+ -DSQLITE_POWERSAFE_OVERWRITE=1 \
+ -DSQLITE_DEFAULT_FILE_FORMAT=4 \
+ -DSQLITE_DEFAULT_AUTOVACUUM=1 \
+ -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 \
+ -DSQLITE_ENABLE_FTS3 \
+ -DSQLITE_ENABLE_FTS3_BACKWARDS \
+ -DSQLITE_ENABLE_FTS4 \
+ -DSQLITE_OMIT_BUILTIN_TEST \
+ -DSQLITE_OMIT_COMPILEOPTION_DIAGS \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 \
+ -DSQLITE_ENABLE_ICU \
+ -DUSE_PREAD64 \
+ $WARNING_ANDROID_OPTS \
+ -Os sqlite3.c shell.c
+fi
+echo '********** No optimizations. ENABLE_STAT4. THREADSAFE=0 *******'
+echo '********** ' Options: $WARNING_OPTS
+gcc -c $WARNING_OPTS -std=c89 \
+ -ansi -DSQLITE_ENABLE_STAT4 -DSQLITE_THREADSAFE=0 \
+ sqlite3.c
+echo '********** Optimized -O3. Includes FTS4/5, GEOPOLY, JSON1 ******'
+echo '********** ' Options: $WARNING_OPTS
+gcc -O3 -c $WARNING_OPTS -std=c89 \
+ -ansi -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_GEOPOLY \
+ -DSQLITE_ENABLE_FTS5 \
+ sqlite3.c
diff --git a/tool/win/sqlite.vsix b/tool/win/sqlite.vsix
new file mode 100644
index 0000000..1450011
--- /dev/null
+++ b/tool/win/sqlite.vsix
Binary files differ