Implementation notes Cut-detecting file cutter. ---------------------------------------------------------------------- 0. Concept A program that can survive lines being deleted, and changes behavior depending on which line was deleted. I had an idea to create something where the functionality depends on deleted lines while making Fern, but ended up creating a language with just one command. I was still in the mood of deleting stuff, so I thought this time I would make a less general entry with fewer functions, and those functions would depend on which lines get deleted. One obvious thing to try is a quine that is able to tolerate some deletion. There are at least two prior arts that I know of, for Ruby and Perl: https://mametter.hatenablog.com/entry/20140219/p1 https://shinh.hatenablog.com/entries/2014/02/20#1392834649 After some thought, I have concluded that C just doesn't have enough flexibility to survive arbitrary characters being deleted. Surviving lines being deleted is very easy, but I wasn't interested in making quines. Instead, I made a program that can perform multiple types of line-based cuts, and there is one character who would be perfect for the ASCII art layout. ---------------------------------------------------------------------- 1. Slash, head, and tail The first function to implement was a tool that would delete a single line of text (slash), and have the output be tools that delete a range of lines (head and tail). Deleting a single line versus deleting a range of lines are basically the same function, and easily implemented in very few lines if the line numbers are specified from start of file. Line numbers that start from end of file (used by "tail" tool) will require some buffer management, but it's mostly straightforward to implement, see slash.c, head.c, and tail.c. ---------------------------------------------------------------------- 2. Cut survivor Basic idea behind having a file that can survive a single line being deleted is to duplicate the important lines. Sometimes an exact duplicate is all we need, for example: #include #include But this doesn't work for most other lines. The submitted readme.md mentions a two-line pattern, but that's just to explain why we needed to use C99. In practice, we need a three-line pattern: original_line(); /* backup_line(); /*/ // */ There can be some overlap between line groups, so the expectation is that the original line count will double and not triple. But the increased line count is not really a problem, compared to the inconvenience that the parentheses and braces must remain balanced in the face of deletion. A more flexible structure is with a preprocessor macro that can optionally elide multiple lines at once, something like this: #ifdef PRESERVE_CODE #define CODE(...) __VA_ARGS__ #else #define CODE(...) /* nothing */ #endif CODE( some_number_of_lines(); ) But now each of those preprocessor lines must also be doubled to ensure that the preprocessor directives survive deletion, so there is some tradeoff to decide which pattern to use. For this project, the "tail" proportion of the code is inside a macro, while most other code uses the trailing-comment-and-backup-line pattern. Applying the redundancy patterns to code and having the result fit the desired template took a fair bit of effort, but the patterns themselves probably aren't that difficult to come by. I expect anyone who has attempted this exercise of writing cut-surviving code will eventually discover similar patterns simply by tweaking comment characters in any text editor with syntax highlighting. ---------------------------------------------------------------------- 3. Cut detector Writing code that survives deletions is only half the problem, the other half is detecting which lines were deleted, since we want to use the deleted line number to select "head" or "tail" operation modes. The selection mode I settled on was to detect deletes in either the "head" region or the "tail" region of the code, i.e. removing a single line in one of two contiguous regions. Deletions in the head region is detected by comparing value of __LINE__ embedded in line 41 of the final code, while deletions in the tail region are detected by using a chain of tailing "/*/" comments, such that the final #define is dropped if anything between lines 42-62 is deleted. I arrived at this arrangement after lots of experiments with tweaking preprocessor lines, you can see a sample of these in cut_detector*.c I had more ambitious plans early on where I would detect whether the deleted line number was odd or even, but the overhead of implementing that kind of detector costs too much code. Detecting whether the deleted line number was prime or composite or one is a much cheaper operation (because prime numbers are spread further apart), but not that much cheaper, so I settled on two contiguous regions. ---------------------------------------------------------------------- 4. Prime classifier Even though I have settled on a relatively simple scheme of detecting deletes in two contiguous regions, detecting whether the deleted line number is prime just seem like such a fun idea, I had to implement it. Once I figured out the general pattern for detecting deleted lines, this prime detector is just a simpler application of the same methods. In fact, it's so much easier to implement this prime detector that I made one that does all the work at compile time. Detecting deletes to the first line takes some thought, but all the remaining bits are fairly straightforward. Evolution of my prime detector can be found in prime_cut*.c ---------------------------------------------------------------------- 5. Chop and fill Because of all the extra comments needed to fill backup lines, I ended up with a fair bit of spare space. Those were filled by putting in three extra programs: - A Ruby program for deleting range of lines, complementing the functionalities of "head" and "tail". I knew I would have space leftover, so I reserved some lines at the beginning where I could insert Ruby code header inside a multi-line preprocessor statement, and interleave the remaining Ruby code inside "j" and "`" pairs. - A Perl program for outputting an UTF-8 scissor. Perl works great with Ruby as I learned while working on Kurumi, so I was able to squeeze in some Perl without costing too many extra bytes. - A brainfuck program for outputting an ASCII art scissor. Brainfuck is usually my go-to language to fill in any remaining bytes. ---------------------------------------------------------------------- 6. Finally... I worked on Ubel from January to February, just before PlayJam 9 started. It was a solid month of work, mostly because I had to restart the layout process twice. Ubel won the "compound prize" award. I am glad judges appreciate all the layers that went into it. This would be my third IOCCC entry for 2025, after Fern and Kurumi. I didn't have time to do a 4th one due to PlayJam. I am so happy that all 3 of them won awards :)