There are still some known bugs in INTERCAL (and no doubt many we don’t even suspect as yet). The following list accumulates current bug reports and TO DO agenda items through the current release.
ESR = Eric S. Raymond SS = Steve Swales LHH = Louis Howell BLR = Brian Raiter AIS = Alex Smith
Known Bugs
-
(ESR) INTERCAL would be intrinsically a crock even if it worked right.
-
(BLR) I have hacked up a fix to the problem with line numbers being off due to statements sharing/spanning lines. The parser now stores the starting line number for all tuples, not just the splatted ones. Not only does this give synchronized lines in the comments in the degenerated C code, but run-time error messages are just as reliable as the parse/compile-time ones. The line number in the error message is actually the line number of the next statement (e.g., if the next statement is on the same line, the line number of both statements will be used).
Note that I said "just as reliable," as opposed to "fully reliable". Since the lexer doesn't know where the line ends until after it's already parsed the preamble (DO-PLEASE combo, line label, oh-oh-seven number, etc) of the following line, it actually stores as the starting line number the first non-whitespace of the statement proper. So if a statement has its preamble on one line and the rest of it on the next, the preamble will be beheaded.Still, it's a vast improvement, and as LHH pointed out, it's not going to get much better without writing a preprocessing lexer that can actually separate out the statements before any real parsing begins. Until someone wants to do that, this will do nicely, I think.As a side bonus, splatting now shows the entire statement. (Er, except when the statement both spans and shares a line. If a multi- line statement ends on the same line as the beginning of the next statement, the last part will get truncated. Again, not a case we want to be encouraging people to explore anyway.) -
(BLR) Interleave does not check the type of its arguments. The manual insinuates that interleaving 32-bit values should result in error 533; instead, C-INTERCAL silently truncates them to 16 bits. Unfortunately, the modified behavior of select’s return type depends on this, so there’s not much we can do about it as things stand.
-
(AIS) Reverse assignments (those with the expression on the left) are very unlikely to work in bases other than 2. (The current code calculates what the answer would be in base 2 and throws an error if it isn’t correct for the current base.)
-
(AIS) Some of the newer features in intercal.el are not yet fully implemented and have a tendency to print Unimplemented rather than doing anything useful.
-
(AIS) The external call system only works with gcc (due to its need to pass command-line arguments like -x and -E to it), and fails when mixed with come-from-gerund and next-from-gerund.
To Do
-
(ESR) Add more optimization templates, esp. idioms for +, -, *, /.
-
(ESR) Forget this @!%$#! crock and take a long vacation.
Found by AI =
## Code Audit by ChatGPT 5.6: C-INTERCAL Compiler (v0.34)
### Critical Bugs
src/ick_ec.h — Macro label collision risk
src/ick_ec.h uses labels like ick_l1_ICK_EC_PP_1, ick_l2_ICK_EC_PP_2,
ick_l6_ICK_EC_PP_6 generated from macro parameters. If the id parameter contains
characters that produce invalid C identifiers, or if two invocations produce the same
label, this breaks. The ICK_EC_PP_6 reference in ick_startup hardcodes 6 but the label
name uses the macro parameter id, creating a potential mismatch.
src/ick_lose.c — exit(atoi(m)) has no error handling
src/ick_lose.c:48
exit(atoi(m));
atoi returns 0 on failure and has no way to distinguish "the string starts with 0" from
"the string is not a number". If a message string is corrupted or truncated, the exit
code will be silently wrong.
src/uncommon.c — Static buffer not thread-safe
src/uncommon.c:52,100
Both ick_findandfopen() and ick_findandtestopen() use static char buf2[BUFSIZ]. If the
compiler is used from a multithreaded context (e.g., as a library), concurrent calls
will corrupt each other's buffers.
src/lexer.l — getc() override breaks FILE* semantics
src/lexer.l:100-168
The lexer overrides getc() globally. This means any code calling getc() will
inadvertently use the lexer's custom getc() that handles INTERCAL line continuation.
This is a dangerous global side effect. The correct approach would be to use fgetc()
with a custom FILE* or rename the override.
src/unravel.c — Heavy setjmp/longjmp abuse
src/unravel.c uses setjmp/longjmp extensively to implement multithreading and computed
COME FROM. The comments acknowledge this:
> "setjmp/longjmp is worse than goto in terms of spaghettification of code."
Jumping into the middle of uninitialized stack frames in degenerated C code (which
didn't exist when libickmt.a was compiled) is undefined behavior on many platforms. The
longjmp to a target that hasn't executed the corresponding setjmp is specifically
listed as UB in C11 7.13.2.1.
### Code Quality Issues
src/dekludge.c — Magic constants in abstainmatch()
src/dekludge.c:112-125
static int abstainmatch(int npconstant, int tptype)
{
if(npconstant == tptype) return 1;
if(npconstant == ABSTAIN)
if(tptype == FROM || tptype == MANYFROM || tptype == DISABLE) return 1;
...
}
The constants ABSTAIN, REINSTATE, GETS, FROM, MANYFROM, DISABLE, ENABLE, RESIZE,
UNKNOWN, SPLATTERED are magic numbers with no inline documentation of their values.
src/ick_ec.h — Complex goto macro patterns are unmaintainable
The ICK_EC_FUNC_START/ICK_EC_FUNC_END macros and related macros (ick_linelabel,
ick_comefrom, ick_nextfrom, etc.) use goto-based control flow that is extremely
difficult to reason about. The labels are generated from macro parameters, making them
hard to debug. A cleaner approach would use function pointers or state machines.
src/feh2.c — emit() function is overly complex
The emit() function in src/feh2.c is ~1500 lines and handles code generation for all
INTERCAL statement types in a single massive switch. This makes it hard to verify
correctness for any single statement type.
src/oil.y — Memory leak on parser error
src/oil.y uses BINARYEXPR and UNARYEXPR macros that call malloc for each node in the
parse tree. On parser error paths, treefree() is called, but the tofree/tfi mechanism
(used for lexer-allocated strings) is not cleaned up if the parser fails mid-way.
src/pick2.h — ick_roll() is a terrible randomizer
src/pick2.h:116
ICK_INT1 ick_roll(ICK_INT8 n)
{
return n>50;
}
This returns a pseudo-random value based solely on the input parameter n > 50. It has
no internal state, so calling it with the same argument always returns the same result.
This is likely intentional for PIC simplicity, but it should be documented.
### Suggestions for Improvement
Priority Area Suggestion
────────── ────────────────────────── ───────────────────────────────────────────────
High ick_ec.h macros Replace goto-based macros with state-machine
or function pointer approach
────────── ────────────────────────── ───────────────────────────────────────────────
Medium unravel.c longjmp Document which platforms are supported and
add runtime guards
────────── ────────────────────────── ───────────────────────────────────────────────
Medium getc() override Rename to ick_getc() or use a custom FILE*
wrapper
────────── ────────────────────────── ───────────────────────────────────────────────
Medium pick2.h typo Fix syslipopt1500 → syslibopt1500
────────── ────────────────────────── ───────────────────────────────────────────────
Medium abstainmatch() Add a comment table mapping constants to
values
────────── ────────────────────────── ───────────────────────────────────────────────
Low emit() complexity Split into per-statement-type helper
functions
────────── ────────────────────────── ───────────────────────────────────────────────
Low oil.y error cleanup Add explicit error path that frees the parse
tree
────────── ────────────────────────── ───────────────────────────────────────────────
Low ick_roll() documentation Document that it's deterministic for a given
input