We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

Left-Right March/C Interpreter

From Esolang
Jump to navigation Jump to search
#define _GNU_SOURCE
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdbit.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct
{
    char *content; // Not null terminated!
    size_t size;
} field = {NULL, 0};
struct
{
    char *pos;
    enum
    {
        LEFT = -1,
        RIGHT = 1,
    } dir;
} field_ptr;
uint_fast8_t A;

void prog_error(const char *err_msg)
{
    fprintf(stderr, "-----\nERROR\n-----\n%s\n", err_msg);
    assert(field.content <= field_ptr.pos && field_ptr.pos < field.content + field.size);
    ptrdiff_t fp_index = field_ptr.pos - field.content;
    if (field_ptr.dir == LEFT && fp_index < 2)
    {
        for (ptrdiff_t i = 2; i > fp_index; i--)
        {
            fputc(' ', stderr);
        }
    }
    fwrite(field.content, sizeof(*field.content), field.size, stderr);
    fputc('\n', stderr);
    switch (field_ptr.dir)
    {
    case LEFT:
        for (const char *i = field.content; i < field_ptr.pos - 2; i++)
        {
            fputc(' ', stderr);
        }
        fputs("<<^", stderr);
        break;
    case RIGHT:
        for (const char *i = field.content; i < field_ptr.pos; i++)
        {
            fputc(' ', stderr);
        }
        fputs("^>>", stderr);
        break;
    default:
        unreachable();
    }
    free(field.content);
    fprintf(stderr, "\nA = %.2hhu\n", A);
}

void printLSCEF(uint_least8_t c)
{
    assert(c < 100);
    if (c >= 98)
    {
        prog_error("Invalid character index");
        exit(EXIT_FAILURE);
    }

    if (c == 66)
    {
        puts("\u00A3");
    }
    else
    {
        static const char chars[98] = "\n ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\"\x00$%^&*(){}[]-=_+;'#~@:,./?><\\|`\t";
        putchar(chars[c]);
    }
}

uint_least8_t inputLSCEF(void)
{
begin:
    char ascii = getchar();
    if (feof(stdin))
    {
        return 99;
    }
    else if (ascii & 128)
    {
        char utf8[5] = {ascii};
        utf8[fread(utf8 + 1, sizeof(char), stdc_leading_ones_uc(ascii), stdin) + 1] = 0;
        static const char pound[] = "\u00A3";
        if (strcmp(utf8, pound))
        {
            fprintf(stderr, "Cannot encode %s into LSCEF", utf8);
            goto begin;
        }
        else
        {
            return 66;
        }
    }
    else
    {
        static const uint_least8_t values[128] = {98, 98, 98, 98, 98, 98, 98, 98, 98, 97, 0, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 1, 64, 65, 84, 67, 68, 70, 83, 72, 73, 71, 81, 88, 78, 89, 90, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 87, 82, 93, 79, 92, 91, 86, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 76, 94, 77, 69, 80, 96, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 74, 95, 75, 85, 98}; // Reverse lookup for LSCEF
        uint_least8_t ret = values[(unsigned)ascii];
        if (ret == 98)
        {
            fprintf(stderr, "Cannot encode %c into LSCEF", ascii);
            goto begin;
        }
        else
        {
            return ret;
        }
    }
}

int main(int argc, const char *const *argv)
{
    // File handling
    if (argc < 2)
    {
        fputs("Specify the file to interperet.\n", stderr);
        return EXIT_FAILURE;
    }
    FILE *f = fopen(argv[1], "r");
    if (f == NULL)
    {
        perror("Could not access the requested file");
        return EXIT_FAILURE;
    }

    // Load the program
    {
        size_t dummy = 0;
        field.size = getline(&field.content, &dummy, f);
    }
    if (ferror(f))
    {
        fclose(f);
        free(field.content);
        perror(NULL);
        return EXIT_FAILURE;
    }
    else if (feof(f) && field.size == (size_t)-1)
    {
        field.size = 0;
    }
    fclose(f);
    if (field.content[field.size] == '\n')
    {
        field.size--;
    }
    field.content = realloc(field.content, field.size);
    for (const char *i = field.content; i < field.content + field.size; i++)
    {
        if (*i & 128)
        {
            free(field.content);
            fputs("Non\u2010ASCII characters are not allowed in source code.\n", stderr);
            return EXIT_FAILURE;
        }
    }

    // Execute the program
    field_ptr.pos = field.content;
    field_ptr.dir = RIGHT;
    while (field_ptr.pos >= field.content && field_ptr.pos < field.content + field.size)
    {
        if (isdigit(*field_ptr.pos))
        {
            field_ptr.pos += field_ptr.dir;
            continue;
        }
        if (!(isdigit(field_ptr.pos[field_ptr.dir]) && isdigit(field_ptr.pos[2 * field_ptr.dir])) || field_ptr.pos + 2 * field_ptr.dir < field.content || field_ptr.pos + 2 * field_ptr.dir >= field.content + field.size)
        {
            prog_error("Invalid number for command");
            return EXIT_FAILURE;
        }
        uint_fast8_t F;
        switch (field_ptr.dir)
        {
        case LEFT:
            F = 10 * (field_ptr.pos[-2] - '0') + (field_ptr.pos[-1] - '0');
            break;
        case RIGHT:
            F = 10 * (field_ptr.pos[1] - '0') + (field_ptr.pos[2] - '0');
            break;
        default:
            unreachable();
        }
        assert(A < 100 && F < 100);
        switch (*field_ptr.pos)
        {
        case 'a':
            A = (A + F) % 100;
            break;
        case 'b':
            if (A)
            {
                field_ptr.pos += (F - 2) * field_ptr.dir; // Step-on-3s optimization requires this
            }
            break;
        case 'c':
            A = F;
            break;
        case 'd':
            if (!F)
            {
                prog_error("Division by 0");
                return EXIT_FAILURE;
            }
            A /= F;
            break;
        case 'i':
            A = inputLSCEF();
            break;
        case 'j':
            field_ptr.pos += (F - 2) * field_ptr.dir; // Step-on-3s optimization requires this
            break;
        case 'l':
            field_ptr.dir = LEFT;
            field_ptr.pos += 2; // Step-on-3s optimization requires this
            break;
        case 'm':
            A = (A * F) % 100;
            break;
        case 'p':
            printLSCEF(A);
            break;
        case 'r':
            field_ptr.dir = RIGHT;
            field_ptr.pos -= 2; // Step-on-3s optimization requires this
            break;
        case 's':
            A = (A - F + 100) % 100;
            break;
        case 'w':
            switch (field_ptr.dir)
            {
            case LEFT:
                field_ptr.pos[-2] = A / 10;
                field_ptr.pos[-1] = A % 10;
                break;
            case RIGHT:
                field_ptr.pos[1] = A / 10;
                field_ptr.pos[2] = A % 10;
                break;
            default:
                unreachable();
            }
            break;
        default:
            prog_error("Unknown command");
            return EXIT_FAILURE;
        }
        field_ptr.pos += 3 * field_ptr.dir; // Digits used by this instruction are guaranteed to NOP, so skip them entirely
    }

    free(field.content);
    return EXIT_SUCCESS;
}