From 6fcbbfbab28cd6d24297a27de49c55f8eb9b916c Mon Sep 17 00:00:00 2001 From: andri lim Date: Fri, 2 Nov 2018 12:10:58 +0700 Subject: [PATCH] initial commit --- .appveyor.yml | 98 + .gitignore | 3 + .travis.yml | 46 + LICENSE | 21 + snappy.nim | 498 + snappy.nimble | 12 + tests/config.nims | 1 + tests/data/COPYING | 23 + tests/data/Mark.Twain-Tom.Sawyer.txt | 396 + .../data/Mark.Twain-Tom.Sawyer.txt.rawsnappy | Bin 0 -> 9871 bytes tests/data/alice29.txt | 3609 ++++++ tests/data/asyoulik.txt | 4122 ++++++ tests/data/baddata1.snappy | Bin 0 -> 27512 bytes tests/data/baddata2.snappy | Bin 0 -> 27483 bytes tests/data/baddata3.snappy | Bin 0 -> 28384 bytes tests/data/fireworks.jpeg | Bin 0 -> 123093 bytes tests/data/geo.protodata | Bin 0 -> 118588 bytes tests/data/html | 1 + tests/data/html_x_4 | 1 + tests/data/kppkn.gtb | Bin 0 -> 184320 bytes tests/data/lcet10.txt | 7519 +++++++++++ tests/data/paper-100k.pdf | 598 + tests/data/plrabn12.txt | 10699 ++++++++++++++++ tests/data/urls.10K | 10000 +++++++++++++++ tests/randgen.nim | 46 + tests/test.nim | 245 + 26 files changed, 37938 insertions(+) create mode 100644 .appveyor.yml create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 snappy.nim create mode 100644 snappy.nimble create mode 100644 tests/config.nims create mode 100644 tests/data/COPYING create mode 100644 tests/data/Mark.Twain-Tom.Sawyer.txt create mode 100644 tests/data/Mark.Twain-Tom.Sawyer.txt.rawsnappy create mode 100644 tests/data/alice29.txt create mode 100644 tests/data/asyoulik.txt create mode 100644 tests/data/baddata1.snappy create mode 100644 tests/data/baddata2.snappy create mode 100644 tests/data/baddata3.snappy create mode 100644 tests/data/fireworks.jpeg create mode 100644 tests/data/geo.protodata create mode 100644 tests/data/html create mode 100644 tests/data/html_x_4 create mode 100644 tests/data/kppkn.gtb create mode 100644 tests/data/lcet10.txt create mode 100644 tests/data/paper-100k.pdf create mode 100644 tests/data/plrabn12.txt create mode 100644 tests/data/urls.10K create mode 100644 tests/randgen.nim create mode 100644 tests/test.nim diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..36a374a --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,98 @@ +version: '{build}' + +cache: +- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z -> .appveyor.yml +- i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z -> .appveyor.yml +- Nim -> .appveyor.yml + +matrix: + # We always want 32 and 64-bit compilation + fast_finish: false + +platform: + - x86 + - x64 + +install: + - setlocal EnableExtensions EnableDelayedExpansion + + - IF "%PLATFORM%" == "x86" ( + SET "MINGW_ARCHIVE=i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" & + SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win32/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" & + SET "MINGW_DIR=mingw32" + ) ELSE ( + IF "%PLATFORM%" == "x64" ( + SET "MINGW_ARCHIVE=x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" & + SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win64/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" & + SET "MINGW_DIR=mingw64" + ) else ( + echo "Unknown platform" + ) + ) + + - SET PATH=%CD%\%MINGW_DIR%\bin;%CD%\Nim\bin;%PATH% + + # Unpack mingw + - IF NOT EXIST "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%" + - 7z x -y "%MINGW_ARCHIVE%" > nul + + # build nim from our own branch - this to avoid the day-to-day churn and + # regressions of the fast-paced Nim development while maintaining the + # flexibility to apply patches + - SET "NEED_REBUILD=" + + - IF NOT EXIST "Nim\\.git\\" ( + git clone https://github.com/nim-lang/Nim.git + ) ELSE ( + ( cd Nim ) & + ( git pull ) & + ( cd .. ) + ) + + # Rebuild Nim if HEAD has moved or if we don't yet have a cached version + - IF NOT EXIST "Nim\\ver.txt" ( + SET NEED_REBUILD=1 + ) ELSE ( + ( CD Nim ) & + ( git rev-parse HEAD > ..\\cur_ver.txt ) & + ( fc ver.txt ..\\cur_ver.txt > nul ) & + ( IF NOT ERRORLEVEL == 0 SET NEED_REBUILD=1 ) & + ( cd .. ) + ) + + - IF NOT EXIST "Nim\\bin\\nim.exe" SET NEED_REBUILD=1 + - IF NOT EXIST "Nim\\bin\\nimble.exe" SET NEED_REBUILD=1 + + # after building nim, wipe csources to save on cache space + - IF DEFINED NEED_REBUILD ( + cd Nim & + ( IF EXIST "csources" rmdir /s /q csources ) & + git clone --depth 1 https://github.com/nim-lang/csources & + cd csources & + ( IF "%PLATFORM%" == "x64" ( build64.bat > nul ) else ( build.bat > nul ) ) & + cd .. & + bin\nim c --verbosity:0 --hints:off koch & + koch boot -d:release --verbosity:0 --hints:off & + koch nimble > nul & + git rev-parse HEAD > ver.txt & + rmdir /s /q csources & + cd .. + ) + + - git clone --depth 1 https://github.com/google/snappy + +build_script: + - cd snappy + - mkdir build + - cd build + - cmake .. -G "MinGW Makefiles" + - mingw32-make + - copy libsnappy.a C:\projects\%APPVEYOR_PROJECT_SLUG%\tests + + - cd C:\projects\%APPVEYOR_PROJECT_SLUG% + - nimble install -y > nul + +test_script: + - nimble test + +deploy: off diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..249de29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +master +*.exe +libsnappy.a \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c3971be --- /dev/null +++ b/.travis.yml @@ -0,0 +1,46 @@ +language: c # or other C/C++ variants + +sudo: false + +# https://docs.travis-ci.com/user/caching/ +# +# Caching the whole nim folder is better than relying on ccache - this way, we +# skip the expensive bootstrap process and linking +cache: + directories: + - nim + +os: + - linux + - osx + +install: + # build nim from our own branch - this to avoid the day-to-day churn and + # regressions of the fast-paced Nim development while maintaining the + # flexibility to apply patches + # + # check version of remote branch + - "export NIMVER=$(git ls-remote https://github.com/nim-lang/nim.git HEAD | cut -f 1)" + + # after building nim, wipe csources to save on cache space + - "{ [ -f nim/$NIMVER/bin/nim ] && [ -f nim/$NIMVER/bin/nimble ] ; } || + { rm -rf nim ; + mkdir -p nim ; + git clone --depth=1 https://github.com/nim-lang/nim.git nim/$NIMVER ; + cd nim/$NIMVER ; + sh build_all.sh > /dev/null; + rm -rf csources ; + cd ../.. ; + }" + - "export PATH=$PWD/nim/$NIMVER/bin:$PATH" + - git clone --depth 1 https://github.com/google/snappy + +script: + - cd snappy + - mkdir build + - cd build && cmake ../ && make + - cd ../.. + - cp snappy/build/libsnappy.a tests/libsnappy.a + + - nimble install -y > /dev/null + - nimble test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4655588 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 andri lim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/snappy.nim b/snappy.nim new file mode 100644 index 0000000..c060b5b --- /dev/null +++ b/snappy.nim @@ -0,0 +1,498 @@ +const + tagLiteral* = 0x00 + tagCopy1* = 0x01 + tagCopy2* = 0x02 + tagCopy4* = 0x03 + + inputMargin = 16 - 1 + +# PutUvarint encodes a uint64 into buf and returns the number of bytes written. +func putUvarint(buf: var openArray[byte], x: uint64): int = + var + i = 0 + x = x + + while x >= 0x80'u64: + buf[i] = byte(x and 0xFF) or 0x80 + x = x shr 7 + inc i + buf[i] = byte(x and 0xFF) + result = i + 1 + +# Uvarint decodes a uint64 from buf and returns that value and the +# number of bytes read (> 0). If an error occurred, the value is 0 +# and the number of bytes n is <= 0 meaning: +# +# n == 0: buf too small +# n < 0: value larger than 64 bits (overflow) +# and -n is the number of bytes read +# +func uvarint(buf: openArray[byte]): (uint64, int) = + var x: uint64 + var s: uint + for i, b in buf: + if int(b) < 0x80: + if (i > 9) or (i == 9) and (int(b) > 1): + return (0'u64, -(i + 1)) # overflow + return (x or (uint64(b) shl s), i + 1) + x = x or (uint64(b and 0x7F) shl s) + inc(s, 7) + result = (0'u64, 0) + +template sliceImpl(r: openArray[byte], a, b: int): auto = + toOpenArray(cast[ptr array[0, byte]](r[0].unsafeAddr)[], a, b) + +template `%`(s, i: untyped): untyped = + (when i is BackwardsIndex: s.len - int(i) else: int(i)) + +template `[]`[U, V](r: openArray[byte], s: HSlice[U, V]): auto = + sliceImpl(r, r % s.a, r % s.b) + +func load32(b: openArray[byte]): uint32 {.inline.} = + result = uint32(b[0]) or + (uint32(b[1]) shl 8 ) or + (uint32(b[2]) shl 16) or + (uint32(b[3]) shl 24) + +func load32(b: openArray[byte], i: int): uint32 = + result = load32(b[i..= 68: + # Emit a length 64 copy, encoded as 3 bytes. + dst[i+0] = (63 shl 2) or tagCopy2 + dst[i+1] = byte(offset) + dst[i+2] = byte(offset shr 8) + inc(i, 3) + dec(length, 64) + + if length > 64: + # Emit a length 60 copy, encoded as 3 bytes. + dst[i+0] = (59 shl 2) or tagCopy2 + dst[i+1] = byte(offset) + dst[i+2] = byte(offset shr 8) + inc(i, 3) + dec(length, 60) + + if (length >= 12) or (offset >= 2048): + # Emit the remaining copy, encoded as 3 bytes. + dst[i+0] = (byte(length-1) shl 2) or tagCopy2 + dst[i+1] = byte(offset) + dst[i+2] = byte(offset shr 8) + return i + 3 + + # Emit the remaining copy, encoded as 2 bytes. + dst[i+0] = (byte(offset shr 8) shl 5) or (byte(length-4) shl 2) or tagCopy1 + dst[i+1] = byte(offset) + result = i + 2 + +when false: + # extendMatch returns the largest k such that k <= len(src) and that + # src[i:i+k-j] and src[j:k] have the same contents. + # + # It assumes that: + # 0 <= i and i < j and j <= len(src) + func extendMatch(src: openArray[byte], i, j: int): int = + var + i = i + j = j + while j < src.len and src[i] == src[j]: + inc i + inc j + result = j + +func hash(u, shift: uint32): uint32 = + result = (u * 0x1e35a7bd) shr shift + +# encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It +# assumes that the varint-encoded length of the decompressed bytes has already +# been written. +# +# It also assumes that: +# len(dst) >= MaxEncodedLen(len(src)) and +# minNonLiteralBlockSize <= len(src) and len(src) <= maxBlockSize +func encodeBlock(dst, src: var openArray[byte]): int = + # Initialize the hash table. Its size ranges from 1shl8 to 1shl14 inclusive. + # The table element type is uint16, as s < sLimit and sLimit < len(src) + # and len(src) <= maxBlockSize and maxBlockSize == 65536. + const + maxTableSize = 1 shl 14 + # tableMask is redundant, but helps the compiler eliminate bounds + # checks. + tableMask = maxTableSize - 1 + + var + shift = 32 - 8 + tableSize = 1 shl 8 + + while tableSize < maxTableSize and tableSize < src.len: + tableSize = tableSize * 2 + dec shift + + # In Nim, all array elements are zero-initialized, so there is no advantage + # to a smaller tableSize per se. However, it matches the C++ algorithm, + # and in the asm versions of this code, we can get away with zeroing only + # the first tableSize elements. + var table: array[maxTableSize, uint16] + + # sLimit is when to stop looking for offset/length copies. The inputMargin + # lets us use a fast path for emitLiteral in the main loop, while we are + # looking for copies. + var sLimit = src.len - inputMargin + # nextEmit is where in src the next emitLiteral should start from. + var nextEmit = 0 + + # The encoded form must start with a literal, as there are no previous + # bytes to copy, so we start looking for hash matches at s == 1. + var s = 1 + var nextHash = hash(load32(src, s), shift.uint32) + var d = 0 + + template emitRemainder(): untyped = + if nextEmit < src.len: + let litLen = emitLiteral(dst[d..^1], src[nextEmit..^1]) + inc(d, litLen) + return d + + while true: + # Copied from the C++ snappy implementation: + # + # Heuristic match skipping: If 32 bytes are scanned with no matches + # found, start looking only at every other byte. If 32 more bytes are + # scanned (or skipped), look at every third byte, etc.. When a match + # is found, immediately go back to looking at every byte. This is a + # small loss (~5% performance, ~0.1% density) for compressible data + # due to more bookkeeping, but for non-compressible data (such as + # JPEG) it's a huge win since the compressor quickly "realizes" the + # data is incompressible and doesn't bother looking for matches + # everywhere. + # + # The "skip" variable keeps track of how many bytes there are since + # the last match; dividing it by 32 (ie. right-shifting by five) gives + # the number of bytes to move ahead for each iteration. + var skip = 32 + + var nextS = s + var candidate = 0 + while true: + s = nextS + let bytesBetweenHashLookups = skip shr 5 + nextS = s + bytesBetweenHashLookups + inc(skip, bytesBetweenHashLookups) + if nextS > sLimit: + emitRemainder() + + candidate = int(table[nextHash and tableMask]) + table[nextHash and tableMask] = uint16(s) + nextHash = hash(load32(src, nextS), shift.uint32) + if load32(src, s) == load32(src, candidate): + break + + # A 4-byte match has been found. We'll later see if more than 4 bytes + # match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + # them as literal bytes. + var litLen = emitLiteral(dst[d..^1], src[nextEmit..= sLimit: + emitRemainder() + + # We could immediately start working at s now, but to improve + # compression we first update the hash table at s-1 and at s. If + # another emitCopy is not our next move, also calculate nextHash + # at s+1. At least on ARCH=amd64, these three hash calculations + # are faster as one load64 call (with some shifts) instead of + # three load32 calls. + var x = load64(src, s-1) + var prevHash = hash(uint32(x shr 0), shift.uint32) + table[prevHash and tableMask] = uint16(s - 1) + var currHash = hash(uint32(x shr 8), shift.uint32) + candidate = int(table[currHash and tableMask]) + table[currHash and tableMask] = uint16(s) + if uint32(x shr 8) != load32(src, candidate): + nextHash = hash(uint32(x shr 16), shift.uint32) + inc s + break + result = d + +const + decodeErrCodeCorrupt = 1 + decodeErrCodeUnsupportedLiteralLength = 2 + +func decode(dst, src: var openArray[byte]): int = + var + d = 0 + s = 0 + offset = 0 + length = 0 + + while s < src.len: + let tag = src[s] and 0x03 + case tag + of tagLiteral: + var x = int(src[s]) shr 2 + if x < 60: + inc s + elif x == 60: + inc(s, 2) + if s > src.len: + return decodeErrCodeCorrupt + x = int(src[s-1]) + elif x == 61: + inc(s, 3) + if s > src.len: + return decodeErrCodeCorrupt + x = int(src[s-2]) or (int(src[s-1]) shl 8) + elif x == 62: + inc(s, 4) + if s > src.len: + return decodeErrCodeCorrupt + x = int(src[s-3]) or (int(src[s-2]) shl 8) or (int(src[s-1]) shl 16) + elif x == 63: + inc(s, 5) + if s > src.len: + return decodeErrCodeCorrupt + x = int(src[s-4]) or (int(src[s-3]) shl 8) or (int(src[s-2]) shl 16) or (int(src[s-1]) shl 24) + length = x + 1 + if length <= 0: + return decodeErrCodeUnsupportedLiteralLength + + if (length > (dst.len-d)) or (length > (src.len-s)): + return decodeErrCodeCorrupt + + copyMem(dst[d].addr, src[s].addr, length) + inc(d, length) + inc(s, length) + continue + + of tagCopy1: + inc(s, 2) + if s > src.len: + return decodeErrCodeCorrupt + length = 4 + ((int(src[s-2]) shr 2) and 0x07) + offset = ((int(src[s-2]) and 0xe0) shl 3) or int(src[s-1]) + + of tagCopy2: + s += 3 + if s > src.len: + return decodeErrCodeCorrupt + length = 1 + (int(src[s-3]) shr 2) + offset = int(src[s-2]) or (int(src[s-1]) shl 8) + + of tagCopy4: + s += 5 + if s > src.len: + return decodeErrCodeCorrupt + length = 1 + (int(src[s-5]) shr 2) + offset = int(src[s-4]) or (int(src[s-3]) shl 8) or (int(src[s-2]) shl 16) or (int(src[s-1]) shl 24) + + else: discard + + if offset <= 0 or d < offset or (length > (dst.len-d)): + return decodeErrCodeCorrupt + + # Copy from an earlier sub-slice of dst to a later sub-slice. Unlike + # the built-in copy function, this byte-by-byte copy always runs + # forwards, even if the slices overlap. Conceptually, this is: + # + # d += forwardCopy(dst[d:d+length], dst[d-offset:]) + var stop = d + length + while d != stop: + dst[d] = dst[d-offset] + inc d + + if d != dst.len: + return decodeErrCodeCorrupt + return 0 + +# MaxEncodedLen returns the maximum length of a snappy block, given its +# uncompressed length. +# +# It will return a zero value if srcLen is too large to encode. +func maxEncodedLen(srcLen: int): int = + var n = uint64(srcLen) + if n > 0xffffffff'u64: + return 0 + + # Compressed data can be defined as: + # compressed := item* literal* + # item := literal* copy + # + # The trailing literal sequence has a space blowup of at most 62/60 + # since a literal of length 60 needs one tag byte + one extra byte + # for length information. + # + # Item blowup is trickier to measure. Suppose the "copy" op copies + # 4 bytes of data. Because of a special check in the encoding code, + # we produce a 4-byte copy only if the offset is < 65536. Therefore + # the copy op takes 3 bytes to encode, and this type of item leads + # to at most the 62/60 blowup for representing literals. + # + # Suppose the "copy" op copies 5 bytes of data. If the offset is big + # enough, it will take 5 bytes to encode the copy op. Therefore the + # worst case here is a one-byte literal followed by a five-byte copy. + # That is, 6 bytes of input turn into 7 bytes of "compressed" data. + # + # This last factor dominates the blowup, so the final estimate is: + n = 32'u64 + n + n div 6'u64 + if n > 0xffffffff'u64: + return 0 + + result = int(n) + +const + maxBlockSize = 65536 + +# minNonLiteralBlockSize is the minimum size of the input to encodeBlock that +# could be encoded with a copy tag. This is the minimum with respect to the +# algorithm used by encodeBlock, not a minimum enforced by the file format. +# +# The encoded output must start with at least a 1 byte literal, as there are +# no previous bytes to copy. A minimal (1 byte) copy after that, generated +# from an emitCopy call in encodeBlock's main loop, would require at least +# another inputMargin bytes, for the reason above: we want any emitLiteral +# calls inside encodeBlock's main loop to use the fast path if possible, which +# requires being able to overrun by inputMargin bytes. Thus, +# minNonLiteralBlockSize equals 1 + 1 + inputMargin. +# +# The C++ code doesn't use this exact threshold, but it could, as discussed at +# https:#groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion +# The difference between Nim (2+inputMargin) and C++ (inputMargin) is purely an +# optimization. It should not affect the encoded form. This is tested by +# TestSameEncodingAsCppShortCopies. +const + minNonLiteralBlockSize = 1 + 1 + inputMargin + +# Encode returns the encoded form of src. The returned slice may be a sub- +# slice of dst if dst was large enough to hold the entire encoded block. +# Otherwise, a newly allocated slice will be returned. +# +# The dst and src must not overlap. It is valid to pass a nil dst. +proc encode*(src: openArray[byte]): seq[byte] = + let n = maxEncodedLen(src.len) + if n == 0: return + var dst = newSeq[byte](n) + + # The block starts with the varint-encoded length of the decompressed bytes. + var + p = 0 + d = putUVarInt(dst, uint64(src.len)) + len = src.len + + while len > 0: + var blockSize = len + if blockSize > maxBlockSize: + blockSize = maxBlockSize + + if blockSize < minNonLiteralBlockSize: + let litLen = emitLiteral(dst[d..^1], src[p.. 0xffffffff'u64: + return + + const wordSize = sizeof(uint) * 8 + if (wordSize == 32) and (len > 0x7fffffff'u64): + return + + if int(len) > 0: + result = newSeq[byte](len) + let errCode = decode(result, src[bytesRead..^1]) + if errCode != 0: result = @[] diff --git a/snappy.nimble b/snappy.nimble new file mode 100644 index 0000000..12dcfce --- /dev/null +++ b/snappy.nimble @@ -0,0 +1,12 @@ +packageName = "snappy" +version = "0.1.0" +author = "Andri Lim" +description = "Nim implementation of snappy compression algorithm" +license = "MIT" +skipDirs = @["tests"] + +requires: "nim >= 0.19.0" + +task test, "Run all tests": + exec "nim c tests/test" + exec "nim c -d:release tests/test" diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..46348f1 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1 @@ +switch("path", "..") diff --git a/tests/data/COPYING b/tests/data/COPYING new file mode 100644 index 0000000..ae69a7b --- /dev/null +++ b/tests/data/COPYING @@ -0,0 +1,23 @@ +Some of the benchmark data in in this directory is licensed thusly: + + - fireworks.jpeg is Copyright 2013 Steinar H. Gunderson, and + is licensed under the Creative Commons Attribution 3.0 license + (CC-BY-3.0). See https://creativecommons.org/licenses/by/3.0/ + for more information. + + - kppkn.gtb is taken from the Gaviota chess tablebase set, and + is licensed under the MIT License. See + https://sites.google.com/site/gaviotachessengine/Home/endgame-tablebases-1 + for more information. + + - paper-100k.pdf is an excerpt (bytes 92160 to 194560) from the paper + “Combinatorial Modeling of Chromatin Features Quantitatively Predicts DNA + Replication Timing in _Drosophila_” by Federico Comoglio and Renato Paro, + which is licensed under the CC-BY license. See + http://www.ploscompbiol.org/static/license for more ifnormation. + + - alice29.txt, asyoulik.txt, plrabn12.txt and lcet10.txt are from Project + Gutenberg. The first three have expired copyrights and are in the public + domain; the latter does not have expired copyright, but is still in the + public domain according to the license information + (http://www.gutenberg.org/ebooks/53). diff --git a/tests/data/Mark.Twain-Tom.Sawyer.txt b/tests/data/Mark.Twain-Tom.Sawyer.txt new file mode 100644 index 0000000..86a1875 --- /dev/null +++ b/tests/data/Mark.Twain-Tom.Sawyer.txt @@ -0,0 +1,396 @@ +Produced by David Widger. The previous edition was updated by Jose +Menendez. + + + + + + THE ADVENTURES OF TOM SAWYER + BY + MARK TWAIN + (Samuel Langhorne Clemens) + + + + + P R E F A C E + +MOST of the adventures recorded in this book really occurred; one or +two were experiences of my own, the rest those of boys who were +schoolmates of mine. Huck Finn is drawn from life; Tom Sawyer also, but +not from an individual--he is a combination of the characteristics of +three boys whom I knew, and therefore belongs to the composite order of +architecture. + +The odd superstitions touched upon were all prevalent among children +and slaves in the West at the period of this story--that is to say, +thirty or forty years ago. + +Although my book is intended mainly for the entertainment of boys and +girls, I hope it will not be shunned by men and women on that account, +for part of my plan has been to try to pleasantly remind adults of what +they once were themselves, and of how they felt and thought and talked, +and what queer enterprises they sometimes engaged in. + + THE AUTHOR. + +HARTFORD, 1876. + + + + T O M S A W Y E R + + + +CHAPTER I + +"TOM!" + +No answer. + +"TOM!" + +No answer. + +"What's gone with that boy, I wonder? You TOM!" + +No answer. + +The old lady pulled her spectacles down and looked over them about the +room; then she put them up and looked out under them. She seldom or +never looked THROUGH them for so small a thing as a boy; they were her +state pair, the pride of her heart, and were built for "style," not +service--she could have seen through a pair of stove-lids just as well. +She looked perplexed for a moment, and then said, not fiercely, but +still loud enough for the furniture to hear: + +"Well, I lay if I get hold of you I'll--" + +She did not finish, for by this time she was bending down and punching +under the bed with the broom, and so she needed breath to punctuate the +punches with. She resurrected nothing but the cat. + +"I never did see the beat of that boy!" + +She went to the open door and stood in it and looked out among the +tomato vines and "jimpson" weeds that constituted the garden. No Tom. +So she lifted up her voice at an angle calculated for distance and +shouted: + +"Y-o-u-u TOM!" + +There was a slight noise behind her and she turned just in time to +seize a small boy by the slack of his roundabout and arrest his flight. + +"There! I might 'a' thought of that closet. What you been doing in +there?" + +"Nothing." + +"Nothing! Look at your hands. And look at your mouth. What IS that +truck?" + +"I don't know, aunt." + +"Well, I know. It's jam--that's what it is. Forty times I've said if +you didn't let that jam alone I'd skin you. Hand me that switch." + +The switch hovered in the air--the peril was desperate-- + +"My! Look behind you, aunt!" + +The old lady whirled round, and snatched her skirts out of danger. The +lad fled on the instant, scrambled up the high board-fence, and +disappeared over it. + +His aunt Polly stood surprised a moment, and then broke into a gentle +laugh. + +"Hang the boy, can't I never learn anything? Ain't he played me tricks +enough like that for me to be looking out for him by this time? But old +fools is the biggest fools there is. Can't learn an old dog new tricks, +as the saying is. But my goodness, he never plays them alike, two days, +and how is a body to know what's coming? He 'pears to know just how +long he can torment me before I get my dander up, and he knows if he +can make out to put me off for a minute or make me laugh, it's all down +again and I can't hit him a lick. I ain't doing my duty by that boy, +and that's the Lord's truth, goodness knows. Spare the rod and spile +the child, as the Good Book says. I'm a laying up sin and suffering for +us both, I know. He's full of the Old Scratch, but laws-a-me! he's my +own dead sister's boy, poor thing, and I ain't got the heart to lash +him, somehow. Every time I let him off, my conscience does hurt me so, +and every time I hit him my old heart most breaks. Well-a-well, man +that is born of woman is of few days and full of trouble, as the +Scripture says, and I reckon it's so. He'll play hookey this evening, * +and [* Southwestern for "afternoon"] I'll just be obleeged to make him +work, to-morrow, to punish him. It's mighty hard to make him work +Saturdays, when all the boys is having holiday, but he hates work more +than he hates anything else, and I've GOT to do some of my duty by him, +or I'll be the ruination of the child." + +Tom did play hookey, and he had a very good time. He got back home +barely in season to help Jim, the small colored boy, saw next-day's +wood and split the kindlings before supper--at least he was there in +time to tell his adventures to Jim while Jim did three-fourths of the +work. Tom's younger brother (or rather half-brother) Sid was already +through with his part of the work (picking up chips), for he was a +quiet boy, and had no adventurous, troublesome ways. + +While Tom was eating his supper, and stealing sugar as opportunity +offered, Aunt Polly asked him questions that were full of guile, and +very deep--for she wanted to trap him into damaging revealments. Like +many other simple-hearted souls, it was her pet vanity to believe she +was endowed with a talent for dark and mysterious diplomacy, and she +loved to contemplate her most transparent devices as marvels of low +cunning. Said she: + +"Tom, it was middling warm in school, warn't it?" + +"Yes'm." + +"Powerful warm, warn't it?" + +"Yes'm." + +"Didn't you want to go in a-swimming, Tom?" + +A bit of a scare shot through Tom--a touch of uncomfortable suspicion. +He searched Aunt Polly's face, but it told him nothing. So he said: + +"No'm--well, not very much." + +The old lady reached out her hand and felt Tom's shirt, and said: + +"But you ain't too warm now, though." And it flattered her to reflect +that she had discovered that the shirt was dry without anybody knowing +that that was what she had in her mind. But in spite of her, Tom knew +where the wind lay, now. So he forestalled what might be the next move: + +"Some of us pumped on our heads--mine's damp yet. See?" + +Aunt Polly was vexed to think she had overlooked that bit of +circumstantial evidence, and missed a trick. Then she had a new +inspiration: + +"Tom, you didn't have to undo your shirt collar where I sewed it, to +pump on your head, did you? Unbutton your jacket!" + +The trouble vanished out of Tom's face. He opened his jacket. His +shirt collar was securely sewed. + +"Bother! Well, go 'long with you. I'd made sure you'd played hookey +and been a-swimming. But I forgive ye, Tom. I reckon you're a kind of a +singed cat, as the saying is--better'n you look. THIS time." + +She was half sorry her sagacity had miscarried, and half glad that Tom +had stumbled into obedient conduct for once. + +But Sidney said: + +"Well, now, if I didn't think you sewed his collar with white thread, +but it's black." + +"Why, I did sew it with white! Tom!" + +But Tom did not wait for the rest. As he went out at the door he said: + +"Siddy, I'll lick you for that." + +In a safe place Tom examined two large needles which were thrust into +the lapels of his jacket, and had thread bound about them--one needle +carried white thread and the other black. He said: + +"She'd never noticed if it hadn't been for Sid. Confound it! sometimes +she sews it with white, and sometimes she sews it with black. I wish to +geeminy she'd stick to one or t'other--I can't keep the run of 'em. But +I bet you I'll lam Sid for that. I'll learn him!" + +He was not the Model Boy of the village. He knew the model boy very +well though--and loathed him. + +Within two minutes, or even less, he had forgotten all his troubles. +Not because his troubles were one whit less heavy and bitter to him +than a man's are to a man, but because a new and powerful interest bore +them down and drove them out of his mind for the time--just as men's +misfortunes are forgotten in the excitement of new enterprises. This +new interest was a valued novelty in whistling, which he had just +acquired from a negro, and he was suffering to practise it undisturbed. +It consisted in a peculiar bird-like turn, a sort of liquid warble, +produced by touching the tongue to the roof of the mouth at short +intervals in the midst of the music--the reader probably remembers how +to do it, if he has ever been a boy. Diligence and attention soon gave +him the knack of it, and he strode down the street with his mouth full +of harmony and his soul full of gratitude. He felt much as an +astronomer feels who has discovered a new planet--no doubt, as far as +strong, deep, unalloyed pleasure is concerned, the advantage was with +the boy, not the astronomer. + +The summer evenings were long. It was not dark, yet. Presently Tom +checked his whistle. A stranger was before him--a boy a shade larger +than himself. A new-comer of any age or either sex was an impressive +curiosity in the poor little shabby village of St. Petersburg. This boy +was well dressed, too--well dressed on a week-day. This was simply +astounding. His cap was a dainty thing, his close-buttoned blue cloth +roundabout was new and natty, and so were his pantaloons. He had shoes +on--and it was only Friday. He even wore a necktie, a bright bit of +ribbon. He had a citified air about him that ate into Tom's vitals. The +more Tom stared at the splendid marvel, the higher he turned up his +nose at his finery and the shabbier and shabbier his own outfit seemed +to him to grow. Neither boy spoke. If one moved, the other moved--but +only sidewise, in a circle; they kept face to face and eye to eye all +the time. Finally Tom said: + +"I can lick you!" + +"I'd like to see you try it." + +"Well, I can do it." + +"No you can't, either." + +"Yes I can." + +"No you can't." + +"I can." + +"You can't." + +"Can!" + +"Can't!" + +An uncomfortable pause. Then Tom said: + +"What's your name?" + +"'Tisn't any of your business, maybe." + +"Well I 'low I'll MAKE it my business." + +"Well why don't you?" + +"If you say much, I will." + +"Much--much--MUCH. There now." + +"Oh, you think you're mighty smart, DON'T you? I could lick you with +one hand tied behind me, if I wanted to." + +"Well why don't you DO it? You SAY you can do it." + +"Well I WILL, if you fool with me." + +"Oh yes--I've seen whole families in the same fix." + +"Smarty! You think you're SOME, now, DON'T you? Oh, what a hat!" + +"You can lump that hat if you don't like it. I dare you to knock it +off--and anybody that'll take a dare will suck eggs." + +"You're a liar!" + +"You're another." + +"You're a fighting liar and dasn't take it up." + +"Aw--take a walk!" + +"Say--if you give me much more of your sass I'll take and bounce a +rock off'n your head." + +"Oh, of COURSE you will." + +"Well I WILL." + +"Well why don't you DO it then? What do you keep SAYING you will for? +Why don't you DO it? It's because you're afraid." + +"I AIN'T afraid." + +"You are." + +"I ain't." + +"You are." + +Another pause, and more eying and sidling around each other. Presently +they were shoulder to shoulder. Tom said: + +"Get away from here!" + +"Go away yourself!" + +"I won't." + +"I won't either." + +So they stood, each with a foot placed at an angle as a brace, and +both shoving with might and main, and glowering at each other with +hate. But neither could get an advantage. After struggling till both +were hot and flushed, each relaxed his strain with watchful caution, +and Tom said: + +"You're a coward and a pup. I'll tell my big brother on you, and he +can thrash you with his little finger, and I'll make him do it, too." + +"What do I care for your big brother? I've got a brother that's bigger +than he is--and what's more, he can throw him over that fence, too." +[Both brothers were imaginary.] + +"That's a lie." + +"YOUR saying so don't make it so." + +Tom drew a line in the dust with his big toe, and said: + +"I dare you to step over that, and I'll lick you till you can't stand +up. Anybody that'll take a dare will steal sheep." + +The new boy stepped over promptly, and said: + +"Now you said you'd do it, now let's see you do it." + +"Don't you crowd me now; you better look out." + +"Well, you SAID you'd do it--why don't you do it?" + +"By jingo! for two cents I WILL do it." + +The new boy took two broad coppers out of his pocket and held them out +with derision. Tom struck them to the ground. In an instant both boys +were rolling and tumbling in the dirt, gripped together like cats; and +for the space of a minute they tugged and tore at each other's hair and +clothes, punched and scratched each other's nose, and covered +themselves with dust and glory. Presently the confusion took form, and +through the fog of battle Tom appeared, seated astride the new boy, and +pounding him with his fists. "Holler 'nuff!" said he. + +The boy only struggled to free himself. He was crying--mainly from rage. + +"Holler 'nuff!"--and the pounding went on. + +At last the stranger got out a smothered "'Nuff!" and Tom let him up +and said: + +"Now that'll learn you. Better look out who you're fooling with next +time." + +The new boy went off brushing the dust from his clothes, sobbing, +snuffling, and occasionally looking back and shaking his head and +threatening what he would do to Tom the "next time he caught him out." +To which Tom responded with jeers, and started off in high feather, and +as soon as his back was turned the new boy snatched up a stone, threw +it and hit him between the shoulders and then turned tail and ran like +an antelope. Tom chased the traitor home, and thus found out where he +lived. He then held a position at the gate for some time, daring the +enemy to come outside, but the enemy only made faces at him through the +window and declined. At last the enemy's mother appeared, and called +Tom a bad, vicious, vulgar child, and ordered him away. So he went +away; but he said he "'lowed" to "lay" for that boy. + +He got home pretty late that night, and when he climbed cautiously in +at the window, he uncovered an ambuscade, in the person of his aunt; +and when she saw the state his clothes were in her resolution to turn +his Saturday holiday into captivity at hard labor became adamantine in +its firmness. diff --git a/tests/data/Mark.Twain-Tom.Sawyer.txt.rawsnappy b/tests/data/Mark.Twain-Tom.Sawyer.txt.rawsnappy new file mode 100644 index 0000000000000000000000000000000000000000..9c56d985888e48a9967e187523f0e3326330aeca GIT binary patch literal 9871 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