root/usr.bin/dc/bcode.c
/*      $OpenBSD: bcode.c,v 1.65 2025/12/15 12:10:46 tb Exp $   */

/*
 * Copyright (c) 2003, Otto Moerbeek <otto@drijf.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <err.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "extern.h"

/* #define      DEBUGGING */

#define MAX_ARRAY_INDEX         2048
#define READSTACK_SIZE          8

#define NO_ELSE                 -2      /* -1 is EOF */
#define REG_ARRAY_SIZE_SMALL    (UCHAR_MAX + 1)
#define REG_ARRAY_SIZE_BIG      (UCHAR_MAX + 1 + USHRT_MAX + 1)

struct bmachine {
        struct stack            stack;
        u_int                   scale;
        u_int                   obase;
        u_int                   ibase;
        size_t                  readsp;
        bool                    extended_regs;
        size_t                  reg_array_size;
        struct stack            *reg;
        volatile sig_atomic_t   interrupted;
        struct source           *readstack;
        size_t                  readstack_sz;
        BN_CTX                  *ctx;
};

static struct bmachine  bmachine;
static void sighandler(int);

static __inline int     readch(void);
static __inline void    unreadch(void);
static __inline char    *readline(void);
static __inline void    src_free(void);

static __inline u_int   max(u_int, u_int);
static u_long           get_ulong(struct number *);

static __inline void    push_number(struct number *);
static __inline void    push_string(char *);
static __inline void    push(struct value *);
static __inline struct value *tos(void);
static __inline struct number   *pop_number(void);
static __inline char    *pop_string(void);
static void             clear_stack(void);
static void             print_tos(void);
static void             print_err(void);
static void             pop_print(void);
static void             pop_printn(void);
static void             print_stack(void);
static void             dup(void);
static void             swap(void);
static void             drop(void);

static void             get_scale(void);
static void             set_scale(void);
static void             get_obase(void);
static void             set_obase(void);
static void             get_ibase(void);
static void             set_ibase(void);
static void             stackdepth(void);
static void             push_scale(void);
static u_int            count_digits(const struct number *);
static void             num_digits(void);
static void             to_ascii(void);
static void             push_line(void);
static void             comment(void);
static void             badd(void);
static void             bsub(void);
static void             bmul(void);
static void             bdiv(void);
static void             bmod(void);
static void             bdivmod(void);
static void             bexp(void);
static void             bsqrt(void);
static void             not(void);
static void             equal_numbers(void);
static void             less_numbers(void);
static void             lesseq_numbers(void);
static void             equal(void);
static void             less(void);
static void             greater(void);
static void             not_compare(void);
static bool             compare_numbers(enum bcode_compare, struct number *,
                            struct number *);
static void             compare(enum bcode_compare);
static int              readreg(void);
static void             load(void);
static void             store(void);
static void             load_stack(void);
static void             store_stack(void);
static void             load_array(void);
static void             store_array(void);
static void             nop(void);
static void             quit(void);
static void             quitN(void);
static void             skipN(void);
static void             skip_until_mark(void);
static void             parse_number(void);
static void             unknown(void);
static void             eval_string(char *);
static void             eval_line(void);
static void             eval_tos(void);


typedef void            (*opcode_function)(void);

struct jump_entry {
        u_char          ch;
        opcode_function f;
};


static opcode_function jump_table[UCHAR_MAX + 1];

static const struct jump_entry jump_table_data[] = {
        { ' ',  nop             },
        { '!',  not_compare     },
        { '#',  comment         },
        { '%',  bmod            },
        { '(',  less_numbers    },
        { '*',  bmul            },
        { '+',  badd            },
        { '-',  bsub            },
        { '.',  parse_number    },
        { '/',  bdiv            },
        { '0',  parse_number    },
        { '1',  parse_number    },
        { '2',  parse_number    },
        { '3',  parse_number    },
        { '4',  parse_number    },
        { '5',  parse_number    },
        { '6',  parse_number    },
        { '7',  parse_number    },
        { '8',  parse_number    },
        { '9',  parse_number    },
        { ':',  store_array     },
        { ';',  load_array      },
        { '<',  less            },
        { '=',  equal           },
        { '>',  greater         },
        { '?',  eval_line       },
        { 'A',  parse_number    },
        { 'B',  parse_number    },
        { 'C',  parse_number    },
        { 'D',  parse_number    },
        { 'E',  parse_number    },
        { 'F',  parse_number    },
        { 'G',  equal_numbers   },
        { 'I',  get_ibase       },
        { 'J',  skipN           },
        { 'K',  get_scale       },
        { 'L',  load_stack      },
        { 'M',  nop             },
        { 'N',  not             },
        { 'O',  get_obase       },
        { 'P',  pop_print       },
        { 'Q',  quitN           },
        { 'R',  drop            },
        { 'S',  store_stack     },
        { 'X',  push_scale      },
        { 'Z',  num_digits      },
        { '[',  push_line       },
        { '\f', nop             },
        { '\n', nop             },
        { '\r', nop             },
        { '\t', nop             },
        { '^',  bexp            },
        { '_',  parse_number    },
        { 'a',  to_ascii        },
        { 'c',  clear_stack     },
        { 'd',  dup             },
        { 'e',  print_err       },
        { 'f',  print_stack     },
        { 'i',  set_ibase       },
        { 'k',  set_scale       },
        { 'l',  load            },
        { 'n',  pop_printn      },
        { 'o',  set_obase       },
        { 'p',  print_tos       },
        { 'q',  quit            },
        { 'r',  swap            },
        { 's',  store           },
        { 'v',  bsqrt           },
        { 'x',  eval_tos        },
        { 'z',  stackdepth      },
        { '{',  lesseq_numbers  },
        { '~',  bdivmod         }
};

#ifndef nitems
#define nitems(a)       (sizeof((a)) / sizeof((a)[0]))
#endif

static void
sighandler(int ignored)
{
        bmachine.interrupted = true;
}

void
init_bmachine(bool extended_registers)
{
        int i;

        bmachine.ctx = BN_CTX_new();
        bn_checkp(bmachine.ctx);

        bmachine.extended_regs = extended_registers;
        bmachine.reg_array_size = bmachine.extended_regs ?
            REG_ARRAY_SIZE_BIG : REG_ARRAY_SIZE_SMALL;

        bmachine.reg = calloc(bmachine.reg_array_size,
            sizeof(bmachine.reg[0]));
        if (bmachine.reg == NULL)
                err(1, NULL);

        for (i = 0; i < nitems(jump_table); i++)
                jump_table[i] = unknown;

        for (i = 0; i < nitems(jump_table_data); i++) {
                if ((unsigned int)jump_table_data[i].ch >= nitems(jump_table))
                        errx(1, "opcode '%c' overflows jump table",
                            jump_table_data[i].ch);
                if (jump_table[jump_table_data[i].ch] != unknown)
                        errx(1, "opcode '%c' already assigned",
                            jump_table_data[i].ch);
                jump_table[jump_table_data[i].ch] = jump_table_data[i].f;
        }

        stack_init(&bmachine.stack);

        for (i = 0; i < bmachine.reg_array_size; i++)
                stack_init(&bmachine.reg[i]);

        bmachine.readstack_sz = READSTACK_SIZE;
        bmachine.readstack = calloc(sizeof(struct source),
            bmachine.readstack_sz);
        if (bmachine.readstack == NULL)
                err(1, NULL);
        bmachine.obase = bmachine.ibase = 10;
        (void)signal(SIGINT, sighandler);
}

u_int
bmachine_scale(void)
{
        return bmachine.scale;
}

/* Reset the things needed before processing a (new) file */
void
reset_bmachine(struct source *src)
{
        bmachine.readsp = 0;
        bmachine.readstack[0] = *src;
}

static __inline int
readch(void)
{
        struct source *src = &bmachine.readstack[bmachine.readsp];

        return src->vtable->readchar(src);
}

static __inline void
unreadch(void)
{
        struct source *src = &bmachine.readstack[bmachine.readsp];

        src->vtable->unreadchar(src);
}

static __inline char *
readline(void)
{
        struct source *src = &bmachine.readstack[bmachine.readsp];

        return src->vtable->readline(src);
}

static __inline void
src_free(void)
{
        struct source *src = &bmachine.readstack[bmachine.readsp];

        src->vtable->free(src);
}

#ifdef DEBUGGING
void
pn(const char *str, const struct number *n)
{
        char *p = BN_bn2dec(n->number);
        if (p == NULL)
                err(1, "BN_bn2dec failed");
        (void)fputs(str, stderr);
        (void)fprintf(stderr, " %s (%u)\n" , p, n->scale);
        OPENSSL_free(p);
}

void
pbn(const char *str, const BIGNUM *n)
{
        char *p = BN_bn2dec(n);
        if (p == NULL)
                err(1, "BN_bn2dec failed");
        (void)fputs(str, stderr);
        (void)fprintf(stderr, " %s\n", p);
        OPENSSL_free(p);
}

#endif

static __inline u_int
max(u_int a, u_int b)
{
        return a > b ? a : b;
}

static unsigned long factors[] = {
        0, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
        100000000, 1000000000
};

void
scale_number(BIGNUM *n, int s)
{
        int abs_scale;

        if (s == 0)
                return;

        abs_scale = s > 0 ? s : -s;

        if (abs_scale < nitems(factors)) {
                if (s > 0)
                        bn_check(BN_mul_word(n, factors[abs_scale]));
                else
                        (void)BN_div_word(n, factors[abs_scale]);
        } else {
                BIGNUM *a, *p;

                a = BN_new();
                bn_checkp(a);
                p = BN_new();
                bn_checkp(p);

                bn_check(BN_set_word(a, 10));
                bn_check(BN_set_word(p, abs_scale));
                bn_check(BN_exp(a, a, p, bmachine.ctx));
                if (s > 0)
                        bn_check(BN_mul(n, n, a, bmachine.ctx));
                else
                        bn_check(BN_div(n, NULL, n, a, bmachine.ctx));
                BN_free(a);
                BN_free(p);
        }
}

void
split_number(const struct number *n, BIGNUM *i, BIGNUM *f)
{
        u_long rem;

        bn_checkp(BN_copy(i, n->number));

        if (n->scale == 0) {
                if (f != NULL)
                        bn_check(BN_set_word(f, 0));
        } else if (n->scale < nitems(factors)) {
                rem = BN_div_word(i, factors[n->scale]);
                if (f != NULL)
                        bn_check(BN_set_word(f, rem));
        } else {
                BIGNUM *a, *p;

                a = BN_new();
                bn_checkp(a);
                p = BN_new();
                bn_checkp(p);

                bn_check(BN_set_word(a, 10));
                bn_check(BN_set_word(p, n->scale));
                bn_check(BN_exp(a, a, p, bmachine.ctx));
                bn_check(BN_div(i, f, n->number, a, bmachine.ctx));
                BN_free(a);
                BN_free(p);
        }
}

void
normalize(struct number *n, u_int s)
{
        scale_number(n->number, s - n->scale);
        n->scale = s;
}

static u_long
get_ulong(struct number *n)
{
        normalize(n, 0);
        return BN_get_word(n->number);
}

void
negate(struct number *n)
{
        BN_set_negative(n->number, !BN_is_negative(n->number));
}

static __inline void
push_number(struct number *n)
{
        stack_pushnumber(&bmachine.stack, n);
}

static __inline void
push_string(char *string)
{
        stack_pushstring(&bmachine.stack, string);
}

static __inline void
push(struct value *v)
{
        stack_push(&bmachine.stack, v);
}

static __inline struct value *
tos(void)
{
        return stack_tos(&bmachine.stack);
}

static __inline struct value *
pop(void)
{
        return stack_pop(&bmachine.stack);
}

static __inline struct number *
pop_number(void)
{
        return stack_popnumber(&bmachine.stack);
}

static __inline char *
pop_string(void)
{
        return stack_popstring(&bmachine.stack);
}

static void
clear_stack(void)
{
        stack_clear(&bmachine.stack);
}

static void
print_stack(void)
{
        stack_print(stdout, &bmachine.stack, "", bmachine.obase);
}

static void
print_tos(void)
{
        struct value *value = tos();
        if (value != NULL) {
                print_value(stdout, value, "", bmachine.obase);
                (void)putchar('\n');
        } else
                warnx("stack empty");
}

static void
print_err(void)
{
        struct value *value = tos();
        if (value != NULL) {
                print_value(stderr, value, "", bmachine.obase);
                (void)putc('\n', stderr);
        } else
                warnx("stack empty");
}

static void
pop_print(void)
{
        struct value *value = pop();

        if (value != NULL) {
                switch (value->type) {
                case BCODE_NONE:
                        break;
                case BCODE_NUMBER:
                        normalize(value->u.num, 0);
                        print_ascii(stdout, value->u.num);
                        (void)fflush(stdout);
                        break;
                case BCODE_STRING:
                        (void)fputs(value->u.string, stdout);
                        (void)fflush(stdout);
                        break;
                }
                stack_free_value(value);
        }
}

static void
pop_printn(void)
{
        struct value *value = pop();

        if (value != NULL) {
                print_value(stdout, value, "", bmachine.obase);
                (void)fflush(stdout);
                stack_free_value(value);
        }
}

static void
dup(void)
{
        stack_dup(&bmachine.stack);
}

static void
swap(void)
{
        stack_swap(&bmachine.stack);
}

static void
drop(void)
{
        struct value *v = pop();
        if (v != NULL)
                stack_free_value(v);
}

static void
get_scale(void)
{
        struct number   *n;

        n = new_number();
        bn_check(BN_set_word(n->number, bmachine.scale));
        push_number(n);
}

static void
set_scale(void)
{
        struct number   *n;
        u_long          scale;

        n = pop_number();
        if (n != NULL) {
                if (BN_is_negative(n->number))
                        warnx("scale must be a nonnegative number");
                else {
                        scale = get_ulong(n);
                        if (scale != (BN_ULONG)-1 && scale <= UINT_MAX)
                                bmachine.scale = (u_int)scale;
                        else
                                warnx("scale too large");
                }
                free_number(n);
        }
}

static void
get_obase(void)
{
        struct number   *n;

        n = new_number();
        bn_check(BN_set_word(n->number, bmachine.obase));
        push_number(n);
}

static void
set_obase(void)
{
        struct number   *n;
        u_long          base;

        n = pop_number();
        if (n != NULL) {
                base = get_ulong(n);
                if (base != (BN_ULONG)-1 && base > 1 && base <= UINT_MAX)
                        bmachine.obase = (u_int)base;
                else
                        warnx("output base must be a number greater than 1");
                free_number(n);
        }
}

static void
get_ibase(void)
{
        struct number *n;

        n = new_number();
        bn_check(BN_set_word(n->number, bmachine.ibase));
        push_number(n);
}

static void
set_ibase(void)
{
        struct number   *n;
        u_long          base;

        n = pop_number();
        if (n != NULL) {
                base = get_ulong(n);
                if (base != (BN_ULONG)-1 && 2 <= base && base <= 16)
                        bmachine.ibase = (u_int)base;
                else
                        warnx("input base must be a number between 2 and 16 "
                            "(inclusive)");
                free_number(n);
        }
}

static void
stackdepth(void)
{
        size_t i;
        struct number *n;

        i = stack_size(&bmachine.stack);
        n = new_number();
        bn_check(BN_set_word(n->number, i));
        push_number(n);
}

static void
push_scale(void)
{
        struct value    *value;
        u_int           scale = 0;
        struct number   *n;

        value = pop();
        if (value != NULL) {
                switch (value->type) {
                case BCODE_NONE:
                        return;
                case BCODE_NUMBER:
                        scale = value->u.num->scale;
                        break;
                case BCODE_STRING:
                        break;
                }
                stack_free_value(value);
                n = new_number();
                bn_check(BN_set_word(n->number, scale));
                push_number(n);
        }
}

static u_int
count_digits(const struct number *n)
{
        BIGNUM          *int_part, *a, *p;
        uint            d;
        const uint64_t  c = 1292913986; /* floor(2^32 * log_10(2)) */
        int             bits;

        if (BN_is_zero(n->number))
                return n->scale;

        int_part = BN_new();
        bn_checkp(int_part);

        split_number(n, int_part, NULL);
        bits = BN_num_bits(int_part);

        if (bits == 0)
                d = 0;
        else {
                /*
                 * Estimate number of decimal digits based on number of bits.
                 * Divide 2^32 factor out by shifting
                 */
                d = (c * bits) >> 32;

                /* If close to a possible rounding error fix if needed */
                if (d != (c * (bits - 1)) >> 32) {
                        a = BN_new();
                        bn_checkp(a);
                        p = BN_new();
                        bn_checkp(p);

                        bn_check(BN_set_word(a, 10));
                        bn_check(BN_set_word(p, d));
                        bn_check(BN_exp(a, a, p, bmachine.ctx));

                        if (BN_ucmp(int_part, a) >= 0)
                                d++;

                        BN_free(a);
                        BN_free(p);
                } else
                        d++;
        }

        BN_free(int_part);

        return d + n->scale;
}

static void
num_digits(void)
{
        struct value    *value;
        size_t          digits;
        struct number   *n = NULL;

        value = pop();
        if (value != NULL) {
                switch (value->type) {
                case BCODE_NONE:
                        return;
                case BCODE_NUMBER:
                        digits = count_digits(value->u.num);
                        n = new_number();
                        bn_check(BN_set_word(n->number, digits));
                        break;
                case BCODE_STRING:
                        digits = strlen(value->u.string);
                        n = new_number();
                        bn_check(BN_set_word(n->number, digits));
                        break;
                }
                stack_free_value(value);
                push_number(n);
        }
}

static void
to_ascii(void)
{
        char            str[2];
        struct value    *value;
        struct number   *n;

        value = pop();
        if (value != NULL) {
                str[1] = '\0';
                switch (value->type) {
                case BCODE_NONE:
                        return;
                case BCODE_NUMBER:
                        n = value->u.num;
                        normalize(n, 0);
                        if (BN_num_bits(n->number) > 8)
                                bn_check(BN_mask_bits(n->number, 8));
                        str[0] = (char)BN_get_word(n->number);
                        break;
                case BCODE_STRING:
                        str[0] = value->u.string[0];
                        break;
                }
                stack_free_value(value);
                push_string(bstrdup(str));
        }
}

static int
readreg(void)
{
        int idx, ch1, ch2;

        idx = readch();
        if (idx == 0xff && bmachine.extended_regs) {
                ch1 = readch();
                ch2 = readch();
                if (ch1 == EOF || ch2 == EOF) {
                        warnx("unexpected eof");
                        idx = -1;
                } else
                        idx = (ch1 << 8) + ch2 + UCHAR_MAX + 1;
        }
        if (idx < 0 || idx >= bmachine.reg_array_size) {
                warnx("internal error: reg num = %d", idx);
                idx = -1;
        }
        return idx;
}

static void
load(void)
{
        int             idx;
        struct value    *v, copy;
        struct number   *n;

        idx = readreg();
        if (idx >= 0) {
                v = stack_tos(&bmachine.reg[idx]);
                if (v == NULL) {
                        n = new_number();
                        bn_check(BN_set_word(n->number, 0));
                        push_number(n);
                } else
                        push(stack_dup_value(v, &copy));
        }
}

static void
store(void)
{
        int             idx;
        struct value    *val;

        idx = readreg();
        if (idx >= 0) {
                val = pop();
                if (val == NULL) {
                        return;
                }
                stack_set_tos(&bmachine.reg[idx], val);
        }
}

static void
load_stack(void)
{
        int             idx;
        struct stack    *stack;
        struct value    *value;

        idx = readreg();
        if (idx >= 0) {
                stack = &bmachine.reg[idx];
                value = NULL;
                if (stack_size(stack) > 0) {
                        value = stack_pop(stack);
                }
                if (value != NULL)
                        push(value);
                else
                        warnx("stack register '%c' (0%o) is empty",
                            idx, idx);
        }
}

static void
store_stack(void)
{
        int             idx;
        struct value    *value;

        idx = readreg();
        if (idx >= 0) {
                value = pop();
                if (value == NULL)
                        return;
                stack_push(&bmachine.reg[idx], value);
        }
}

static void
load_array(void)
{
        int                     reg;
        struct number           *inumber, *n;
        u_long                  idx;
        struct stack            *stack;
        struct value            *v, copy;

        reg = readreg();
        if (reg >= 0) {
                inumber = pop_number();
                if (inumber == NULL)
                        return;
                idx = get_ulong(inumber);
                if (BN_is_negative(inumber->number))
                        warnx("negative idx");
                else if (idx == (BN_ULONG)-1 || idx > MAX_ARRAY_INDEX)
                        warnx("idx too big");
                else {
                        stack = &bmachine.reg[reg];
                        v = frame_retrieve(stack, idx);
                        if (v == NULL || v->type == BCODE_NONE) {
                                n = new_number();
                                bn_check(BN_set_word(n->number, 0));
                                push_number(n);
                        } else
                                push(stack_dup_value(v, &copy));
                }
                free_number(inumber);
        }
}

static void
store_array(void)
{
        int                     reg;
        struct number           *inumber;
        u_long                  idx;
        struct value            *value;
        struct stack            *stack;

        reg = readreg();
        if (reg >= 0) {
                inumber = pop_number();
                if (inumber == NULL)
                        return;
                value = pop();
                if (value == NULL) {
                        free_number(inumber);
                        return;
                }
                idx = get_ulong(inumber);
                if (BN_is_negative(inumber->number)) {
                        warnx("negative idx");
                        stack_free_value(value);
                } else if (idx == (BN_ULONG)-1 || idx > MAX_ARRAY_INDEX) {
                        warnx("idx too big");
                        stack_free_value(value);
                } else {
                        stack = &bmachine.reg[reg];
                        frame_assign(stack, idx, value);
                }
                free_number(inumber);
        }
}

static void
push_line(void)
{
        push_string(read_string(&bmachine.readstack[bmachine.readsp]));
}

static void
comment(void)
{
        free(readline());
}

static void
badd(void)
{
        struct number   *a, *b;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        if (b->scale > a->scale)
                normalize(a, b->scale);
        else if (a->scale > b->scale)
                normalize(b, a->scale);
        bn_check(BN_add(b->number, a->number, b->number));
        free_number(a);
        push_number(b);
}

static void
bsub(void)
{
        struct number   *a, *b;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        if (b->scale > a->scale)
                normalize(a, b->scale);
        else if (a->scale > b->scale)
                normalize(b, a->scale);
        bn_check(BN_sub(b->number, b->number, a->number));
        free_number(a);
        push_number(b);
}

void
bmul_number(struct number *r, struct number *a, struct number *b, u_int scale)
{
        /* Create copies of the scales, since r might be equal to a or b */
        u_int ascale = a->scale;
        u_int bscale = b->scale;
        u_int rscale = ascale + bscale;

        bn_check(BN_mul(r->number, a->number, b->number, bmachine.ctx));

        r->scale = rscale;
        if (rscale > bmachine.scale && rscale > ascale && rscale > bscale)
                normalize(r, max(scale, max(ascale, bscale)));
}

static void
bmul(void)
{
        struct number   *a, *b;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        bmul_number(b, a, b, bmachine.scale);
        free_number(a);
        push_number(b);
}

static void
bdiv(void)
{
        struct number   *a, *b;
        struct number   *r;
        u_int           scale;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        r = new_number();
        r->scale = bmachine.scale;
        scale = max(a->scale, b->scale);

        if (BN_is_zero(a->number))
                warnx("divide by zero");
        else {
                normalize(a, scale);
                normalize(b, scale + r->scale);

                bn_check(BN_div(r->number, NULL, b->number, a->number, bmachine.ctx));
        }
        push_number(r);
        free_number(a);
        free_number(b);
}

static void
bmod(void)
{
        struct number   *a, *b;
        struct number   *r;
        u_int           scale;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        r = new_number();
        scale = max(a->scale, b->scale);
        r->scale = max(b->scale, a->scale + bmachine.scale);

        if (BN_is_zero(a->number))
                warnx("remainder by zero");
        else {
                normalize(a, scale);
                normalize(b, scale + bmachine.scale);

                bn_check(BN_mod(r->number, b->number, a->number, bmachine.ctx));
        }
        push_number(r);
        free_number(a);
        free_number(b);
}

static void
bdivmod(void)
{
        struct number   *a, *b;
        struct number   *rdiv, *rmod;
        u_int           scale;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        rdiv = new_number();
        rmod = new_number();
        rdiv->scale = bmachine.scale;
        rmod->scale = max(b->scale, a->scale + bmachine.scale);
        scale = max(a->scale, b->scale);

        if (BN_is_zero(a->number))
                warnx("divide by zero");
        else {
                normalize(a, scale);
                normalize(b, scale + bmachine.scale);

                bn_check(BN_div(rdiv->number, rmod->number,
                    b->number, a->number, bmachine.ctx));
        }
        push_number(rdiv);
        push_number(rmod);
        free_number(a);
        free_number(b);
}

static void
bexp(void)
{
        struct number   *a, *p;
        struct number   *r;
        bool            neg;
        u_int           rscale;

        p = pop_number();
        if (p == NULL)
                return;
        a = pop_number();
        if (a == NULL) {
                push_number(p);
                return;
        }

        if (p->scale != 0) {
                BIGNUM *i, *f;
                i = BN_new();
                bn_checkp(i);
                f = BN_new();
                bn_checkp(f);
                split_number(p, i, f);
                if (!BN_is_zero(f))
                        warnx("Runtime warning: non-zero fractional part "
                            "in exponent");
                BN_free(p->number);
                p->number = i;
                BN_free(f);
        }

        neg = BN_is_negative(p->number);
        if (neg) {
                negate(p);
                rscale = bmachine.scale;
        } else {
                /* Posix bc says min(a.scale * b, max(a.scale, scale)) */
                u_long  b;
                u_int   m;

                b = BN_get_word(p->number);
                m = max(a->scale, bmachine.scale);
                rscale = a->scale * (u_int)b;
                if (rscale > m || (a->scale > 0 && (b == (BN_ULONG)-1 ||
                    b > UINT_MAX)))
                        rscale = m;
        }

        if (BN_is_zero(p->number)) {
                r = new_number();
                bn_check(BN_one(r->number));
                normalize(r, rscale);
        } else {
                u_int ascale, mscale;

                ascale = a->scale;
                while (!BN_is_bit_set(p->number, 0)) {
                        ascale *= 2;
                        bmul_number(a, a, a, ascale);
                        bn_check(BN_rshift1(p->number, p->number));
                }

                r = dup_number(a);
                bn_check(BN_rshift1(p->number, p->number));

                mscale = ascale;
                while (!BN_is_zero(p->number)) {
                        ascale *= 2;
                        bmul_number(a, a, a, ascale);
                        if (BN_is_bit_set(p->number, 0)) {
                                mscale += ascale;
                                bmul_number(r, r, a, mscale);
                        }
                        bn_check(BN_rshift1(p->number, p->number));
                }

                if (neg) {
                        BIGNUM  *one;

                        one = BN_new();
                        bn_checkp(one);
                        bn_check(BN_one(one));
                        scale_number(one, r->scale + rscale);

                        if (BN_is_zero(r->number))
                                warnx("divide by zero");
                        else
                                bn_check(BN_div(r->number, NULL, one,
                                    r->number, bmachine.ctx));
                        BN_free(one);
                        r->scale = rscale;
                } else
                        normalize(r, rscale);
        }
        push_number(r);
        free_number(a);
        free_number(p);
}

static void
bsqrt(void)
{
        struct number   *n;
        struct number   *r;
        BIGNUM          *x, *y, *t;
        u_int           scale, onecount;

        onecount = 0;
        n = pop_number();
        if (n == NULL)
                return;
        if (BN_is_zero(n->number)) {
                r = new_number();
                push_number(r);
        } else if (BN_is_negative(n->number))
                warnx("square root of negative number");
        else {
                scale = max(bmachine.scale, n->scale);
                normalize(n, 2*scale);
                x = BN_dup(n->number);
                bn_checkp(x);
                bn_check(BN_rshift(x, x, BN_num_bits(x)/2));
                y = BN_new();
                bn_checkp(y);
                do {
                        bn_check(BN_div(y, NULL, n->number, x, bmachine.ctx));
                        bn_check(BN_add(y, x, y));
                        bn_check(BN_rshift1(y, y));
                        bn_check(BN_sub(x, y, x));
                        t = x;
                        x = y;
                        y = t;
                } while (!BN_is_zero(y) && (onecount += BN_is_one(y)) < 2);
                bn_check(BN_sub(y, x, y));
                r = bmalloc(sizeof(*r));
                r->scale = scale;
                r->number = y;
                BN_free(x);
                push_number(r);
        }

        free_number(n);
}

static void
not(void)
{
        struct number   *a;

        a = pop_number();
        if (a == NULL)
                return;
        a->scale = 0;
        bn_check(BN_set_word(a->number, BN_get_word(a->number) ? 0 : 1));
        push_number(a);
}

static void
equal(void)
{
        compare(BCODE_EQUAL);
}

static void
equal_numbers(void)
{
        struct number *a, *b, *r;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }
        r = new_number();
        bn_check(BN_set_word(r->number,
            compare_numbers(BCODE_EQUAL, a, b) ? 1 : 0));
        push_number(r);
}

static void
less_numbers(void)
{
        struct number *a, *b, *r;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }
        r = new_number();
        bn_check(BN_set_word(r->number,
            compare_numbers(BCODE_LESS, a, b) ? 1 : 0));
        push_number(r);
}

static void
lesseq_numbers(void)
{
        struct number *a, *b, *r;

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }
        r = new_number();
        bn_check(BN_set_word(r->number,
            compare_numbers(BCODE_NOT_GREATER, a, b) ? 1 : 0));
        push_number(r);
}

static void
less(void)
{
        compare(BCODE_LESS);
}

static void
not_compare(void)
{
        switch (readch()) {
        case '<':
                compare(BCODE_NOT_LESS);
                break;
        case '>':
                compare(BCODE_NOT_GREATER);
                break;
        case '=':
                compare(BCODE_NOT_EQUAL);
                break;
        default:
                unreadch();
                warnx("! command is deprecated");
                break;
        }
}

static void
greater(void)
{
        compare(BCODE_GREATER);
}

static bool
compare_numbers(enum bcode_compare type, struct number *a, struct number *b)
{
        u_int   scale;
        int     cmp;

        scale = max(a->scale, b->scale);

        if (scale > a->scale)
                normalize(a, scale);
        else if (scale > b->scale)
                normalize(b, scale);

        cmp = BN_cmp(a->number, b->number);

        free_number(a);
        free_number(b);

        switch (type) {
        case BCODE_EQUAL:
                return cmp == 0;
        case BCODE_NOT_EQUAL:
                return cmp != 0;
        case BCODE_LESS:
                return cmp < 0;
        case BCODE_NOT_LESS:
                return cmp >= 0;
        case BCODE_GREATER:
                return cmp > 0;
        case BCODE_NOT_GREATER:
                return cmp <= 0;
        }
        return false;
}

static void
compare(enum bcode_compare type)
{
        int             idx, elseidx;
        struct number   *a, *b;
        bool            ok;
        struct value    *v;

        elseidx = NO_ELSE;
        idx = readreg();
        if (readch() == 'e')
                elseidx = readreg();
        else
                unreadch();

        a = pop_number();
        if (a == NULL)
                return;
        b = pop_number();
        if (b == NULL) {
                push_number(a);
                return;
        }

        ok = compare_numbers(type, a, b);

        if (!ok && elseidx != NO_ELSE)
                idx = elseidx;

        if (idx >= 0 && (ok || (!ok && elseidx != NO_ELSE))) {
                v = stack_tos(&bmachine.reg[idx]);
                if (v == NULL)
                        warnx("register '%c' (0%o) is empty", idx, idx);
                else {
                        switch(v->type) {
                        case BCODE_NONE:
                                warnx("register '%c' (0%o) is empty", idx, idx);
                                break;
                        case BCODE_NUMBER:
                                warn("eval called with non-string argument");
                                break;
                        case BCODE_STRING:
                                eval_string(bstrdup(v->u.string));
                                break;
                        }
                }
        }
}

static void
nop(void)
{
}

static void
quit(void)
{
        if (bmachine.readsp < 2)
                exit(0);
        src_free();
        bmachine.readsp--;
        src_free();
        bmachine.readsp--;
}

static void
quitN(void)
{
        struct number   *n;
        u_long          i;

        n = pop_number();
        if (n == NULL)
                return;
        i = get_ulong(n);
        free_number(n);
        if (i == (BN_ULONG)-1 || i == 0)
                warnx("Q command requires a number >= 1");
        else if (bmachine.readsp < i)
                warnx("Q command argument exceeded string execution depth");
        else {
                while (i-- > 0) {
                        src_free();
                        bmachine.readsp--;
                }
        }
}

static void
skipN(void)
{
        struct number   *n;
        u_long          i;

        n = pop_number();
        if (n == NULL)
                return;
        i = get_ulong(n);
        if (i == (BN_ULONG)-1)
                warnx("J command requires a number >= 0");
        else if (i > 0 && bmachine.readsp < i)
                warnx("J command argument exceeded string execution depth");
        else {
                while (i-- > 0) {
                        src_free();
                        bmachine.readsp--;
                }
                skip_until_mark();
        }
}

static void
skip_until_mark(void)
{
        for (;;) {
                switch (readch()) {
                case 'M':
                        return;
                case EOF:
                        errx(1, "mark not found");
                        return;
                case 'l':
                case 'L':
                case 's':
                case 'S':
                case ':':
                case ';':
                case '<':
                case '>':
                case '=':
                        (void)readreg();
                        if (readch() == 'e')
                                (void)readreg();
                        else
                                unreadch();
                        break;
                case '[':
                        free(read_string(&bmachine.readstack[bmachine.readsp]));
                        break;
                case '!':
                        switch (readch()) {
                                case '<':
                                case '>':
                                case '=':
                                        (void)readreg();
                                        if (readch() == 'e')
                                                (void)readreg();
                                        else
                                                unreadch();
                                        break;
                                default:
                                        free(readline());
                                        break;
                        }
                        break;
                default:
                        break;
                }
        }
}

static void
parse_number(void)
{
        unreadch();
        push_number(readnumber(&bmachine.readstack[bmachine.readsp],
            bmachine.ibase));
}

static void
unknown(void)
{
        int ch = bmachine.readstack[bmachine.readsp].lastchar;
        warnx("%c (0%o) is unimplemented", ch, ch);
}

static void
eval_string(char *p)
{
        int ch;

        if (bmachine.readsp > 0) {
                /* Check for tail call. Do not recurse in that case. */
                ch = readch();
                if (ch == EOF) {
                        src_free();
                        src_setstring(&bmachine.readstack[bmachine.readsp], p);
                        return;
                } else
                        unreadch();
        }
        if (bmachine.readsp == bmachine.readstack_sz - 1) {
                size_t newsz = bmachine.readstack_sz * 2;
                struct source *stack;
                stack = reallocarray(bmachine.readstack, newsz,
                    sizeof(struct source));
                if (stack == NULL)
                        err(1, "recursion too deep");
                bmachine.readstack_sz = newsz;
                bmachine.readstack = stack;
        }
        src_setstring(&bmachine.readstack[++bmachine.readsp], p);
}

static void
eval_line(void)
{
        /* Always read from stdin */
        struct source   in;
        char            *p;

        clearerr(stdin);
        src_setstream(&in, stdin);
        p = (*in.vtable->readline)(&in);
        eval_string(p);
}

static void
eval_tos(void)
{
        char *p;

        p = pop_string();
        if (p != NULL)
                eval_string(p);
}

void
eval(void)
{
        int     ch;

        for (;;) {
                ch = readch();
                if (ch == EOF) {
                        if (bmachine.readsp == 0)
                                return;
                        src_free();
                        bmachine.readsp--;
                        continue;
                }
                if (bmachine.interrupted) {
                        if (bmachine.readsp > 0) {
                                src_free();
                                bmachine.readsp--;
                                continue;
                        } else
                                bmachine.interrupted = false;
                }
#ifdef DEBUGGING
                (void)fprintf(stderr, "# %c\n", ch);
                stack_print(stderr, &bmachine.stack, "* ",
                    bmachine.obase);
                (void)fprintf(stderr, "%zd =>\n", bmachine.readsp);
#endif

                if (0 <= ch && ch < nitems(jump_table))
                        (*jump_table[ch])();
                else
                        unknown();

#ifdef DEBUGGING
                stack_print(stderr, &bmachine.stack, "* ",
                    bmachine.obase);
                (void)fprintf(stderr, "%zd ==\n", bmachine.readsp);
#endif
        }
}