├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── benchmarks-vs ├── bench.go └── benchropes_test.go ├── debug.go ├── fuzz.go ├── rope.go ├── rope_test.go ├── scanner.go ├── scanner_test.go ├── search.go ├── utils.go └── workdir └── corpus ├── 0568259c1f5af6f940d8e8fb108043b7cae330de-11 ├── 091cc257a5ae51cf93833ebcfe3e05b1e64f93f4-12 ├── 097d9474fad1260c2fc255f1048f45217f83297f-7 ├── 0d718dcf8e41e4ee45a351ab1c1f9ac600d6fd99-4 ├── 0fdde89ed21c31675c8ce4332da98e319832d6a7-1 ├── 1103f3ab5d513c5925aac770e97c71efbb9768f2-5 ├── 122c71c17966fe8ce7dcaee0af2168bdaba5fd7b-1 ├── 175635b48b4f360083fbfe58c45ec36802e2b649-9 ├── 1e14c927ca0d6d32de66e20a54a34998970fac2e ├── 285ba8efda8b0b67d9ea78286fd2ca997757f8fb ├── 29866502e4dbf89c5683936b0952f3119916fa7b-4 ├── 3478b37705bdadeba78b7882a5c39f99a0cb5593 ├── 359f58c40401b4022482dff0a1e3e0fdffab2b47-1 ├── 377d832f63f774e33440b2688798b7bcb7588d1b-4 ├── 3df130541f67f68ed7f4b19760a2dd8c22c8aed0-4 ├── 441d8d20000776834490ecd83f64ff95bff09ea0-12 ├── 4af2baaa0c7db252dac606712d08c13b1c921de1 ├── 4e50ef18669aa073868ad78a7b0873bb463e42bf-1 ├── 556816732436327a700a81916cd30aaebbdc082b ├── 58c08cb70b8049b531e7bd6912007223b73a9a31-3 ├── 5cafdbf3eb448ec2bc10f4b35f6b549d218f13d6-3 ├── 5ed88ff1226785b218e011f86a80d946efb1fcbb-10 ├── 5fb0483706289d4878f9fc32e6fe16909da0e2d2-5 ├── 61597000dfbb4eda6dc48b306ae24ee3b02f308c-9 ├── 64ec56cc01bdb7fde9f2cb1a7e4fa26f071abf0b-13 ├── 6b6652c231feb7cfba5124fb49ef0d38fa156498-11 ├── 6e138ca1b4c4e37902e261e5dcc68e2ebc79b3be-3 ├── 6e6c421a64df2b4768d6ee8c59840f6d00423039 ├── 7e003a29799b04f21c11d22a7116890240ce648f-7 ├── 7e5c0f7aba32cf3e22fd30c4513a21e6d1c3aeff-1 ├── 7f05e1cac5d7dc12e042dc83f3fcd10540786aa0-14 ├── 810a1da74529c06767b01440a72c6dc699980d12-2 ├── 843394c26a0b8a1afbc852a80de25c4344674e19-11 ├── 8652b884f91ad0b4fb9c96e91566338145317cd8-1 ├── 88e2ef96d27f9f5551d0f7121ea9fef541b0cc82-6 ├── 8a80c7b3fca0bb837b5878469c083e41346299e0-1 ├── 8e589bb71b429ff4ceab0984ffbd3ceeb6e85011-10 ├── 8fbe7e9cf9117f680e8b25007cbb801c1593684b-8 ├── 905388c50a25a80ea874f3328ba371639bbd4a2d ├── 90de8387e74919a87a88c0e411803250b21ae5d1-6 ├── 9b0357bd16c8e8bb12415f70b25fc7600fcd6c7e-1 ├── 9d52dc21f764e03cc5a7d24ca970cbe57458a350-11 ├── 9edc07b314e9edbe831b71deefd8819e311bd61d-12 ├── a15024b390071dabc44a3fcc8f0501356408a55c-10 ├── a5c373199f21b4c3b2d329314170cbcc9df11e1f-1 ├── aba83d00667befd41c1cddb7d272b353c183f249 ├── aefc80f9a2a2e1ec680d32a66fb4830140fd4666-1 ├── b25b0fbf46cef1d888fe900445c9ab95330f44cd-1 ├── b6198e1a8df05a0a71f65d22325857f57600df52-5 ├── c0483379477f8160f07c21f13d327febaa32370c ├── c0c60c25f055c947c193bd826651b56159d31590-13 ├── c1cd8e1a9f61b8bb6d2c7a0d16684fa319469ba6-2 ├── c3416d0c634908099dc74c3591d88c9ebce7a14e-13 ├── c70223b639d696326ab1966c74285edf16f1805d-6 ├── c753411f6386108552b2102db05df9cadd06d354-5 ├── cb4613e292d1d4f4b57728d921cc62961a9c3810 ├── cbaa4a2d9f5bf1416d3577fe06aedf1b79cef930-2 ├── cdb2c5cc0ed74b1c41ef5a49d984a6b20e616dac-1 ├── ce02a48292c4329435c60da7079db829fa1eeff7 ├── cf3cb205ea5db39c17ae0ca20ee91c882e187f96-1 ├── d2b8a3d2cd0248f9e35014bfe4f598b6c4e45d7f ├── d62fa5c1bd59beb6725232e3bc15b76c0b9f157e-11 ├── d8b276720792805e0add87fa45856abafa51f9cd-7 ├── da39a3ee5e6b4b0d3255bfef95601890afd80709-1 ├── da3d70c98f1e4af8abebfb6256f2846b91a2e454-5 ├── ddad8f6b96ce003a350ae1b7e42c507566f64625-2 ├── df68bdb3d0695420cde7c8475c49f41e440a8316-4 ├── e07be16e70a053e5fc0dcb2ad6b10fece26abb24-12 ├── eb99a12e7859cd3fe2ba913d4ea7fbbec35a0479 ├── f58c0efc6fc3f16130bb0e593d3175a98f67df54-5 ├── f7910ec2fa0c176e315ed1c2812c44da457b1f7f-3 ├── fab43624b500191304bc9ee2571a135fd5e67b26-7 ├── hello.txt ├── terminal_injection.txt └── upsidedown.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Fuzz files not to commit 17 | workdir/crashers 18 | workdir/suppressions 19 | skiprope-fuzz.zip 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | branches: 4 | only: 5 | - master 6 | 7 | go: 8 | - 1.5.x 9 | - 1.6.x 10 | - 1.7.x 11 | - 1.8.x 12 | - 1.9.x 13 | - tip 14 | 15 | env: 16 | global: 17 | - GOARCH=amd64 18 | - TRAVISTEST=true 19 | 20 | before_install: 21 | - go get github.com/mattn/goveralls 22 | 23 | script: 24 | - go test -run=. -coverprofile=profile.cov 25 | - $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci 26 | 27 | matrix: 28 | allow_failures: 29 | - go: tip -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing # 2 | 3 | Please discuss the changes via email, or the github issues pages before making a change. Please follow the code of conduct in your interactions with this project. Thank you. 4 | 5 | # Pull Request Process # 6 | 7 | 1. Ensure the `Gopkg.toml` has been updated if any new dependencies have been added. 8 | 2. Ensure that there are tests for the new code. 9 | 3. Ensure that the old tests don't fail. 10 | 4. Send a pull request. 11 | 5. I'll review it, and merge it or come back with comments for change. 12 | 6. After it's been approved, I'll add your name to the list of contributors (see CONTRIBUTORS.md) 13 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/davecgh/go-spew" 6 | packages = ["spew"] 7 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 8 | version = "v1.1.0" 9 | 10 | [[projects]] 11 | branch = "master" 12 | name = "github.com/eugene-eeo/rope" 13 | packages = ["."] 14 | revision = "2cbd7df3d4823adb26070dbbff3934e6ef5d19c9" 15 | 16 | [[projects]] 17 | name = "github.com/pmezard/go-difflib" 18 | packages = ["difflib"] 19 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 20 | version = "v1.0.0" 21 | 22 | [[projects]] 23 | name = "github.com/stretchr/testify" 24 | packages = ["assert"] 25 | revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" 26 | version = "v1.1.4" 27 | 28 | [solve-meta] 29 | analyzer-name = "dep" 30 | analyzer-version = 1 31 | inputs-digest = "051c144b599ce33663715bbcf4f94d2954ac52e84e697621377d47c8d4ecbf93" 32 | solver-name = "gps-cdcl" 33 | solver-version = 1 34 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | branch = "master" 26 | name = "github.com/eugene-eeo/rope" 27 | 28 | [[constraint]] 29 | name = "github.com/stretchr/testify" 30 | version = "1.1.4" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chewxy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkipRope [![GoDoc](https://godoc.org/github.com/chewxy/skiprope?status.svg)](https://godoc.org/github.com/chewxy/skiprope) [![Build Status](https://travis-ci.org/chewxy/skiprope.svg?branch=master)](https://travis-ci.org/chewxy/skiprope) [![Coverage Status](https://coveralls.io/repos/github/chewxy/skiprope/badge.svg?branch=master)](https://coveralls.io/github/chewxy/skiprope?branch=master) # 2 | 3 | package `skiprope` is an implementation of the [rope](https://en.wikipedia.org/wiki/Rope_%28data_structure%29) data structure. It's not strictly a rope as per se. It's not built on top of a binary tree like most rope data structures are. 4 | 5 | Instead it's built on top of a [skip list](https://en.wikipedia.org/wiki/Skip_list). This makes it more akin to a piece table than a true rope. 6 | 7 | This library is quite complete as it is (it has all the functions and I need for my projects). However, any addition, corrections etc are welcome. Please see CONTRIBUTING.md for more information on how to contribute. 8 | 9 | # Installing # 10 | 11 | This package is go-gettable: `go get -u github.com/chewxy/skiprope`. 12 | 13 | This package is versioned with SemVer 2.0, and does not have any external dependencies except for testing (of which the fantastic [`assert`](https://github.com/stretchr/testify) package is used). 14 | 15 | # Usage # 16 | 17 | ``` 18 | r := skiprope.New() 19 | if err := r.Insert(0, "Hello World! Hello, 世界"); err != nil { 20 | // handle error 21 | } 22 | if err := r.Insert(5, "there "); err != nil { 23 | // handle error 24 | } 25 | fmt.Println(r.String()) // "Hello there World! Hello, 世界" 26 | ``` 27 | 28 | # FAQ # 29 | 30 | ## How do I keep track of row, col information? ## 31 | 32 | The best way is to use a linebuffer data structure in conjunction with the `Rope`: 33 | 34 | ``` 35 | type Foo struct{ 36 | *skiprope.Rope 37 | linebuf []int 38 | } 39 | ``` 40 | 41 | The `linebuf` is essentially a list of offsets to a newline character. The main reason why I didn't add a `linebuf` slice into the `*Rope` data structure is because while it's a common thing to do, for a couple of my projects I didn't need it. The nature of Go's composable data structure and programs make it quite easy to add these things. A pro-tip is to extend the `Insert`, `InsertRunes` and `EraseAt` methods. 42 | 43 | I do not think it to be wise to add it to the core data structure. 44 | 45 | ## When is `*Rope` going to implement `io.Writer` and `io.Reader`? ## 46 | 47 | The main reason why I didn't do it was mostly because I didn't need it. However, I've been asked about this before. I personally don't have the bandwidth to do it. 48 | 49 | Please send a pull request :) 50 | 51 | # Benchmarks # 52 | 53 | There is a benchmark mini-program that is not required for the running, but here are the results: 54 | 55 | ``` 56 | go test -run=^$ -bench=. -benchmem -cpuprofile=test.prof 57 | BenchmarkNaiveRandomInsert-8 50000 37774 ns/op 15 B/op 0 allocs/op 58 | BenchmarkRopeRandomInsert-8 3000000 407 ns/op 27 B/op 0 allocs/op 59 | BenchmarkERopeRandomInsert-8 1000000 2143 ns/op 1161 B/op 25 allocs/op 60 | PASS 61 | ``` 62 | 63 | # Fuzz testing 64 | 65 | Guard against crashing for certain inputs by running fuzz tests. First, build the instrumented binaries for the tests and then run them. 66 | 67 | ``` 68 | $ go-fuzz-build github.com/chewxy/skiprope 69 | $ go-fuzz -bin=skiprope-fuzz.zip -workdir=workdir 70 | ``` 71 | 72 | # History, Development and Acknowledgements # 73 | This package started its life as a textbook binary-tree data structure for another personal project of mine. Over time, I decided to add more and more optimizations to it. The original design had a split at every new-line character. 74 | 75 | I started by moving more and more things off the heap, and onto the stack. As I wondered how to incorporate a search structure using a skiplist, I stumbled onto a well developed library for C, [librope](https://github.com/josephg/librope). 76 | 77 | It had everything I had wanted: a rope-like structure, using skiplists to find nodes, minimal allocations on heap, and a solution to my problem wrt keeping the skiplists off heap. The solution turned out to be to be the `skiplist` data structure, without a pointer. So I ended up adapting most of Joseph Gentle's algorithm. So this library owes most of its work to Joseph Gentle. 78 | 79 | Hours before releasing this library, I had a consult by Egon Elbre, who gave good advice on whether just sticking with `[]rune` was a good idea. He managed to convince me that it isn't, so the first pull request was made to update this library to deal with `[]byte` instead. As a result, memory use went down 40B/op at the cost of an increase of about 30 ns/op. The number can be further shaved down with better optimizations. 80 | -------------------------------------------------------------------------------- /benchmarks-vs/bench.go: -------------------------------------------------------------------------------- 1 | package benchropes 2 | 3 | import ( 4 | "bytes" 5 | "unicode/utf8" 6 | 7 | er "github.com/eugene-eeo/rope" 8 | ) 9 | 10 | type eugenerope struct { 11 | er.Rope 12 | } 13 | 14 | func (e *eugenerope) Insert(at int, str string) error { 15 | if at == 0 { 16 | pre := er.L(str) 17 | e.Rope = pre.Concat(e.Rope) 18 | return nil 19 | } 20 | if at >= e.Length() { 21 | post := er.L(str) 22 | e.Rope = e.Rope.Concat(post) 23 | return nil 24 | } 25 | i, j := e.SplitAt(at) 26 | insert := er.L(str) 27 | e.Rope = er.Concat(i, insert, j) 28 | return nil 29 | } 30 | 31 | func (e *eugenerope) EraseAt(at, n int) error { 32 | i, j := e.SplitAt(at) 33 | _, j = j.SplitAt(n) 34 | e.Rope = i.Concat(j) 35 | return nil 36 | } 37 | 38 | type naivebuffer struct { 39 | *bytes.Buffer 40 | } 41 | 42 | func (b *naivebuffer) Insert(at int, str string) error { 43 | // ensure we alwys have space 44 | if b.Cap() < b.Len()+len(str) { 45 | b.Grow(len(str)) 46 | } 47 | bs := []byte(str) 48 | b.Write(bs) // just to add to the length 49 | 50 | bb := b.Bytes() 51 | offset := byteOffset(bb, at) 52 | copy(bb[offset+len(str):], bb[offset:]) 53 | copy(bb[offset:offset+len(str)], bs) 54 | return nil 55 | } 56 | 57 | func (b *naivebuffer) EraseAt(at, n int) error { 58 | bb := b.Bytes() 59 | offset := byteOffset(bb, at) 60 | copy(bb[offset:], bb[offset+n:]) 61 | b.Truncate(len(bb) - n) 62 | return nil 63 | } 64 | 65 | // byteOffset takes a slice of bytes, and returns the index at which the expected number of runes there is 66 | func byteOffset(a []byte, runes int) (offset int) { 67 | if runes == 0 { 68 | return 0 69 | } 70 | 71 | var runeCount int 72 | for _, size := utf8.DecodeRune(a[offset:]); offset < len(a) && runeCount < runes; _, size = utf8.DecodeRune(a[offset:]) { 73 | offset += size 74 | runeCount++ 75 | } 76 | return offset 77 | } 78 | -------------------------------------------------------------------------------- /benchmarks-vs/benchropes_test.go: -------------------------------------------------------------------------------- 1 | package benchropes 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | rope "github.com/chewxy/skiprope" 11 | er "github.com/eugene-eeo/rope" 12 | ) 13 | 14 | const a = `Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, ‘and what is the use of a book,’ thought Alice ‘without pictures or conversations?’ 15 | 16 | So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her. 17 | 18 | There was nothing so very remarkable in that; nor did Alice think it so very much out of the way to hear the Rabbit say to itself, ‘Oh dear! Oh dear! I shall be late!’ (when she thought it over afterwards, it occurred to her that she ought to have wondered at this, but at the time it all seemed quite natural); but when the Rabbit actually took a watch out of its waistcoat-pocket, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it, and fortunately was just in time to see it pop down a large rabbit-hole under the hedge. 19 | 20 | In another moment down went Alice after it, never once considering how in the world she was to get out again. 21 | 22 | The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down a very deep well. 23 | 24 | Either the well was very deep, or she fell very slowly, for she had plenty of time as she went down to look about her and to wonder what was going to happen next. First, she tried to look down and make out what she was coming to, but it was too dark to see anything; then she looked at the sides of the well, and noticed that they were filled with cupboards and book-shelves; here and there she saw maps and pictures hung upon pegs. She took down a jar from one of the shelves as she passed; it was labelled ‘ORANGE MARMALADE’, but to her great disappointment it was empty: she did not like to drop the jar for fear of killing somebody, so managed to put it into one of the cupboards as she fell past it. 25 | 26 | ‘Well!’ thought Alice to herself, ‘after such a fall as this, I shall think nothing of tumbling down stairs! How brave they’ll all think me at home! Why, I wouldn’t say anything about it, even if I fell off the top of the house!’ (Which was very likely true.) 27 | 28 | Down, down, down. Would the fall never come to an end! ‘I wonder how many miles I’ve fallen by this time?’ she said aloud. ‘I must be getting somewhere near the centre of the earth. Let me see: that would be four thousand miles down, I think—’ (for, you see, Alice had learnt several things of this sort in her lessons in the schoolroom, and though this was not a very good opportunity for showing off her knowledge, as there was no one to listen to her, still it was good practice to say it over) ‘—yes, that’s about the right distance—but then I wonder what Latitude or Longitude I’ve got to?’ (Alice had no idea what Latitude was, or Longitude either, but thought they were nice grand words to say.) 29 | 30 | Presently she began again. ‘I wonder if I shall fall right through the earth! How funny it’ll seem to come out among the people that walk with their heads downward! The Antipathies, I think—’ (she was rather glad there was no one listening, this time, as it didn’t sound at all the right word) ‘—but I shall have to ask them what the name of the country is, you know. Please, Ma’am, is this New Zealand or Australia?’ (and she tried to curtsey as she spoke—fancy curtseying as you’re falling through the air! Do you think you could manage it?) ‘And what an ignorant little girl she’ll think me for asking! No, it’ll never do to ask: perhaps I shall see it written up somewhere.’ 31 | 32 | Down, down, down. There was nothing else to do, so Alice soon began talking again. ‘Dinah’ll miss me very much to-night, I should think!’ (Dinah was the cat.) ‘I hope they’ll remember her saucer of milk at tea-time. Dinah my dear! I wish you were down here with me! There are no mice in the air, I’m afraid, but you might catch a bat, and that’s very like a mouse, you know. But do cats eat bats, I wonder?’ And here Alice began to get rather sleepy, and went on saying to herself, in a dreamy sort of way, ‘Do cats eat bats? Do cats eat bats?’ and sometimes, ‘Do bats eat cats?’ for, you see, as she couldn’t answer either question, it didn’t much matter which way she put it. She felt that she was dozing off, and had just begun to dream that she was walking hand in hand with Dinah, and saying to her very earnestly, ‘Now, Dinah, tell me the truth: did you ever eat a bat?’ when suddenly, thump! thump! down she came upon a heap of sticks and dry leaves, and the fall was over. 33 | 34 | Alice was not a bit hurt, and she jumped up on to her feet in a moment: she looked up, but it was all dark overhead; before her was another long passage, and the White Rabbit was still in sight, hurrying down it. There was not a moment to be lost: away went Alice like the wind, and was just in time to hear it say, as it turned a corner, ‘Oh my ears and whiskers, how late it’s getting!’ She was close behind it when she turned the corner, but the Rabbit was no longer to be seen: she found herself in a long, low hall, which was lit up by a row of lamps hanging from the roof. 35 | 36 | There were doors all round the hall, but they were all locked; and when Alice had been all the way down one side and up the other, trying every door, she walked sadly down the middle, wondering how she was ever to get out again. 37 | 38 | Suddenly she came upon a little three-legged table, all made of solid glass; there was nothing on it except a tiny golden key, and Alice’s first thought was that it might belong to one of the doors of the hall; but, alas! either the locks were too large, or the key was too small, but at any rate it would not open any of them. However, on the second time round, she came upon a low curtain she had not noticed before, and behind it was a little door about fifteen inches high: she tried the little golden key in the lock, and to her great delight it fitted! 39 | 40 | Alice opened the door and found that it led into a small passage, not much larger than a rat-hole: she knelt down and looked along the passage into the loveliest garden you ever saw. How she longed to get out of that dark hall, and wander about among those beds of bright flowers and those cool fountains, but she could not even get her head through the doorway; ‘and even if my head would go through,’ thought poor Alice, ‘it would be of very little use without my shoulders. Oh, how I wish I could shut up like a telescope! I think I could, if I only knew how to begin.’ For, you see, so many out-of-the-way things had happened lately, that Alice had begun to think that very few things indeed were really impossible. 41 | 42 | There seemed to be no use in waiting by the little door, so she went back to the table, half hoping she might find another key on it, or at any rate a book of rules for shutting people up like telescopes: this time she found a little bottle on it, (‘which certainly was not here before,’ said Alice,) and round the neck of the bottle was a paper label, with the words ‘DRINK ME’ beautifully printed on it in large letters. 43 | 44 | It was all very well to say ‘Drink me,’ but the wise little Alice was not going to do that in a hurry. ‘No, I’ll look first,’ she said, ‘and see whether it’s marked “poison” or not’; for she had read several nice little histories about children who had got burnt, and eaten up by wild beasts and other unpleasant things, all because they would not remember the simple rules their friends had taught them: such as, that a red-hot poker will burn you if you hold it too long; and that if you cut your finger very deeply with a knife, it usually bleeds; and she had never forgotten that, if you drink much from a bottle marked ‘poison,’ it is almost certain to disagree with you, sooner or later. 45 | 46 | However, this bottle was not marked ‘poison,’ so Alice ventured to taste it, and finding it very nice, (it had, in fact, a sort of mixed flavour of cherry-tart, custard, pine-apple, roast turkey, toffee, and hot buttered toast,) she very soon finished it off. 47 | 48 | * * * * * * * 49 | 50 | * * * * * * 51 | 52 | * * * * * * * 53 | 54 | ‘What a curious feeling!’ said Alice; ‘I must be shutting up like a telescope.’ 55 | 56 | And so it was indeed: she was now only ten inches high, and her face brightened up at the thought that she was now the right size for going through the little door into that lovely garden. First, however, she waited for a few minutes to see if she was going to shrink any further: she felt a little nervous about this; ‘for it might end, you know,’ said Alice to herself, ‘in my going out altogether, like a candle. I wonder what I should be like then?’ And she tried to fancy what the flame of a candle is like after the candle is blown out, for she could not remember ever having seen such a thing. 57 | 58 | After a while, finding that nothing more happened, she decided on going into the garden at once; but, alas for poor Alice! when she got to the door, she found she had forgotten the little golden key, and when she went back to the table for it, she found she could not possibly reach it: she could see it quite plainly through the glass, and she tried her best to climb up one of the legs of the table, but it was too slippery; and when she had tired herself out with trying, the poor little thing sat down and cried. 59 | 60 | ‘Come, there’s no use in crying like that!’ said Alice to herself, rather sharply; ‘I advise you to leave off this minute!’ She generally gave herself very good advice, (though she very seldom followed it), and sometimes she scolded herself so severely as to bring tears into her eyes; and once she remembered trying to box her own ears for having cheated herself in a game of croquet she was playing against herself, for this curious child was very fond of pretending to be two people. ‘But it’s no use now,’ thought poor Alice, ‘to pretend to be two people! Why, there’s hardly enough of me left to make one respectable person!’ 61 | 62 | Soon her eye fell on a little glass box that was lying under the table: she opened it, and found in it a very small cake, on which the words ‘EAT ME’ were beautifully marked in currants. ‘Well, I’ll eat it,’ said Alice, ‘and if it makes me grow larger, I can reach the key; and if it makes me grow smaller, I can creep under the door; so either way I’ll get into the garden, and I don’t care which happens!’ 63 | 64 | She ate a little bit, and said anxiously to herself, ‘Which way? Which way?’, holding her hand on the top of her head to feel which way it was growing, and she was quite surprised to find that she remained the same size: to be sure, this generally happens when one eats cake, but Alice had got so much into the way of expecting nothing but out-of-the-way things to happen, that it seemed quite dull and stupid for life to go on in the common way. 65 | 66 | So she set to work, and very soon finished off the cake. 67 | ` 68 | 69 | var randoms []int 70 | var randWords []string 71 | 72 | var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) 73 | 74 | func init() { 75 | rs := []rune(a) 76 | randoms = make([]int, 0, 40000000) 77 | 78 | for i := 0; i < 40000000; i++ { 79 | randoms = append(randoms, rnd.Intn(len(rs)-1)) 80 | } 81 | randWords = strings.Fields(a) 82 | } 83 | 84 | func BenchmarkNaiveRandomInsert(b *testing.B) { 85 | buf := &naivebuffer{bytes.NewBuffer([]byte(a))} 86 | var randTrack int 87 | b.ResetTimer() 88 | for i := 0; i < b.N; i++ { 89 | at := randoms[randTrack] 90 | word := randWords[rand.Intn(len(randWords))] 91 | rs := []rune(word) 92 | buf.Insert(at, word) 93 | 94 | if i%25 == 0 { 95 | randTrack++ 96 | if randTrack > len(randoms) { 97 | randTrack = 0 98 | } 99 | for at = randoms[randTrack]; at >= buf.Len(); at = randoms[randTrack] { 100 | randTrack++ 101 | if randTrack > len(randoms) { 102 | randTrack = 0 103 | } 104 | } 105 | buf.EraseAt(at, len(rs)) 106 | } 107 | randTrack++ 108 | if randTrack > len(randoms) { 109 | randTrack = 0 110 | } 111 | } 112 | } 113 | 114 | func BenchmarkRopeRandomInsert(b *testing.B) { 115 | r := rope.New() 116 | r.Insert(0, a) 117 | 118 | b.ResetTimer() 119 | var randTrack int 120 | for i := 0; i < b.N; i++ { 121 | at := randoms[randTrack] 122 | word := randWords[rand.Intn(len(randWords))] 123 | rs := []rune(word) 124 | // r.InsertRunes(at, rs) 125 | r.Insert(at, word) 126 | if i%25 == 0 { 127 | randTrack++ 128 | if randTrack >= len(randoms) { 129 | randTrack = 0 130 | } 131 | at = randoms[randTrack] 132 | r.EraseAt(at, len(rs)) 133 | } 134 | randTrack++ 135 | if randTrack >= len(randoms) { 136 | randTrack = 0 137 | } 138 | } 139 | } 140 | 141 | func BenchmarkERopeRandomInsert(b *testing.B) { 142 | r := eugenerope{er.L(a)} 143 | b.ResetTimer() 144 | 145 | var randTrack int 146 | for i := 0; i < b.N; i++ { 147 | at := randoms[randTrack] 148 | word := randWords[rand.Intn(len(randWords))] 149 | rs := []rune(word) 150 | r.Insert(at, word) 151 | 152 | if i%25 == 0 { 153 | randTrack++ 154 | if randTrack >= len(randoms) { 155 | randTrack = 0 156 | } 157 | at = randoms[randTrack] 158 | r.EraseAt(at, len(rs)) 159 | } 160 | 161 | randTrack++ 162 | if randTrack >= len(randoms) { 163 | randTrack = 0 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | // +build debug 2 | 3 | package skiprope 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | "sync/atomic" 11 | ) 12 | 13 | const DEBUG = true 14 | 15 | var TABCOUNT uint32 16 | 17 | var TRACK = false 18 | 19 | var _logger_ = log.New(os.Stderr, "", 0) 20 | var replacement = "\n" 21 | 22 | func tabcount() int { 23 | return int(atomic.LoadUint32(&TABCOUNT)) 24 | } 25 | 26 | func enterLoggingContext() { 27 | atomic.AddUint32(&TABCOUNT, 1) 28 | tabcount := tabcount() 29 | _logger_.SetPrefix(strings.Repeat("\t", tabcount)) 30 | replacement = "\n" + strings.Repeat("\t", tabcount) 31 | } 32 | 33 | func leaveLoggingContext() { 34 | tabcount := tabcount() 35 | tabcount-- 36 | 37 | if tabcount < 0 { 38 | atomic.StoreUint32(&TABCOUNT, 0) 39 | tabcount = 0 40 | } else { 41 | atomic.StoreUint32(&TABCOUNT, uint32(tabcount)) 42 | } 43 | _logger_.SetPrefix(strings.Repeat("\t", tabcount)) 44 | replacement = "\n" + strings.Repeat("\t", tabcount) 45 | } 46 | 47 | func logf(format string, others ...interface{}) { 48 | if DEBUG { 49 | // format = strings.Replace(format, "\n", replacement, -1) 50 | s := fmt.Sprintf(format, others...) 51 | s = strings.Replace(s, "\n", replacement, -1) 52 | _logger_.Println(s) 53 | // _logger_.Printf(format, others...) 54 | } 55 | } 56 | 57 | func (k knot) GoString() string { 58 | return fmt.Sprintf("Data: %q | (Height %d, Used %d) | %v", string(k.data[:]), k.height, k.used, k.nexts) 59 | } 60 | 61 | func (s skipknot) GoString() string { 62 | return fmt.Sprintf("%#v | skipped %v", s.knot, s.skipped) 63 | } 64 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package skiprope 4 | 5 | import ( 6 | "os" 7 | "unicode/utf8" 8 | ) 9 | 10 | func Fuzz(data []byte) int { 11 | r := New() 12 | if err := r.Insert(0, string(data)); err != nil { 13 | return 0 14 | } 15 | rb := New() 16 | if err := rb.InsertBytes(0, data); err != nil { 17 | return 0 18 | } 19 | 20 | if r.String() != rb.String() { 21 | println("Insert and InsertBytes did not insert equally") 22 | println(r.String()) 23 | println(rb.String()) 24 | os.Exit(1) 25 | } 26 | 27 | runeData := []rune{} 28 | for i, w := 0, 0; i < len(data); i += w { 29 | runeValue, width := utf8.DecodeRune(data[i:]) 30 | runeData = append(runeData, runeValue) 31 | w = width 32 | } 33 | 34 | rr := New() 35 | if err := rr.InsertRunes(0, runeData); err != nil { 36 | return 0 37 | } 38 | 39 | // Insert not at = 0 40 | if err := rb.InsertBytes(len(data)/3, data); err != nil { 41 | return 0 42 | } 43 | _ = rb.String() 44 | 45 | // Before 46 | _, _, err := rb.Before(len(data)/2, func(r rune) bool { return true }) 47 | if err != nil { 48 | return 0 49 | } 50 | _, _, err = rb.Before(len(data)/2, func(r rune) bool { return false }) 51 | if err != nil { 52 | return 0 53 | } 54 | _, _, err = rb.Before(len(data)/2, func(r rune) bool { return r == '0' }) 55 | if err != nil { 56 | return 0 57 | } 58 | 59 | // EraseAt 60 | eraser := New() 61 | if err := eraser.InsertBytes(0, data); err != nil { 62 | return 0 63 | } 64 | if err := eraser.EraseAt(len(data)/2, len(data)/3); err != nil { 65 | return 0 66 | } 67 | 68 | // Index 69 | indexer := New() 70 | if err := indexer.InsertBytes(0, data); err != nil { 71 | return 0 72 | } 73 | indexer.Index(len(data) / 2) 74 | 75 | // Runes 76 | runer := New() 77 | if err := runer.InsertBytes(0, data); err != nil { 78 | return 0 79 | } 80 | runer.Runes() 81 | 82 | // Size 83 | sizer := New() 84 | if err := sizer.InsertBytes(0, data); err != nil { 85 | return 0 86 | } 87 | sizer.Size() 88 | 89 | // Substr 90 | substrer := New() 91 | if err := substrer.InsertBytes(0, data); err != nil { 92 | return 0 93 | } 94 | substrer.Substr(0, len(data)/3) 95 | 96 | // SubstrRunes 97 | substrRuner := New() 98 | if err := substrRuner.InsertBytes(0, data); err != nil { 99 | return 0 100 | } 101 | substrRuner.SubstrRunes(0, len(data)/3) 102 | 103 | // SubstrBytes 104 | substrByter := New() 105 | if err := substrByter.InsertBytes(0, data); err != nil { 106 | return 0 107 | } 108 | substrByter.SubstrBytes(0, len(data)/3) 109 | 110 | return 1 111 | } 112 | -------------------------------------------------------------------------------- /rope.go: -------------------------------------------------------------------------------- 1 | // package skiprope provides rope-like data structure for efficient manipulation of large strings. 2 | package skiprope 3 | 4 | import ( 5 | "unicode/utf8" 6 | ) 7 | 8 | const ( 9 | MaxHeight = 60 // maximum size of the skiplist 10 | BucketSize = 64 // data bucket size in a knot - about 64 bytes is optimal for insertion on a core i7. 11 | ) 12 | 13 | // Bias indicates the probability that a new knot will have height of n+1. 14 | // This is the parameter to tweak when considering the tradeoff between high amounts of append operations 15 | // and amount of random writes. 16 | // 17 | // The higher the bias is, the better the data structure is at performing end-of-string appends. The tradeoff is 18 | // performance of random writes will deterioriate. 19 | var Bias = 20 20 | 21 | // Rope is a rope data structure built on top of a skip list. 22 | type Rope struct { 23 | Head knot 24 | size int // number of bytes 25 | runes int // number of code points 26 | } 27 | 28 | // knot is a node in a rope.... because... geddit? 29 | type knot struct { 30 | data [BucketSize]byte // array is preallocated - 64*4 bytes used 31 | nexts []skipknot // next 32 | height int // number of elements located in nexts. Minium height is 1 33 | used int // indicates how many byte are used in data 34 | } 35 | 36 | func newKnot(height int) *knot { 37 | return &knot{ 38 | height: height, 39 | nexts: make([]skipknot, height), 40 | } 41 | } 42 | 43 | type skipknot struct { 44 | *knot 45 | skipped int // number of bytes between the start and current node and the start of the next 46 | skippedRunes int // number of runes between the start and current node and the start of the next 47 | } 48 | 49 | // New creates a new Rope. 50 | func New() *Rope { 51 | var r Rope 52 | Init(&r) 53 | return &r 54 | } 55 | 56 | // Init re-initializes the rope 57 | func Init(r *Rope) { 58 | r.Head = knot{ 59 | height: 1, 60 | nexts: make([]skipknot, MaxHeight), 61 | } 62 | } 63 | 64 | // Size is the length of the rope. 65 | func (r *Rope) Size() int { return r.size } 66 | 67 | // Runes is the number of runes in the rope 68 | func (r *Rope) Runes() int { return r.runes } 69 | 70 | // SubstrRunes is like Substr, but returns []rune 71 | func (r *Rope) SubstrRunes(pointA, pointB int) []rune { 72 | return []rune(string(r.SubstrBytes(pointA, pointB))) 73 | } 74 | 75 | // SubstrBytes returns a byte slice given the "substring" of the rope. 76 | // Both pointA and pointB refers to the rune, not the byte offset. 77 | // 78 | // Example: "你好world" has a length of 11 bytes. If we only want "好", 79 | // we'd have to call SubstrBytes(1, 2), not SubstrBytes(3, 6) which you would if you 80 | // were dealing with pure bytes 81 | func (r *Rope) SubstrBytes(pointA, pointB int) []byte { 82 | lastPos := r.runes 83 | a := clamp(min(pointA, pointB), 0, lastPos) 84 | b := clamp(max(pointB, pointB), 0, lastPos) 85 | 86 | size := b - a 87 | if size == 0 { 88 | return nil 89 | } 90 | s := skiplist{r: r} 91 | var k1, k2 *knot 92 | var start, end, retOffset, startSkipped, endSkipped int 93 | var err error 94 | if k1, start, startSkipped, err = s.find(a); err != nil { 95 | panic(err) 96 | } 97 | if k2, end, endSkipped, err = s.find(b); err != nil { 98 | panic(err) 99 | } 100 | 101 | retVal := make([]byte, 0, (endSkipped+end)-(start+startSkipped)) 102 | ds := start // data start 103 | for n := k1; n != nil; n = n.nexts[0].knot { 104 | de := n.used // data end 105 | // last block 106 | if n == k2 { 107 | de = end 108 | } 109 | 110 | // copy(retVal[retOffset:], n.data[ds:de]) 111 | retVal = append(retVal, n.data[ds:de]...) 112 | retOffset += (de - ds) 113 | if n == k1 { 114 | ds = 0 115 | } 116 | if n == k2 { 117 | break 118 | } 119 | } 120 | return retVal 121 | } 122 | 123 | // Substr creates a substring. 124 | func (r *Rope) Substr(pointA, pointB int) string { 125 | return string(r.SubstrBytes(pointA, pointB)) 126 | } 127 | 128 | // InsertRunes inserts the runes at the point 129 | func (r *Rope) InsertRunes(point int, data []rune) (err error) { 130 | return r.InsertBytes(point, []byte(string(data))) 131 | } 132 | 133 | // InsertBytes inserts the bytes at the point. 134 | func (r *Rope) InsertBytes(point int, data []byte) (err error) { 135 | if point > r.runes { 136 | point = r.runes 137 | } 138 | 139 | // search for the Knot where we'll insert 140 | var k *knot 141 | s := skiplist{r: r} 142 | if k, err = s.find2(point); err != nil { 143 | return err 144 | } 145 | return s.insert(k, data) 146 | } 147 | 148 | // Insert inserts the string at the point 149 | func (r *Rope) Insert(at int, str string) error { 150 | return r.InsertBytes(at, []byte(str)) 151 | } 152 | 153 | // EraseAt erases n runes starting from the point. 154 | func (r *Rope) EraseAt(point, n int) (err error) { 155 | if point > r.runes { 156 | point = r.runes 157 | } 158 | if n >= r.runes-point { 159 | n = r.runes - point 160 | } 161 | var k *knot 162 | s := skiplist{r: r} 163 | if k, err = s.find2(point); err != nil { 164 | return err 165 | } 166 | s.del(k, n) 167 | return nil 168 | } 169 | 170 | // Index returns the rune at the given index. 171 | func (r *Rope) Index(at int) rune { 172 | s := skiplist{r: r} 173 | var k *knot 174 | var offset int 175 | var err error 176 | 177 | if k, offset, _, err = s.find(at); err != nil { 178 | return -1 179 | } 180 | if offset == BucketSize { 181 | char, _ := utf8.DecodeRune(k.nexts[0].data[0:]) 182 | return char 183 | } 184 | char, _ := utf8.DecodeRune(k.data[offset:]) 185 | return char 186 | } 187 | 188 | // ByteOffset returns the byte offset of a rune at the given point. 189 | func (r *Rope) ByteOffset(at int) int { 190 | s := skiplist{r: r} 191 | // var k *knot 192 | var offset, skippedBytes int 193 | var err error 194 | 195 | if _, offset, skippedBytes, err = s.find(at); err != nil { 196 | return -1 197 | } 198 | 199 | return offset + skippedBytes 200 | } 201 | 202 | // String returns the rope as a full string. 203 | func (r *Rope) String() string { 204 | return r.Substr(0, r.runes) 205 | } 206 | 207 | // Before returns the index of the first rune that matches the function before the given point. 208 | // 209 | // Example: "Hello World". Let's say `at` is at 9 (rune = r). And we want to find the whitespace before it. 210 | // This function will return 5, which is the byte index of the rune immediately after the whitespace. 211 | func (r *Rope) Before(at int, fn func(r rune) bool) (retVal int, retRune rune, err error) { 212 | s := skiplist{r: r} 213 | var k, prev *knot 214 | var offset int 215 | var char rune 216 | 217 | if k, offset, _, err = s.find(at); err != nil { 218 | return -1, -1, err 219 | } 220 | 221 | char, _ = utf8.DecodeRune(k.data[offset:]) 222 | if fn(char) { 223 | return at, char, nil 224 | } 225 | 226 | // if it's within this block, then return immediately, otherwise, get previous block 227 | var befores, start int 228 | size := 1 229 | for end := len(k.data[:offset]); end > start; end -= size { 230 | char, size = utf8.DecodeLastRune(k.data[start:end]) 231 | if fn(char) { 232 | return at - befores, char, nil 233 | } 234 | befores += size 235 | } 236 | 237 | // otherwise we'd have to iterate thru the blocks 238 | befores = offset + 1 239 | for { 240 | for it := &r.Head; it != nil; it = it.nexts[0].knot { 241 | if it == k { 242 | // if prev == nil { 243 | // prev = it 244 | // } 245 | break 246 | } 247 | prev = it 248 | } 249 | start = 0 250 | size = 1 251 | for end := len(prev.data) - 1; end > start; end -= size { 252 | char, size = utf8.DecodeLastRune(prev.data[start:end]) 253 | if fn(char) { 254 | return at - befores, char, nil 255 | } 256 | befores += size 257 | } 258 | k = prev 259 | // if k == &r.Head { 260 | // break 261 | // } 262 | } 263 | return 264 | } 265 | 266 | // Write implements the io.Writer interface for a Rope. Existing contents of the Rope will be erased. 267 | func (r *Rope) Write(p []byte) (int, error) { 268 | err := r.Insert(r.runes, string(p)) 269 | if err != nil { 270 | return 0, err 271 | } 272 | 273 | return len(p), nil 274 | } 275 | -------------------------------------------------------------------------------- /rope_test.go: -------------------------------------------------------------------------------- 1 | package skiprope 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | "testing/quick" 8 | "unicode" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func validRope(t *testing.T, r *Rope) { 14 | assert := assert.New(t) 15 | assert.Condition(func() bool { return r.Head.height >= 1 }, "Height has to be greater than 1") 16 | 17 | last := r.Head.nexts[r.Head.height-1] 18 | assert.Equal(r.size, last.skipped, "Expect to have skipped %d for the last element on the skiplist", r.size) 19 | assert.Nil(last.knot, "Last Knot not nil") 20 | 21 | var runeCount int 22 | s := skiplist{r: r} 23 | for i := 0; i < r.Head.height; i++ { 24 | s.s[i].knot = &r.Head 25 | } 26 | 27 | for n := &r.Head; n != nil; n = n.nexts[0].knot { 28 | assert.Condition(func() bool { return n.used > 0 }, "Expected a used count of greater than 0") 29 | assert.Condition(func() bool { return n.height <= MaxHeight }, "node cannot be greater than MaxHeight - %d", n.height) 30 | 31 | for i := 0; i < n.height; i++ { 32 | assert.Equal(s.s[i].knot, n, "search[%d] should be %p", i, n) 33 | assert.Equal(s.s[i].skipped, runeCount, "RuneCount should be the same as skipped.") 34 | 35 | s.s[i].knot = n.nexts[i].knot 36 | s.s[i].skipped += n.nexts[i].skipped 37 | } 38 | runeCount += n.nexts[0].skipped 39 | } 40 | 41 | for i := 0; i < r.Head.height; i++ { 42 | assert.Nil(s.s[i].knot) 43 | assert.Equal(runeCount, s.s[i].skipped) 44 | } 45 | 46 | assert.Equal(runeCount, r.size) 47 | } 48 | 49 | func TestEmptyRope(t *testing.T) { 50 | r := New() 51 | assert.Equal(t, "", r.String()) 52 | assert.Equal(t, 0, r.Size()) 53 | } 54 | 55 | func TestPlay(t *testing.T) { 56 | // t.Skip() 57 | r := New() 58 | if err := r.Insert(0, "0123456789 hello world ab2cdefghi fakk1 eir3d"); err != nil { 59 | t.Fatal(err) 60 | } 61 | rs := r.SubstrRunes(5, 18) 62 | if string(rs) != "56789 hello w" { 63 | t.Errorf("Expected %q. Got %q instead", "56789 hello w", string(rs)) 64 | } 65 | 66 | r.InsertRunes(10, []rune("ADDED")) 67 | rs = r.SubstrRunes(0, r.size) 68 | if string(rs) != "0123456789ADDED hello world ab2cdefghi fakk1 eir3d" { 69 | t.Errorf("Add failed. Got %q instead", string(rs)) 70 | } 71 | 72 | // erase "ADDED" 73 | if err := r.EraseAt(10, 5); err != nil { 74 | t.Error(err) 75 | } 76 | rs = r.SubstrRunes(0, r.size) 77 | if string(rs) != "0123456789 hello world ab2cdefghi fakk1 eir3d" { 78 | t.Errorf("Erase failed. Got %q instead", string(rs)) 79 | } 80 | } 81 | 82 | func TestPlay2(t *testing.T) { 83 | // t.Skip() 84 | r := New() 85 | if err := r.Insert(0, "short"); err != nil { 86 | t.Fatal(err) 87 | } 88 | if err := r.Insert(5, "short"); err != nil { 89 | t.Fatal(err) 90 | } 91 | if r.Size() != 10 { 92 | t.Errorf("Expected size of rope to be 10. Got %d instead", r.Size()) 93 | } 94 | if r.Runes() != 10 { 95 | t.Errorf("Expected size of rope to be 10. Got %d instead", r.Runes()) 96 | } 97 | if r.String() != "shortshort" { 98 | t.Errorf("Expected rope value to be \"shortshort\". Got %q instead", r.String()) 99 | } 100 | 101 | sss := "this is a super long string of characters inserted into the middle " 102 | if err := r.Insert(5, sss); err != nil { 103 | t.Fatal(err) 104 | } 105 | if r.String() != "short"+sss+"short" { 106 | t.Errorf("Insertion failed. Got %q instead", r.String()) 107 | } 108 | assert.Equal(t, "short"+sss+"short", r.String()) 109 | } 110 | 111 | func TestQC(t *testing.T) { 112 | r := New() 113 | f := func(a string) (ok bool) { 114 | // insert at 1, because 1 is greater than 0. This tests the resilience of the rope 115 | if err := r.Insert(1, a); err != nil { 116 | t.Errorf("Failed to insert during QC: %v", err) 117 | return false 118 | } 119 | 120 | // check 121 | b := r.String() 122 | if b != a { 123 | t.Errorf("Expected %q. Got %q instead. Len(a): %d | %d", a, b, len(a), r.size) 124 | return false 125 | } 126 | 127 | // del at len(a)+1, This tests the resilience of the rope 128 | if err := r.EraseAt(0, len(a)); err != nil { 129 | t.Errorf("Failed to erase during QC: %v", err) 130 | return false 131 | } 132 | 133 | // check 134 | c := r.String() 135 | if c != "" { 136 | t.Errorf("Expected %q. Got %q instead", "", c) 137 | return false 138 | } 139 | 140 | return true 141 | } 142 | if err := quick.Check(f, &quick.Config{MaxCount: 2000}); err != nil { 143 | t.Fatal(err) 144 | } 145 | } 146 | 147 | func TestBasic(t *testing.T) { 148 | r := New() 149 | if err := r.Insert(0, a); err != nil { 150 | t.Fatal(err) 151 | } 152 | if r.Size() != len(a) { 153 | t.Errorf("Expected same size of %d. Got %d instead", len(a), r.size) 154 | } 155 | if r.runes != len([]rune(a)) { 156 | t.Errorf("Expected same number of runes %d. Got %d instead", len([]rune(a)), r.runes) 157 | } 158 | 159 | // Test Indexing 160 | if roon := r.Index(4); roon != 'e' { 161 | t.Errorf("Expected rune at 4 to be 'e'. Got %q instead", roon) 162 | } 163 | 164 | if roon := r.Index(BucketSize); roon != 'n' { 165 | t.Errorf("Expected rune at %d to be 'n'. Got %q instead", BucketSize, roon) 166 | } 167 | 168 | if roon := r.Index(BucketSize + 1); roon != ' ' { 169 | t.Errorf("Expected rune at %d to be 'n'. Got %q instead", BucketSize, roon) 170 | } 171 | 172 | if roon := r.Index(len([]rune(a))); roon != 0 { 173 | t.Errorf("Expected rune at %d to be 'n'. Got %q instead", len([]rune(a)), roon) 174 | } 175 | 176 | if roon := r.Index(2 * len([]rune(a))); roon != -1 { 177 | t.Errorf("Expected rune at 2*%d to be 'n'. Got %q instead", len([]rune(a)), roon) 178 | } 179 | } 180 | 181 | func TestBefore(t *testing.T) { 182 | // short 183 | r := New() 184 | if err := r.Insert(0, "Hello World"); err != nil { 185 | t.Fatal(err) 186 | } 187 | 188 | before, char, err := r.Before(9, unicode.IsSpace) 189 | if err != nil { 190 | t.Error(err) 191 | } 192 | 193 | if before != 6 { 194 | t.Errorf("Expected before to be 6. Got %d instead", before) 195 | } 196 | if char != ' ' { 197 | t.Errorf("Expected char to be ' '. Got %q instead", char) 198 | } 199 | } 200 | 201 | func TestByteOffset(t *testing.T) { 202 | a := []byte("hello world") 203 | offset := byteOffset(a, 5) 204 | assert.Equal(t, 5, offset) 205 | assert.Equal(t, ' ', rune(a[offset]), "Expected 'o'. Got %q instead", a[offset]) 206 | 207 | // combining diacritics are one separate character 208 | a = []byte("Sitting in a café, writing this test") 209 | offset = byteOffset(a, 18) // 19th character is ',', and starts at byte offset 20 210 | assert.Equal(t, 19, offset) // the combining diacritic is 2 characters wide 211 | assert.Equal(t, ',', rune(a[offset]), "Expected ','. Got %q instead", string(a[offset:])) 212 | } 213 | 214 | func TestRope_ByteOffset(t *testing.T) { 215 | r := New() 216 | nonUTF8 := "hello world" 217 | if err := r.Insert(0, nonUTF8); err != nil { 218 | t.Fatal(err) 219 | } 220 | for i := range nonUTF8 { 221 | b := r.ByteOffset(i) 222 | assert.Equal(t, i, b) 223 | } 224 | } 225 | 226 | func TestRope_Write(t *testing.T) { 227 | r := New() 228 | expected := "Standing at home, writing this test" 229 | written, err := r.Write([]byte(expected)) 230 | if err != nil { 231 | t.Fatal(err) 232 | } 233 | assert.Equal(t, expected, r.String()) 234 | assert.Equal(t, len(expected), written) 235 | } 236 | 237 | func TestRope_WriteAppend(t *testing.T) { 238 | r := New() 239 | expected := "Sitting at home, writing this test" 240 | written1, err := r.Write([]byte(expected[:5])) 241 | if err != nil { 242 | t.Fatal(err) 243 | } 244 | written2, err := r.Write([]byte(expected[5:])) 245 | if err != nil { 246 | t.Fatal(err) 247 | } 248 | assert.Equal(t, expected, r.String()) 249 | assert.Equal(t, len(expected), written1+written2) 250 | } 251 | 252 | func ExampleBasic() { 253 | r := New() 254 | _ = r.Insert(0, "Hello World. This is a long sentence. The purpose of this long sentence is to make sure there is more than BucketSize worth of runes") 255 | 256 | char := r.Index(70) 257 | fmt.Printf("Char at 70: %q\n", char) 258 | 259 | l := r.Size() 260 | fmt.Printf("Length: %d\n", l) 261 | 262 | // Output: 263 | // Char at 70: 'e' 264 | // Length: 132 265 | // 266 | } 267 | 268 | func ExampleRope_SubstrBytes_Multibyte() { 269 | r := New() 270 | _ = r.Insert(0, "你好world") 271 | // The string is a 11 byte string. Both chinese characters are 3 bytes each. 272 | 273 | // The SubstrBytes method indexes by runes. 274 | // if we want only "好", we'd have to call SubstrBytes(1, 2), 275 | // not SubstrBytes(3,6), which is what you'd do if it indexes by bytes. 276 | bytes := r.SubstrBytes(1, 2) 277 | fmt.Println(string(bytes)) 278 | // Output: 279 | // 好 280 | } 281 | 282 | func ExampleRope_Before() { 283 | r := New() 284 | _ = r.Insert(0, "Hello World. This is a long sentence. The purpose of this long sentence is to make sure there is more than BucketSize worth of runes") 285 | before, char, err := r.Before(70, unicode.IsSpace) 286 | if err != nil { 287 | fmt.Printf("Error: cannot get before 70: %v", err) 288 | } 289 | 290 | fmt.Printf("First whitespace before position 70: %d - %q\n", before, char) 291 | 292 | // Output: 293 | // First whitespace before position 70: 63 - ' ' 294 | } 295 | 296 | func ExampleRope_ByteOffset() { 297 | r := New() 298 | _ = r.Insert(0, "你好world") 299 | b0 := r.ByteOffset(1) // 1st rune is '好' - 3 300 | b1 := r.ByteOffset(2) // 2nd rune is 'w' - 6 301 | bErr := r.ByteOffset(200) // impossible 302 | 303 | fmt.Printf("b0: %d\nb1: %d\nbErr: %d", b0, b1, bErr) 304 | 305 | // Output: 306 | // b0: 3 307 | // b1: 6 308 | // bErr: -1 309 | } 310 | 311 | // func TestLines(t *testing.T) { 312 | // r := New() 313 | // s := `1 Hello World. The first line is intentionally super long as to make sure there are several blocks 314 | // 2 315 | // 3 你好世界 316 | // 4 Last line! 317 | // ` 318 | // if err := r.Insert(0, s); err != nil { 319 | // t.Fatal(err) 320 | // } 321 | // row, col := r.RowCol(100) // 2 322 | // if row != 2 { 323 | // t.Errorf("Expected row to be 2. Got %d instead", row) 324 | // } 325 | // if col != 1 { 326 | // t.Errorf("Expected col to be 1. Got %d instead", col) 327 | // } 328 | 329 | // row, col = r.RowCol(103) // 3 330 | // if row != 3 { 331 | // t.Errorf("Expected row to be 3. Got %d instead", row) 332 | // } 333 | // if col != 1 { 334 | // t.Errorf("Expected col to be 1. Got %d instead", col) 335 | // } 336 | 337 | // s = `1 Hello World. The first line is intentionally super long as to make sure there are several blocks 338 | // 2 339 | // 3 你好世界. Here is another long வாக்கியம் to make sure that the bucket is exceeded 340 | // 4 Last line! 341 | // ` 342 | // r = New() 343 | // if err := r.Insert(0, s); err != nil { 344 | // t.Fatal(err) 345 | // } 346 | // row, col = r.RowCol(185) // 4 347 | // if row != 4 { 348 | // t.Errorf("Expected row to be 4. Got %d instead", row) 349 | // } 350 | // if col != 1 { 351 | // t.Errorf("Expected col to be 1. Got %d instead", col) 352 | // } 353 | // } 354 | 355 | // func TestSkiplist_find(t *testing.T) { 356 | // r := New() 357 | // s := skiplist{r: r} 358 | // k, offset, err := s.find(0) 359 | // if err != nil { 360 | // t.Fatal(err) 361 | // } 362 | // if k != &r.Head { 363 | // t.Error("Expected knot at 0 to be head") 364 | // } 365 | 366 | // r.Insert(0, "0123456789 hello world ab2cdefghi fakk1 eir3d") 367 | // if k, offset, err = s.find(18); err != nil { 368 | // t.Error(err) 369 | // } 370 | // if offset != 2 { 371 | // t.Error("Expected offset to be 2") 372 | // } 373 | // } 374 | 375 | const a = `Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, ‘and what is the use of a book,’ thought Alice ‘without pictures or conversations?’ 376 | 377 | So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her. 378 | 379 | There was nothing so very remarkable in that; nor did Alice think it so very much out of the way to hear the Rabbit say to itself, ‘Oh dear! Oh dear! I shall be late!’ (when she thought it over afterwards, it occurred to her that she ought to have wondered at this, but at the time it all seemed quite natural); but when the Rabbit actually took a watch out of its waistcoat-pocket, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it, and fortunately was just in time to see it pop down a large rabbit-hole under the hedge. 380 | 381 | In another moment down went Alice after it, never once considering how in the world she was to get out again. 382 | 383 | The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down a very deep well. 384 | 385 | Either the well was very deep, or she fell very slowly, for she had plenty of time as she went down to look about her and to wonder what was going to happen next. First, she tried to look down and make out what she was coming to, but it was too dark to see anything; then she looked at the sides of the well, and noticed that they were filled with cupboards and book-shelves; here and there she saw maps and pictures hung upon pegs. She took down a jar from one of the shelves as she passed; it was labelled ‘ORANGE MARMALADE’, but to her great disappointment it was empty: she did not like to drop the jar for fear of killing somebody, so managed to put it into one of the cupboards as she fell past it. 386 | 387 | ‘Well!’ thought Alice to herself, ‘after such a fall as this, I shall think nothing of tumbling down stairs! How brave they’ll all think me at home! Why, I wouldn’t say anything about it, even if I fell off the top of the house!’ (Which was very likely true.) 388 | 389 | Down, down, down. Would the fall never come to an end! ‘I wonder how many miles I’ve fallen by this time?’ she said aloud. ‘I must be getting somewhere near the centre of the earth. Let me see: that would be four thousand miles down, I think—’ (for, you see, Alice had learnt several things of this sort in her lessons in the schoolroom, and though this was not a very good opportunity for showing off her knowledge, as there was no one to listen to her, still it was good practice to say it over) ‘—yes, that’s about the right distance—but then I wonder what Latitude or Longitude I’ve got to?’ (Alice had no idea what Latitude was, or Longitude either, but thought they were nice grand words to say.) 390 | 391 | Presently she began again. ‘I wonder if I shall fall right through the earth! How funny it’ll seem to come out among the people that walk with their heads downward! The Antipathies, I think—’ (she was rather glad there was no one listening, this time, as it didn’t sound at all the right word) ‘—but I shall have to ask them what the name of the country is, you know. Please, Ma’am, is this New Zealand or Australia?’ (and she tried to curtsey as she spoke—fancy curtseying as you’re falling through the air! Do you think you could manage it?) ‘And what an ignorant little girl she’ll think me for asking! No, it’ll never do to ask: perhaps I shall see it written up somewhere.’ 392 | 393 | Down, down, down. There was nothing else to do, so Alice soon began talking again. ‘Dinah’ll miss me very much to-night, I should think!’ (Dinah was the cat.) ‘I hope they’ll remember her saucer of milk at tea-time. Dinah my dear! I wish you were down here with me! There are no mice in the air, I’m afraid, but you might catch a bat, and that’s very like a mouse, you know. But do cats eat bats, I wonder?’ And here Alice began to get rather sleepy, and went on saying to herself, in a dreamy sort of way, ‘Do cats eat bats? Do cats eat bats?’ and sometimes, ‘Do bats eat cats?’ for, you see, as she couldn’t answer either question, it didn’t much matter which way she put it. She felt that she was dozing off, and had just begun to dream that she was walking hand in hand with Dinah, and saying to her very earnestly, ‘Now, Dinah, tell me the truth: did you ever eat a bat?’ when suddenly, thump! thump! down she came upon a heap of sticks and dry leaves, and the fall was over. 394 | 395 | Alice was not a bit hurt, and she jumped up on to her feet in a moment: she looked up, but it was all dark overhead; before her was another long passage, and the White Rabbit was still in sight, hurrying down it. There was not a moment to be lost: away went Alice like the wind, and was just in time to hear it say, as it turned a corner, ‘Oh my ears and whiskers, how late it’s getting!’ She was close behind it when she turned the corner, but the Rabbit was no longer to be seen: she found herself in a long, low hall, which was lit up by a row of lamps hanging from the roof. 396 | 397 | There were doors all round the hall, but they were all locked; and when Alice had been all the way down one side and up the other, trying every door, she walked sadly down the middle, wondering how she was ever to get out again. 398 | 399 | Suddenly she came upon a little three-legged table, all made of solid glass; there was nothing on it except a tiny golden key, and Alice’s first thought was that it might belong to one of the doors of the hall; but, alas! either the locks were too large, or the key was too small, but at any rate it would not open any of them. However, on the second time round, she came upon a low curtain she had not noticed before, and behind it was a little door about fifteen inches high: she tried the little golden key in the lock, and to her great delight it fitted! 400 | 401 | Alice opened the door and found that it led into a small passage, not much larger than a rat-hole: she knelt down and looked along the passage into the loveliest garden you ever saw. How she longed to get out of that dark hall, and wander about among those beds of bright flowers and those cool fountains, but she could not even get her head through the doorway; ‘and even if my head would go through,’ thought poor Alice, ‘it would be of very little use without my shoulders. Oh, how I wish I could shut up like a telescope! I think I could, if I only knew how to begin.’ For, you see, so many out-of-the-way things had happened lately, that Alice had begun to think that very few things indeed were really impossible. 402 | 403 | There seemed to be no use in waiting by the little door, so she went back to the table, half hoping she might find another key on it, or at any rate a book of rules for shutting people up like telescopes: this time she found a little bottle on it, (‘which certainly was not here before,’ said Alice,) and round the neck of the bottle was a paper label, with the words ‘DRINK ME’ beautifully printed on it in large letters. 404 | 405 | It was all very well to say ‘Drink me,’ but the wise little Alice was not going to do that in a hurry. ‘No, I’ll look first,’ she said, ‘and see whether it’s marked “poison” or not’; for she had read several nice little histories about children who had got burnt, and eaten up by wild beasts and other unpleasant things, all because they would not remember the simple rules their friends had taught them: such as, that a red-hot poker will burn you if you hold it too long; and that if you cut your finger very deeply with a knife, it usually bleeds; and she had never forgotten that, if you drink much from a bottle marked ‘poison,’ it is almost certain to disagree with you, sooner or later. 406 | 407 | However, this bottle was not marked ‘poison,’ so Alice ventured to taste it, and finding it very nice, (it had, in fact, a sort of mixed flavour of cherry-tart, custard, pine-apple, roast turkey, toffee, and hot buttered toast,) she very soon finished it off. 408 | 409 | * * * * * * * 410 | 411 | * * * * * * 412 | 413 | * * * * * * * 414 | 415 | ‘What a curious feeling!’ said Alice; ‘I must be shutting up like a telescope.’ 416 | 417 | And so it was indeed: she was now only ten inches high, and her face brightened up at the thought that she was now the right size for going through the little door into that lovely garden. First, however, she waited for a few minutes to see if she was going to shrink any further: she felt a little nervous about this; ‘for it might end, you know,’ said Alice to herself, ‘in my going out altogether, like a candle. I wonder what I should be like then?’ And she tried to fancy what the flame of a candle is like after the candle is blown out, for she could not remember ever having seen such a thing. 418 | 419 | After a while, finding that nothing more happened, she decided on going into the garden at once; but, alas for poor Alice! when she got to the door, she found she had forgotten the little golden key, and when she went back to the table for it, she found she could not possibly reach it: she could see it quite plainly through the glass, and she tried her best to climb up one of the legs of the table, but it was too slippery; and when she had tired herself out with trying, the poor little thing sat down and cried. 420 | 421 | ‘Come, there’s no use in crying like that!’ said Alice to herself, rather sharply; ‘I advise you to leave off this minute!’ She generally gave herself very good advice, (though she very seldom followed it), and sometimes she scolded herself so severely as to bring tears into her eyes; and once she remembered trying to box her own ears for having cheated herself in a game of croquet she was playing against herself, for this curious child was very fond of pretending to be two people. ‘But it’s no use now,’ thought poor Alice, ‘to pretend to be two people! Why, there’s hardly enough of me left to make one respectable person!’ 422 | 423 | Soon her eye fell on a little glass box that was lying under the table: she opened it, and found in it a very small cake, on which the words ‘EAT ME’ were beautifully marked in currants. ‘Well, I’ll eat it,’ said Alice, ‘and if it makes me grow larger, I can reach the key; and if it makes me grow smaller, I can creep under the door; so either way I’ll get into the garden, and I don’t care which happens!’ 424 | 425 | She ate a little bit, and said anxiously to herself, ‘Which way? Which way?’, holding her hand on the top of her head to feel which way it was growing, and she was quite surprised to find that she remained the same size: to be sure, this generally happens when one eats cake, but Alice had got so much into the way of expecting nothing but out-of-the-way things to happen, that it seemed quite dull and stupid for life to go on in the common way. 426 | 427 | So she set to work, and very soon finished off the cake. 428 | ` 429 | 430 | func BenchmarkNaiveSubstr(b *testing.B) { 431 | var s string 432 | for i := 0; i < b.N; i++ { 433 | s = a[5:10] 434 | } 435 | _ = s 436 | } 437 | 438 | func BenchmarkRopeSubstr(b *testing.B) { 439 | var s string 440 | r := New() 441 | r.Insert(0, a) 442 | 443 | b.ResetTimer() 444 | for i := 0; i < b.N; i++ { 445 | s = r.Substr(5, 10) 446 | } 447 | _ = s 448 | } 449 | 450 | func BenchmarkNaiveAppendAndDelete(b *testing.B) { 451 | const c = "ADDED" 452 | buf := bytes.NewBuffer([]byte(a)) 453 | 454 | b.ResetTimer() 455 | for i := 0; i < b.N; i++ { 456 | fmt.Fprintf(buf, c) 457 | buf.Truncate(len(c)) 458 | } 459 | } 460 | 461 | func BenchmarkRopeAppendAndDelete(b *testing.B) { 462 | const c = "ADDED" 463 | r := New() 464 | r.Insert(0, a) 465 | 466 | // these are expensive operations because we're not working with byte slices 467 | la := len([]rune(a)) 468 | lc := len([]rune(c)) 469 | 470 | b.ResetTimer() 471 | for i := 0; i < b.N; i++ { 472 | r.Insert(la, c) 473 | r.EraseAt(la, lc) 474 | } 475 | } 476 | 477 | func BenchmarkNaiveAppend(b *testing.B) { 478 | buf := bytes.NewBuffer([]byte(a)) 479 | 480 | b.ResetTimer() 481 | for i := 0; i < b.N; i++ { 482 | fmt.Fprintf(buf, "a") 483 | } 484 | } 485 | 486 | func BenchmarkRopeAppend(b *testing.B) { 487 | r := New() 488 | r.Insert(0, a) 489 | 490 | b.ResetTimer() 491 | for i := 0; i < b.N; i++ { 492 | r.Insert(len(a), "a") 493 | } 494 | } 495 | 496 | func BenchmarkNaiveRandomInsert(b *testing.B) { 497 | buf := bytes.NewBuffer([]byte(a)) 498 | b.ResetTimer() 499 | rand := 4 500 | for i := 0; i < b.N; i++ { 501 | if i%100 == 0 { 502 | rand = 2 503 | } 504 | buf.Grow(1) 505 | bb := buf.Bytes() 506 | // insert at point 4. Confirmed random 507 | copy(bb[rand+1:], bb[rand:]) 508 | bb[rand] = 'a' 509 | } 510 | } 511 | 512 | func BenchmarkRopeRandomInsert(b *testing.B) { 513 | r := New() 514 | r.Insert(0, a) 515 | 516 | b.ResetTimer() 517 | rand := 4 518 | for i := 0; i < b.N; i++ { 519 | if i%100 == 0 { 520 | rand = 2 521 | } 522 | r.InsertRunes(rand, []rune{'a'}) 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package skiprope 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "unicode/utf8" 7 | ) 8 | 9 | var ErrSOF = errors.New("Cannot unread on SOF") 10 | 11 | // Scanner is a linear scanner over a *Rope which returns runes. 12 | type Scanner struct { 13 | *Rope 14 | k *knot // current 15 | offset int 16 | readBytes int // how many bytes has been read 17 | 18 | // lastSize is used in implementing UnreadRune() 19 | lastSize int 20 | prevK *knot 21 | } 22 | 23 | // NewScanner creates a new scanner. 24 | func NewScanner(r *Rope) *Scanner { 25 | sl := skiplist{r: r} 26 | s := &Scanner{Rope: r} 27 | var err error 28 | if s.k, s.offset, _, err = sl.find(0); err != nil { 29 | panic(err) 30 | } 31 | 32 | // first block may not be used 33 | for s.k.used-s.offset <= 0 { 34 | // go to next block 35 | s.prevK = s.k 36 | s.k = s.k.nexts[0].knot 37 | } 38 | return s 39 | } 40 | 41 | // Len returns the number of bytes unread 42 | func (s *Scanner) Len() int { return s.size - s.readBytes } 43 | 44 | // Read implements io.Reader. It reads up to len(p) bytes into p. The Scanner is a stateful reader, 45 | // meaning it will keep track of how many bytes has been read. 46 | func (s *Scanner) Read(p []byte) (n int, err error) { 47 | if s.readBytes >= s.size || s.k == nil { 48 | return 0, io.EOF 49 | } 50 | 51 | l := len(p) 52 | used := s.k.used - s.offset 53 | n = copy(p, s.k.data[s.offset:s.k.used]) 54 | if l <= used { 55 | s.offset += n 56 | s.readBytes += n 57 | return 58 | } 59 | 60 | // TODO: recursive reading is simple to understand but performance is ??? . 61 | remainder := l - n 62 | if remainder > 0 { 63 | s.offset = 0 64 | 65 | var n2 int 66 | s.k = s.k.nexts[0].knot 67 | if n2, err = s.Read(p[remainder:]); err != nil && err != io.EOF { 68 | return n, err 69 | } 70 | err = nil 71 | n += n2 72 | } 73 | return 74 | } 75 | 76 | // ReadByte implements io.ByteReader 77 | func (s *Scanner) ReadByte() (byte, error) { 78 | if s.readBytes >= s.size || s.k == nil { 79 | return 0, io.EOF 80 | } 81 | retVal := s.k.data[s.offset] 82 | s.offset++ 83 | s.readBytes++ 84 | if s.offset >= s.k.used { 85 | s.prevK = s.k 86 | s.k = s.k.nexts[0].knot 87 | s.offset = 0 88 | } 89 | return retVal, nil 90 | } 91 | 92 | // ReadRune implements io.RuneReader. It reads a single UTF8 encoded character and then returns its size in bytes 93 | func (s *Scanner) ReadRune() (rune, int, error) { 94 | if s.k == nil || s.readBytes >= s.size { 95 | return -1, -1, io.EOF 96 | } 97 | 98 | r, size := utf8.DecodeRune(s.k.data[s.offset:s.k.used]) 99 | s.lastSize = size 100 | s.offset += size 101 | s.readBytes += size 102 | if s.offset >= s.k.used { 103 | s.prevK = s.k 104 | s.k = s.k.nexts[0].knot 105 | s.offset = 0 106 | } 107 | return r, size, nil 108 | } 109 | 110 | // UnreadRune implements io.RuneScanner. 111 | func (s *Scanner) UnreadRune() error { 112 | if s.offset == 0 && s.prevK == nil { 113 | return ErrSOF 114 | } 115 | s.readBytes -= s.lastSize 116 | if s.offset == 0 { 117 | s.k = s.prevK 118 | s.prevK = nil 119 | s.offset = s.k.used - s.lastSize 120 | } else { 121 | s.offset -= s.lastSize 122 | if s.offset < 0 { 123 | // TODO: this is unlikely to happen 124 | return io.ErrShortBuffer 125 | } 126 | } 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /scanner_test.go: -------------------------------------------------------------------------------- 1 | package skiprope 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | func ExampleScanner() { 10 | r := New() 11 | r.Insert(0, "Hello 世界") 12 | 13 | s := NewScanner(r) 14 | for r, _, err := s.ReadRune(); err == nil; r, _, err = s.ReadRune() { 15 | fmt.Printf("%q\n", r) 16 | } 17 | // Output: 18 | // 'H' 19 | // 'e' 20 | // 'l' 21 | // 'l' 22 | // 'o' 23 | // ' ' 24 | // '世' 25 | // '界' 26 | } 27 | 28 | func ExampleScanner_long() { 29 | r := New() 30 | r.Insert(0, "Hello 世界. This is a longer sentence that spans multiple blocks of skip nodes.") 31 | 32 | s := NewScanner(r) 33 | for r, _, err := s.ReadRune(); err == nil; r, _, err = s.ReadRune() { 34 | fmt.Printf("%q\n", r) 35 | } 36 | // Output: 37 | // 'H' 38 | // 'e' 39 | // 'l' 40 | // 'l' 41 | // 'o' 42 | // ' ' 43 | // '世' 44 | // '界' 45 | // '.' 46 | // ' ' 47 | // 'T' 48 | // 'h' 49 | // 'i' 50 | // 's' 51 | // ' ' 52 | // 'i' 53 | // 's' 54 | // ' ' 55 | // 'a' 56 | // ' ' 57 | // 'l' 58 | // 'o' 59 | // 'n' 60 | // 'g' 61 | // 'e' 62 | // 'r' 63 | // ' ' 64 | // 's' 65 | // 'e' 66 | // 'n' 67 | // 't' 68 | // 'e' 69 | // 'n' 70 | // 'c' 71 | // 'e' 72 | // ' ' 73 | // 't' 74 | // 'h' 75 | // 'a' 76 | // 't' 77 | // ' ' 78 | // 's' 79 | // 'p' 80 | // 'a' 81 | // 'n' 82 | // 's' 83 | // ' ' 84 | // 'm' 85 | // 'u' 86 | // 'l' 87 | // 't' 88 | // 'i' 89 | // 'p' 90 | // 'l' 91 | // 'e' 92 | // ' ' 93 | // 'b' 94 | // 'l' 95 | // 'o' 96 | // 'c' 97 | // 'k' 98 | // 's' 99 | // ' ' 100 | // 'o' 101 | // 'f' 102 | // ' ' 103 | // 's' 104 | // 'k' 105 | // 'i' 106 | // 'p' 107 | // ' ' 108 | // 'n' 109 | // 'o' 110 | // 'd' 111 | // 'e' 112 | // 's' 113 | // '.' 114 | } 115 | 116 | func ExampleScanner_UnreadRune() { 117 | r := New() 118 | r.Insert(0, "Hello 世界") 119 | 120 | s := NewScanner(r) 121 | s.ReadRune() 122 | s.UnreadRune() 123 | char, _, _ := s.ReadRune() 124 | fmt.Printf("Read-Unread-Read: %q", char) 125 | // Output: 126 | // Read-Unread-Read: 'H' 127 | } 128 | 129 | func TestScanner_UnreadRune(t *testing.T) { 130 | r := New() 131 | r.Insert(0, "Hello 世界") 132 | s := NewScanner(r) 133 | var err error 134 | if err = s.UnreadRune(); err == nil { 135 | t.Error("Error was expected when trying to unread rune of 0") 136 | } 137 | 138 | r.Insert(0, "Some other long string") 139 | for _, _, err = s.ReadRune(); err == nil; _, _, err = s.ReadRune() { 140 | 141 | } 142 | if err != io.EOF { 143 | t.Error(err) 144 | } 145 | for err = s.UnreadRune(); err == nil; err = s.UnreadRune() { 146 | } 147 | if err != ErrSOF { 148 | t.Error(err) 149 | } 150 | } 151 | 152 | func TestScanner_Read(t *testing.T) { 153 | r := New() 154 | r.Insert(0, "This is a long string that is meant to span multiple *knots") 155 | 156 | p := make([]byte, 5) 157 | s := NewScanner(r) 158 | n, err := s.Read(p) 159 | if err != nil { 160 | t.Fatal(err) 161 | } 162 | if n != 5 { 163 | t.Error("Expected a read of 5 bytes") 164 | } 165 | if string(p) != "This " { 166 | t.Error("First read failed") 167 | } 168 | n, err = s.Read(p) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | if string(p) != "is a " { 173 | t.Error("2nd read failed") 174 | } 175 | 176 | // Longer stuff 177 | s = NewScanner(r) 178 | p = make([]byte, 1024) 179 | if n, err = s.Read(p); err != nil { 180 | t.Fatal(err) 181 | } 182 | if string(p[:n]) != "This is a long string that is meant to span multiple *knots" { 183 | t.Error("Long read failed") 184 | } 185 | } 186 | 187 | // ReadByte is affected by other method calls, as demonstrated by caling ReadRune 188 | func ExampleScanner_ReadByte() { 189 | r := New() 190 | r.Insert(0, "Hello World") 191 | 192 | s := NewScanner(r) 193 | fmt.Printf("String Size: %d.\n", len("Hello World")) 194 | s.ReadByte() // H 195 | s.ReadRune() // E 196 | fmt.Printf("Bytes Unread: %d\n", s.Len()) 197 | for b, err := s.ReadByte(); err == nil; b, err = s.ReadByte() { 198 | fmt.Printf("%q\n", b) 199 | } 200 | 201 | // Output: 202 | // String Size: 11. 203 | // Bytes Unread: 9 204 | // 'l' 205 | // 'l' 206 | // 'o' 207 | // ' ' 208 | // 'W' 209 | // 'o' 210 | // 'r' 211 | // 'l' 212 | // 'd' 213 | } 214 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | package skiprope 2 | 3 | import ( 4 | "unicode/utf8" 5 | 6 | "errors" 7 | // "log" 8 | ) 9 | 10 | // skiplist is a data structure for searching... it's the "skiplist" part of things 11 | type skiplist struct { 12 | r *Rope 13 | s [MaxHeight]skipknot 14 | } 15 | 16 | // newKnot will accept a []byte of BucketSize or less 17 | func (s *skiplist) newKnot(data []byte, runeCount int) { 18 | maxHeight := s.r.Head.height 19 | newHeight := randInt() 20 | 21 | byteCount := len(data) 22 | k := newKnot(newHeight) 23 | k.used = byteCount 24 | copy(k.data[0:], data) 25 | 26 | // the rest of the reason why anyone bothers to take accounting classes 27 | for maxHeight <= newHeight { 28 | s.r.Head.height++ 29 | s.r.Head.nexts[maxHeight] = s.r.Head.nexts[maxHeight-1] 30 | 31 | s.s[maxHeight] = s.s[maxHeight-1] 32 | maxHeight++ 33 | } 34 | 35 | // fill up the `nexts` field of k 36 | for i := 0; i < newHeight; i++ { 37 | prev := s.s[i].knot.nexts[i] 38 | k.nexts[i].knot = prev.knot 39 | k.nexts[i].skipped = byteCount + prev.skipped - s.s[i].skipped 40 | k.nexts[i].skippedRunes = runeCount + prev.skippedRunes - s.s[i].skippedRunes 41 | 42 | s.s[i].knot.nexts[i].knot = k 43 | s.s[i].knot.nexts[i].skipped = s.s[i].skipped 44 | s.s[i].knot.nexts[i].skippedRunes = s.s[i].skippedRunes 45 | 46 | // move search to end of newly inserted node 47 | s.s[i].knot = k 48 | s.s[i].skipped = byteCount 49 | s.s[i].skippedRunes = runeCount 50 | } 51 | 52 | for i := newHeight; i < maxHeight; i++ { 53 | s.s[i].knot.nexts[i].skipped += byteCount 54 | s.s[i].knot.nexts[i].skippedRunes += runeCount 55 | s.s[i].skipped += byteCount 56 | s.s[i].skippedRunes += runeCount 57 | } 58 | s.r.size += byteCount 59 | s.r.runes += runeCount 60 | } 61 | 62 | // find is the generic skip list finding function. It returns the offsets and skipped bytes. 63 | func (s *skiplist) find(point int) (retVal *knot, offsetBytes, skippedBytes int, err error) { 64 | if point > s.r.runes { 65 | return nil, -1, -1, errors.New("Index out of bounds") 66 | } 67 | 68 | k := &s.r.Head 69 | height := k.height - 1 70 | offset := point 71 | 72 | for { 73 | var skip int 74 | skip = k.nexts[height].skippedRunes 75 | if offset > skip { 76 | // go right 77 | offset -= skip 78 | if k.nexts[height].knot == nil { 79 | break 80 | } 81 | skippedBytes += k.nexts[height].skipped 82 | k = k.nexts[height].knot 83 | } else { 84 | // go down 85 | s.s[height].skippedRunes = offset 86 | s.s[height].skipped = skippedBytes 87 | s.s[height].knot = k 88 | if height == 0 { 89 | break 90 | } 91 | height-- 92 | } 93 | } 94 | offsetBytes = byteOffset(k.data[:], offset) 95 | return k, offsetBytes, skippedBytes, nil 96 | } 97 | 98 | // find2 is a method that finds blocks for insertion and deletion. No counts for byteoffsets required. 99 | func (s *skiplist) find2(point int) (retVal *knot, err error) { 100 | if point > s.r.runes { 101 | return nil, errors.New("Index out of bounds") 102 | } 103 | 104 | k := &s.r.Head 105 | height := k.height - 1 106 | offset := point 107 | 108 | for { 109 | var skip int 110 | skip = k.nexts[height].skippedRunes 111 | if offset > skip { 112 | // go right 113 | offset -= skip 114 | if k.nexts[height].knot == nil { 115 | break 116 | } 117 | k = k.nexts[height].knot 118 | } else { 119 | // go down 120 | s.s[height].skippedRunes = offset 121 | s.s[height].knot = k 122 | if height == 0 { 123 | break 124 | } 125 | height-- 126 | } 127 | } 128 | return k, nil 129 | } 130 | 131 | func (s *skiplist) updateOffsets(bytecount, runecount int) { 132 | for i := 0; i < s.r.Head.height; i++ { 133 | s.s[i].knot.nexts[i].skipped += bytecount 134 | s.s[i].knot.nexts[i].skippedRunes += runecount 135 | } 136 | } 137 | 138 | func (s *skiplist) insert(k *knot, data []byte) error { 139 | offset := s.s[0].skippedRunes 140 | var offsetBytes int 141 | if offset > 0 { 142 | offsetBytes = byteOffset(k.data[:], offset) 143 | } 144 | 145 | byteCount := len(data) 146 | 147 | // can insert? 148 | canInsert := k.used+byteCount <= BucketSize 149 | if !canInsert && offsetBytes == k.used { 150 | next := k.nexts[0].knot 151 | if next != nil && next.used+byteCount < BucketSize { 152 | offset = 0 153 | offsetBytes = 0 154 | for i := 0; i < next.height; i++ { 155 | s.s[i].knot = next 156 | } 157 | k = next 158 | canInsert = true 159 | } 160 | } 161 | runeCount := utf8.RuneCount(data) 162 | 163 | if canInsert { 164 | // move shit 165 | if byteCount < k.used { 166 | copy(k.data[offset+byteCount:], k.data[offset:]) 167 | } 168 | copy(k.data[offset:offset+byteCount], data) 169 | k.used += byteCount 170 | s.r.size += byteCount 171 | s.r.runes += runeCount 172 | // update the rest of the search tree 173 | s.updateOffsets(byteCount, runeCount) 174 | } else { 175 | // we'll need to add at least Knot to the rope 176 | 177 | // we'll need to remove the end of the current node's data if this is not at the end of the current node 178 | endBytes := k.used - offsetBytes 179 | var endRunes int 180 | if endBytes > 0 { 181 | k.used = offsetBytes 182 | endRunes = k.nexts[0].skippedRunes - offset 183 | s.updateOffsets(-endBytes, -endRunes) 184 | s.r.size -= endBytes 185 | s.r.runes -= endRunes 186 | } 187 | 188 | // insert new Knots containing new data 189 | var dataOffset int 190 | for dataOffset < len(data) { 191 | var newBytes, newRunes int 192 | for dataOffset+newBytes < len(data) { 193 | _, width := utf8.DecodeRune(data[dataOffset+newBytes:]) 194 | if newBytes+width > BucketSize { 195 | break 196 | } 197 | newBytes += width 198 | newRunes++ 199 | } 200 | // create new Knot 201 | s.newKnot(data[dataOffset:dataOffset+newBytes], newRunes) 202 | dataOffset += newBytes 203 | } 204 | 205 | // if we removed the end, it's time to add it back 206 | if endBytes > 0 { 207 | s.newKnot(k.data[offsetBytes:offsetBytes+endBytes], endRunes) 208 | } 209 | } 210 | return nil 211 | } 212 | 213 | func (s *skiplist) del(k *knot, n int) { 214 | s.r.runes -= n 215 | offset := s.s[0].skippedRunes 216 | var i int 217 | for n > 0 { 218 | if k == nil { 219 | break 220 | } 221 | if offset == k.nexts[0].skippedRunes { 222 | // end found. skip to the start of the next node 223 | k = s.s[0].knot.nexts[0].knot 224 | offset = 0 225 | } 226 | size := k.nexts[0].skippedRunes 227 | removed := min(n, size-offset) 228 | 229 | if removed < size || k == &s.r.Head { 230 | leading := byteOffset(k.data[:], offset) 231 | removedBytes := byteOffset(k.data[leading:], removed) 232 | if trailing := k.used - leading - removedBytes; trailing > 0 { 233 | copy(k.data[offset:], k.data[offset+removedBytes:offset+removedBytes+trailing]) 234 | } 235 | k.used -= removedBytes 236 | s.r.size -= removedBytes 237 | 238 | for i = 0; i < k.height; i++ { 239 | k.nexts[i].skippedRunes -= removed 240 | } 241 | } else { 242 | for i = 0; i < k.height; i++ { 243 | s.s[i].knot.nexts[i].knot = k.nexts[i].knot 244 | s.s[i].knot.nexts[i].skippedRunes += k.nexts[i].skippedRunes - removed 245 | } 246 | s.r.size -= k.used 247 | k = k.nexts[0].knot 248 | 249 | } 250 | for ; i < s.r.Head.height; i++ { 251 | s.s[i].knot.nexts[i].skippedRunes -= removed 252 | } 253 | n -= removed 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package skiprope 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | "unicode/utf8" 7 | // "log" 8 | ) 9 | 10 | var src = rand.New(rand.NewSource(time.Now().UnixNano())) 11 | 12 | func randInt() (retVal int) { 13 | retVal = 1 14 | for retVal < MaxHeight-1 && src.Intn(100) < Bias { 15 | retVal++ 16 | } 17 | return retVal 18 | } 19 | 20 | func min(a, b int) int { 21 | if a < b { 22 | return a 23 | } 24 | return b 25 | } 26 | 27 | func max(a, b int) int { 28 | if a > b { 29 | return a 30 | } 31 | return b 32 | } 33 | 34 | func clamp(a, minVal, maxVal int) int { 35 | return max(minVal, min(maxVal, a)) 36 | } 37 | 38 | // byteOffset takes a slice of bytes, and returns the index at which the expected number of runes there is 39 | func byteOffset(a []byte, runes int) (offset int) { 40 | if runes == 0 { 41 | return 0 42 | } 43 | 44 | var runeCount int 45 | for offset < len(a) && runeCount < runes { 46 | b := a[offset] 47 | if b < utf8.RuneSelf { 48 | // fast path 49 | offset++ 50 | runeCount++ 51 | continue 52 | } 53 | // _, size := utf8.DecodeRune(a[offset:]) 54 | size := int(first[b] & 7) // no checking of correct runes or not 55 | offset += size 56 | runeCount++ 57 | } 58 | // original code - commented out for posterity's sake. The loop below has been optimized 59 | // for _, size := utf8.DecodeRune(a[offset:]); offset < len(a) && runeCount < runes; _, size = utf8.DecodeRune(a[offset:]) { 60 | // offset += size 61 | // runeCount++ 62 | // } 63 | return offset 64 | } 65 | 66 | // first is information about the first byte in a UTF-8 sequence. 67 | 68 | const ( 69 | xx = 0xF1 // invalid: size 1 70 | as = 0xF0 // ASCII: size 1 71 | s1 = 0x02 // accept 0, size 2 72 | s2 = 0x13 // accept 1, size 3 73 | s3 = 0x03 // accept 0, size 3 74 | s4 = 0x23 // accept 2, size 3 75 | s5 = 0x34 // accept 3, size 4 76 | s6 = 0x04 // accept 0, size 4 77 | s7 = 0x44 // accept 4, size 4 78 | ) 79 | 80 | var first = [256]uint8{ 81 | // 1 2 3 4 5 6 7 8 9 A B C D E F 82 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F 83 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F 84 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F 85 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F 86 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F 87 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F 88 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F 89 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F 90 | 91 | // 1 2 3 4 5 6 7 8 9 A B C D E F 92 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F 93 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F 94 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF 95 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF 96 | xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF 97 | s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF 98 | s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF 99 | s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF 100 | 101 | } 102 | -------------------------------------------------------------------------------- /workdir/corpus/0568259c1f5af6f940d8e8fb108043b7cae330de-11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/0568259c1f5af6f940d8e8fb108043b7cae330de-11 -------------------------------------------------------------------------------- /workdir/corpus/091cc257a5ae51cf93833ebcfe3e05b1e64f93f4-12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/091cc257a5ae51cf93833ebcfe3e05b1e64f93f4-12 -------------------------------------------------------------------------------- /workdir/corpus/097d9474fad1260c2fc255f1048f45217f83297f-7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/097d9474fad1260c2fc255f1048f45217f83297f-7 -------------------------------------------------------------------------------- /workdir/corpus/0d718dcf8e41e4ee45a351ab1c1f9ac600d6fd99-4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/0d718dcf8e41e4ee45a351ab1c1f9ac600d6fd99-4 -------------------------------------------------------------------------------- /workdir/corpus/0fdde89ed21c31675c8ce4332da98e319832d6a7-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/0fdde89ed21c31675c8ce4332da98e319832d6a7-1 -------------------------------------------------------------------------------- /workdir/corpus/1103f3ab5d513c5925aac770e97c71efbb9768f2-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/1103f3ab5d513c5925aac770e97c71efbb9768f2-5 -------------------------------------------------------------------------------- /workdir/corpus/122c71c17966fe8ce7dcaee0af2168bdaba5fd7b-1: -------------------------------------------------------------------------------- 1 | Ros_______64e_53GvdTlgO8Q3_y_F9D3_lzgQ3_yF9D3dd,iotsr blu. op you ejobown ue 2 | _ht _ areblueU5I...for my tric.. 3 | Thk Y2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PU_drd, iolts are blu. op you ejobown ue 4 | _ht _ areblueU5I...for my tric.. 5 | Thk Y2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PUS_Dp__r_cWCon fo... [Beeeep] 6 | -------------------------------------------------------------------------------- /workdir/corpus/175635b48b4f360083fbfe58c45ec36802e2b649-9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/175635b48b4f360083fbfe58c45ec36802e2b649-9 -------------------------------------------------------------------------------- /workdir/corpus/1e14c927ca0d6d32de66e20a54a34998970fac2e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/1e14c927ca0d6d32de66e20a54a34998970fac2e -------------------------------------------------------------------------------- /workdir/corpus/285ba8efda8b0b67d9ea78286fd2ca997757f8fb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/285ba8efda8b0b67d9ea78286fd2ca997757f8fb -------------------------------------------------------------------------------- /workdir/corpus/29866502e4dbf89c5683936b0952f3119916fa7b-4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/29866502e4dbf89c5683936b0952f3119916fa7b-4 -------------------------------------------------------------------------------- /workdir/corpus/3478b37705bdadeba78b7882a5c39f99a0cb5593: -------------------------------------------------------------------------------- 1 | c_gte8sDb_1___Fy7Ko9__5Ai_[JI38dgj94_E5nLu__9iTWa_1V_a0_4x_0C_0b5_E3u5__tF3] 2 | 3 | -------------------------------------------------------------------------------- /workdir/corpus/359f58c40401b4022482dff0a1e3e0fdffab2b47-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/359f58c40401b4022482dff0a1e3e0fdffab2b47-1 -------------------------------------------------------------------------------- /workdir/corpus/377d832f63f774e33440b2688798b7bcb7588d1b-4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/377d832f63f774e33440b2688798b7bcb7588d1b-4 -------------------------------------------------------------------------------- /workdir/corpus/3df130541f67f68ed7f4b19760a2dd8c22c8aed0-4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/3df130541f67f68ed7f4b19760a2dd8c22c8aed0-4 -------------------------------------------------------------------------------- /workdir/corpus/441d8d20000776834490ecd83f64ff95bff09ea0-12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/441d8d20000776834490ecd83f64ff95bff09ea0-12 -------------------------------------------------------------------------------- /workdir/corpus/4af2baaa0c7db252dac606712d08c13b1c921de1: -------------------------------------------------------------------------------- 1 | 7_9H_C913_v5c1, -06.07areare .Rosesvca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Qred, violets are Hope you _k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 _ hueop you cC5_5nnJW_3AN_6F0A5Y2_jd1cM_3_3Wvm__zCp__0U85Hsnq36_m1_ 2 | _N____4_6qc8UB7p5_VE68v__Pf_4I__aWToF_r9__FI0_a__r__L0_R9_84_o_2_358tk _ __N_94_n0v6ee_j..uwEI2t_VKtg8Ju7o5w_22XT__B_w_nBv6jR_sk3_D6lx__55_HB__8U__dl_Ph6j36t_7vmm_12H7 my. 3 | Thk fo. . 066,iolts are bluRos_______64e_53GvdTlgO8Q3_y_F9D3_lzgQ3_yF9D3dd,_4D_7GB3CD2DHsZoTNuQeQ7P0t_yo73 . you ue 4 | _ht _ ...for mytric.. 5 | __y5ft_HSMi_2Dqs, y9Jk53G7_4lpeW0_7W__w_1I4J0q_OD41 ,blublu are ,Y2BW6K6U_Sdnx_j4Ln___e__35301m_s_Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PU_drd FhK_2_YYc_p5wpJPhOp90TVu_lf9__Sh6__E7____5__a_7__rJs_R1E61__HDpG1W_q63_5B5C_w9r_34X8xG_N151_oPB27 ue 6 | _areblueU5I...for0fx_K_A__EVD7___wXk__4I' 7 | vca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Qred, are__5W9__JKJBN_8_DHf_9Bi7_u46_cw_97h___ttb_0_I__V_XA__URfZJb_bk57__O8v_W9M . you_k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 g 2c_CBN_12223q__0z1jJ 8 | But 9B my tric.tric..for tric 9 | _8XA__L6_Ns__9xL2__i78_O77QU_3_D__227t_a_6I6j3_l__8_V___c__o_d_1e288Ods___5_ red,gc1___xD78_8_g_03gXare 0y5d75a_B27K___xr  red,1Ii_EQm8B__bopvioletsvioletsvioletsvioletsviolets! Hope brow .r5D terminalhue.forEUIn_dQ_YW4Dj4Uy_Hvkok3__4X4_v_48s4__93_KU3lp_1W3hEt_NDAP_G6PCJOg_a_gb6egLV_2m__mk_3f GxDV179___F.. 10 | . 7bHO__8b_u_7_74bDfuZ9ID Hopeterminal hue 11 | But now..ButnowGxDV179___FvioletsEUIn_dQ_YW4Dj4Uy_Hvkok3__4X4_v_48s4__93_KU3lp_1W3hEt_NDAP_G6PCJOg_a_gb6egLV_2m__mk_3f_8 blue.for . 12 | for my greatest trick.. 13 | .. Rosesfo:.. 14 | areblueU5I.%)..for my 1__j_wB23W5_M3u3_bWhw7K3_R8iSO7F64QnCLmBD0_8afqov84P3CD2Cc_G2_4_e9M837___5s1_9F_hDdWO_7bw0ogMy., _p_8_46YYT_t_An_Q__809_2__c3EtW60bYo_qd7IQTN___Z6371_CduR__6eH6g3svB97qH_Q09 red. youenjoyopop youejobown ue 15 | _8M _ areblueU5I>..formy tric.+ 16 | Thk m3__bHy_iAZ2V___M0khp5yn_30yfOk3_465TAXlLoy49W0Yi__04_HX_u__c_gtea8sDb_v1___Fy70Ko9__55WAi_E5Pf_lRosesX_a__DZC__6_643p_ZD_eH_7R__r593PGrv_d7_T_legz3g6O8Q43_y_F_9D3kC_drd, are blu. op _ _kiKmSxwy3s6 ue 17 | [Beeeep] 18 | 19 | -------------------------------------------------------------------------------- /workdir/corpus/4e50ef18669aa073868ad78a7b0873bb463e42bf-1: -------------------------------------------------------------------------------- 1 | ˙ɐᴉɐɐƃɐɯǝɹʇǝǝɹɐʇʇᴉᴉɔᴉɹɯǝʇɯᴉǝǝʇᴉǝƃᴉɔᴉᴉɔᴉɹɯǝʇɯᴉɐɹʇǝʇɔǝɔʇǝɯɐʇᴉɹɯᴉɯǝɹ˥˙Ɩ -------------------------------------------------------------------------------- /workdir/corpus/556816732436327a700a81916cd30aaebbdc082b: -------------------------------------------------------------------------------- 1 | Roses_d7_T_legz3g6O8Q43__9D3kC_drd, 5 are are .Rosesvca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Qred, violets are Hope you _k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 terminal hueop you cC5_5nnJW_3AN_6F0A5Y2_jd1cM_3_3Wvm__zCp__0U85Hsnq36_m1_ ue 2 | 94_ _ __N_94_n0v6ee_j...for my tric.. 3 | Thk SY2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PUS_Dp__r_cWCon fo... 066,iolts are bluRos_______64e_53GvdTlgO8Q3_y_F9D3_lzgQ3_yF9D3dd,_4D_7GB3CD2DHsZoTNuQeQ7P0t_yo73 . op you ue 4 | _ht _ areblueU5I...for my tric.. 5 | ThkY2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59P_drd, [Beeeep] y9Jk53G7_4lpeW0_7W__w_1I4J0q_OD41 blu, fo are blublu,Y2BW6K6U_Sdnx_j4Ln___e__35301m_s_Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PU_drd. op FhK_2_YYc_p5wpJPhOp90TVu_lf9__Sh6__E7____5__a_7__rJs_R1E61__HDpG1W_q63_5B5C_w9r_34X8xG_N151_oPB27 ejobown ue 6 | _ areblueU5I...for0fx_K_A__EVD7 tric'. 7 | op youejobown ue 8 | _ht _ areblueU5I>..for my tric. 9 | Thk SY2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PUS_Dp__r_cWCon RosesX_a__DZC__6_643p_ZD_eH_7R__r593PGrv_d7_T_legz3g6O8Q43_y_F_9D3kC_drd, are blu. op _ ejobown ue 10 | .. [Beeeep] 11 | 12 | -------------------------------------------------------------------------------- /workdir/corpus/58c08cb70b8049b531e7bd6912007223b73a9a31-3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/58c08cb70b8049b531e7bd6912007223b73a9a31-3 -------------------------------------------------------------------------------- /workdir/corpus/5cafdbf3eb448ec2bc10f4b35f6b549d218f13d6-3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/5cafdbf3eb448ec2bc10f4b35f6b549d218f13d6-3 -------------------------------------------------------------------------------- /workdir/corpus/5ed88ff1226785b218e011f86a80d946efb1fcbb-10: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/5ed88ff1226785b218e011f86a80d946efb1fcbb-10 -------------------------------------------------------------------------------- /workdir/corpus/5fb0483706289d4878f9fc32e6fe16909da0e2d2-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/5fb0483706289d4878f9fc32e6fe16909da0e2d2-5 -------------------------------------------------------------------------------- /workdir/corpus/61597000dfbb4eda6dc48b306ae24ee3b02f308c-9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/61597000dfbb4eda6dc48b306ae24ee3b02f308c-9 -------------------------------------------------------------------------------- /workdir/corpus/64ec56cc01bdb7fde9f2cb1a7e4fa26f071abf0b-13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/64ec56cc01bdb7fde9f2cb1a7e4fa26f071abf0b-13 -------------------------------------------------------------------------------- /workdir/corpus/6b6652c231feb7cfba5124fb49ef0d38fa156498-11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/6b6652c231feb7cfba5124fb49ef0d38fa156498-11 -------------------------------------------------------------------------------- /workdir/corpus/6e138ca1b4c4e37902e261e5dcc68e2ebc79b3be-3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/6e138ca1b4c4e37902e261e5dcc68e2ebc79b3be-3 -------------------------------------------------------------------------------- /workdir/corpus/6e6c421a64df2b4768d6ee8c59840f6d00423039: -------------------------------------------------------------------------------- 1 | vca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Q red, are__5W9__JKJBN_8_DHf_9Bi7_u46_cw_97h___ttb_0_I__V_XA__URfZJb_bk57__O8v_W9M . you _k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 g hue 2 | But nowviolets _8XA__L6_Ns__9xL2__i78_O77QU_3_D__227t_a_6I6j3_l__8_V___c__o_d_1e288Ods___5_ red, gc1___xD78_8_g_03gXare Roses vca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Q red, violets violetsvioletsvioletsvioletsviolets! Hope brow ._k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 terminal hue.forEUIn_dQ_YW4Dj4Uy_Hvkok3__4X4_v_48s4__93_KU3lp_1W3hEt_NDAP_G6PCJOg_a_gb6egLV_2m__mk_3f GxDV179___F.. 3 | violets are blue,Roses . youenjoy Hopeterminal hue 4 | But now...for my greatest trick.. 5 | .. Rosesfo... 6 | 3_, _p_8_46YYT_t_An_Q__809_2__c3EtW60bYo_qd7IQTN___Z6371_CduR__6eH6g3svB97qH_Q09 red. Hope you enjoy -------------------------------------------------------------------------------- /workdir/corpus/7e003a29799b04f21c11d22a7116890240ce648f-7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/7e003a29799b04f21c11d22a7116890240ce648f-7 -------------------------------------------------------------------------------- /workdir/corpus/7e5c0f7aba32cf3e22fd30c4513a21e6d1c3aeff-1: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /workdir/corpus/7f05e1cac5d7dc12e042dc83f3fcd10540786aa0-14: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/7f05e1cac5d7dc12e042dc83f3fcd10540786aa0-14 -------------------------------------------------------------------------------- /workdir/corpus/810a1da74529c06767b01440a72c6dc699980d12-2: -------------------------------------------------------------------------------- 1 | ᴇᴉᴔ�����ᴯᴇᴃᴔᴉᴇᴯᴇᴉᴔ�����ᴯᴇᴃᴔᴉᴇᴯᴉ -------------------------------------------------------------------------------- /workdir/corpus/843394c26a0b8a1afbc852a80de25c4344674e19-11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/843394c26a0b8a1afbc852a80de25c4344674e19-11 -------------------------------------------------------------------------------- /workdir/corpus/8652b884f91ad0b4fb9c96e91566338145317cd8-1: -------------------------------------------------------------------------------- 1 | ˙ɐᴉɐɐƃɐɯǝɹʇǝǝɹɐʇʇᴉᴉɔ�����ᴉɹɯǝʇɯᴉǝǝʇᴉǝƃᴉɔᴉᴉɐɹʇǝʇɔǝɔʇǝɯɐʇᴉɹɯᴉɯǝɹ˥˙Ɩ -------------------------------------------------------------------------------- /workdir/corpus/88e2ef96d27f9f5551d0f7121ea9fef541b0cc82-6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/88e2ef96d27f9f5551d0f7121ea9fef541b0cc82-6 -------------------------------------------------------------------------------- /workdir/corpus/8a80c7b3fca0bb837b5878469c083e41346299e0-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/8a80c7b3fca0bb837b5878469c083e41346299e0-1 -------------------------------------------------------------------------------- /workdir/corpus/8e589bb71b429ff4ceab0984ffbd3ceeb6e85011-10: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/8e589bb71b429ff4ceab0984ffbd3ceeb6e85011-10 -------------------------------------------------------------------------------- /workdir/corpus/8fbe7e9cf9117f680e8b25007cbb801c1593684b-8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/8fbe7e9cf9117f680e8b25007cbb801c1593684b-8 -------------------------------------------------------------------------------- /workdir/corpus/905388c50a25a80ea874f3328ba371639bbd4a2d: -------------------------------------------------------------------------------- 1 | Roses vca9_ohU__E9tR_7lO_t451__T_qt_CpId92kd__vVTj____O8q_Thm6UYnF_j9z8q_KlD3efICS0Q red, violets are . Hope you _k_11I_Hm26xl0Jv_UV1v_9__18uZ6C3_xti__74Zz32LlHZ55_fNO7 terminal hue 2 | But now violets _8 blue.for EUIn_dQ_YW4Dj4Uy_Hvkok3__4X4_v_48s4__93_KU3lp_1W3hEt_NDAP_G6PCJOg_a_gb6egLV_2m__mk_3f GxDV179___F.. 3 | brow. 4 | -------------------------------------------------------------------------------- /workdir/corpus/90de8387e74919a87a88c0e411803250b21ae5d1-6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/90de8387e74919a87a88c0e411803250b21ae5d1-6 -------------------------------------------------------------------------------- /workdir/corpus/9b0357bd16c8e8bb12415f70b25fc7600fcd6c7e-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/9b0357bd16c8e8bb12415f70b25fc7600fcd6c7e-1 -------------------------------------------------------------------------------- /workdir/corpus/9d52dc21f764e03cc5a7d24ca970cbe57458a350-11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/9d52dc21f764e03cc5a7d24ca970cbe57458a350-11 -------------------------------------------------------------------------------- /workdir/corpus/9edc07b314e9edbe831b71deefd8819e311bd61d-12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/9edc07b314e9edbe831b71deefd8819e311bd61d-12 -------------------------------------------------------------------------------- /workdir/corpus/a15024b390071dabc44a3fcc8f0501356408a55c-10: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/a15024b390071dabc44a3fcc8f0501356408a55c-10 -------------------------------------------------------------------------------- /workdir/corpus/a5c373199f21b4c3b2d329314170cbcc9df11e1f-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/a5c373199f21b4c3b2d329314170cbcc9df11e1f-1 -------------------------------------------------------------------------------- /workdir/corpus/aba83d00667befd41c1cddb7d272b353c183f249: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/aba83d00667befd41c1cddb7d272b353c183f249 -------------------------------------------------------------------------------- /workdir/corpus/aefc80f9a2a2e1ec680d32a66fb4830140fd4666-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/aefc80f9a2a2e1ec680d32a66fb4830140fd4666-1 -------------------------------------------------------------------------------- /workdir/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd-1 -------------------------------------------------------------------------------- /workdir/corpus/b6198e1a8df05a0a71f65d22325857f57600df52-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/b6198e1a8df05a0a71f65d22325857f57600df52-5 -------------------------------------------------------------------------------- /workdir/corpus/c0483379477f8160f07c21f13d327febaa32370c: -------------------------------------------------------------------------------- 1 | _4lpeW0_7W__w_1I4J0q_OD4179_4i0E_vs2S96z7_Z_p59PU_drd. op 68 ejoown ue 2 | 4J_135eMjc_8iw3f_3hDACUV_5R_z5ToxQQA__SME63S_0_4Of_h0_ztb888vis_QSlz_I__p_279_v4i0E_v9sS6z7_Z_p59PU_drd v_>..for my tic. 3 | are blu,ThkSY2BW6K6U_Sdnx_j4Ln___e_35301m_s__Q2Slz_I___279_v4i0E_v9s2S96z7_Z_p59PS_Dp__r_cWCo RosesX_a__DZC__6_643p_ZDeH_7R__r593PGrv_d7_T_legz3g6O8Q43_y_F_93kC_drd, are ,Thk SY2BWK6U_Sdnx_j4Ln___e__35301m_s_Q2Slz_I__p_279_v4i0Ev9s2S96z7_Z_p59PUS_Dp__r_cWCon _K2__GBr09_j__9L19 _ ejobown ue 4 | ... 5 | -------------------------------------------------------------------------------- /workdir/corpus/c0c60c25f055c947c193bd826651b56159d31590-13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/c0c60c25f055c947c193bd826651b56159d31590-13 -------------------------------------------------------------------------------- /workdir/corpus/c1cd8e1a9f61b8bb6d2c7a0d16684fa319469ba6-2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/c1cd8e1a9f61b8bb6d2c7a0d16684fa319469ba6-2 -------------------------------------------------------------------------------- /workdir/corpus/c3416d0c634908099dc74c3591d88c9ebce7a14e-13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/c3416d0c634908099dc74c3591d88c9ebce7a14e-13 -------------------------------------------------------------------------------- /workdir/corpus/c70223b639d696326ab1966c74285edf16f1805d-6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/c70223b639d696326ab1966c74285edf16f1805d-6 -------------------------------------------------------------------------------- /workdir/corpus/c753411f6386108552b2102db05df9cadd06d354-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/c753411f6386108552b2102db05df9cadd06d354-5 -------------------------------------------------------------------------------- /workdir/corpus/cb4613e292d1d4f4b57728d921cc62961a9c3810: -------------------------------------------------------------------------------- 1 | Roses are red, violets are blue. Hope you enjoy terminal -------------------------------------------------------------------------------- /workdir/corpus/cbaa4a2d9f5bf1416d3577fe06aedf1b79cef930-2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/cbaa4a2d9f5bf1416d3577fe06aedf1b79cef930-2 -------------------------------------------------------------------------------- /workdir/corpus/cdb2c5cc0ed74b1c41ef5a49d984a6b20e616dac-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/cdb2c5cc0ed74b1c41ef5a49d984a6b20e616dac-1 -------------------------------------------------------------------------------- /workdir/corpus/ce02a48292c4329435c60da7079db829fa1eeff7: -------------------------------------------------------------------------------- 1 | RosesX__a__DZC__6_643p_ZD_eH_7R__r593PGrv_d7_T_legz3g6O8Q43_y_F_9D3kC_drd, iolts are blu. op you ejobown ue 2 | _ht _ areblueU5I...for my tric.. 3 | Thk SY2BW6K6U_Sdnx_j4Ln___e__35301m_s__Q2Slz_I__p_279_v4i0E_v9s2S96z7_Z_p59PUS_Dp__r_cWCon fo... [Beeeep] 4 | -------------------------------------------------------------------------------- /workdir/corpus/cf3cb205ea5db39c17ae0ca20ee91c882e187f96-1: -------------------------------------------------------------------------------- 1 | ˙ɐnbᴉlɐ ɐuƃ ǹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ � -------------------------------------------------------------------------------- /workdir/corpus/d2b8a3d2cd0248f9e35014bfe4f598b6c4e45d7f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/d2b8a3d2cd0248f9e35014bfe4f598b6c4e45d7f -------------------------------------------------------------------------------- /workdir/corpus/d62fa5c1bd59beb6725232e3bc15b76c0b9f157e-11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/d62fa5c1bd59beb6725232e3bc15b76c0b9f157e-11 -------------------------------------------------------------------------------- /workdir/corpus/d8b276720792805e0add87fa45856abafa51f9cd-7: -------------------------------------------------------------------------------- 1 | 񊚊񊚊񚊊񊚊񊚊񊚚 -------------------------------------------------------------------------------- /workdir/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709-1 -------------------------------------------------------------------------------- /workdir/corpus/da3d70c98f1e4af8abebfb6256f2846b91a2e454-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/da3d70c98f1e4af8abebfb6256f2846b91a2e454-5 -------------------------------------------------------------------------------- /workdir/corpus/ddad8f6b96ce003a350ae1b7e42c507566f64625-2: -------------------------------------------------------------------------------- 1 | ˙ɉɐɐƃɐɯǝɹʇǝǝɹɐʇʉɉɹɯǝʇɉǝǝʉǝƉɉɉɹɯǝʇɉɐɹʇǝʇɘǝɔʇǝɯɐʉɹɉɉɐɐƃɐɯǝɹʇǝǝɯǝɹ˥˙Ɩ -------------------------------------------------------------------------------- /workdir/corpus/df68bdb3d0695420cde7c8475c49f41e440a8316-4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/df68bdb3d0695420cde7c8475c49f41e440a8316-4 -------------------------------------------------------------------------------- /workdir/corpus/e07be16e70a053e5fc0dcb2ad6b10fece26abb24-12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/e07be16e70a053e5fc0dcb2ad6b10fece26abb24-12 -------------------------------------------------------------------------------- /workdir/corpus/eb99a12e7859cd3fe2ba913d4ea7fbbec35a0479: -------------------------------------------------------------------------------- 1 | "Hello World! Hello, " 2 | -------------------------------------------------------------------------------- /workdir/corpus/f58c0efc6fc3f16130bb0e593d3175a98f67df54-5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/f58c0efc6fc3f16130bb0e593d3175a98f67df54-5 -------------------------------------------------------------------------------- /workdir/corpus/f7910ec2fa0c176e315ed1c2812c44da457b1f7f-3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/f7910ec2fa0c176e315ed1c2812c44da457b1f7f-3 -------------------------------------------------------------------------------- /workdir/corpus/fab43624b500191304bc9ee2571a135fd5e67b26-7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chewxy/skiprope/dd364399f485215b5f604872ff91a6ce541801fc/workdir/corpus/fab43624b500191304bc9ee2571a135fd5e67b26-7 -------------------------------------------------------------------------------- /workdir/corpus/hello.txt: -------------------------------------------------------------------------------- 1 | "Hello World! Hello, 世界" 2 | -------------------------------------------------------------------------------- /workdir/corpus/terminal_injection.txt: -------------------------------------------------------------------------------- 1 | Roses are red, violets are blue. Hope you enjoy terminal hue 2 | But now...for my greatest trick... 3 | Thk brown fo... [Beeeep] 4 | -------------------------------------------------------------------------------- /workdir/corpus/upsidedown.txt: -------------------------------------------------------------------------------- 1 | ˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥ 2 | 00˙Ɩ$- 3 | --------------------------------------------------------------------------------