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<stdio.h>
   #include<stdio.h>

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 :)
