Mercurial > repo
view interps/cfunge/cfunge-src/src/interpreter.c @ 12518:2d8fe55c6e65 draft default tip
<int-e> learn The password of the month is release incident pilot.
author | HackEso <hackeso@esolangs.org> |
---|---|
date | Sun, 03 Nov 2024 00:31:02 +0000 |
parents | 859f9b4339e6 |
children |
line wrap: on
line source
/* -*- mode: C; coding: utf-8; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- * * cfunge - A standard-conforming Befunge93/98/109 interpreter in C. * Copyright (C) 2008-2009 Arvid Norlander <anmaster AT tele2 DOT se> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at the proxy's option) any later version. Arvid Norlander is a * proxy who can decide which future versions of the GNU General Public * License can be used. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "global.h" #include "interpreter.h" #include "diagnostic.h" #include "funge-space/funge-space.h" #include "input.h" #include "ip.h" #include "settings.h" #include "stack.h" #include "vector.h" #include "fingerprints/manager.h" #include "instructions/execute.h" #include "instructions/io.h" #include "instructions/iterate.h" #include "instructions/sysinfo.h" #include <errno.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* strerror */ #ifdef HAVE_clock_gettime # include <time.h> #else # include <sys/time.h> #endif #include <assert.h> /** * Either the IP or the IP list. */ /*@{*/ #ifdef CONCURRENT_FUNGE static ipList *IPList = NULL; #else static instructionPointer *IP = NULL; #endif /*@}*/ /** * Print warning on unknown instruction if such warnings are enabled. */ FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL static inline void warn_unknown_instr(funge_cell opcode, instructionPointer * restrict ip) { if (FUNGE_UNLIKELY(setting_enable_warnings)) diag_warn_format("Unknown instruction at x=%" FUNGECELLPRI " y=%" FUNGECELLPRI ": %c (%" FUNGECELLPRI ")", ip->position.x, ip->position.y, (char)opcode, opcode); } // These two are called from elsewhere. Avoid code duplication. FUNGE_ATTR_FAST inline void if_east_west(instructionPointer * restrict ip) { if (stack_pop(ip->stack) == 0) ip_go_east(ip); else ip_go_west(ip); } FUNGE_ATTR_FAST inline void if_north_south(instructionPointer * restrict ip) { if (stack_pop(ip->stack) == 0) ip_go_south(ip); else ip_go_north(ip); } #ifdef CONCURRENT_FUNGE # define ReturnFromexecute_instruction(x) return (x) /// Return with value if we are concurrent # define ReturnIfCon(x) return (x) # define CON_RETTYPE bool #else # define ReturnFromexecute_instruction(x) return # define CON_RETTYPE void # define ReturnIfCon(x) (x); return #endif /// Generate a case for use in execute_instruction() that pushes a number on /// the stack. #define PUSHVAL(x, y) \ case (x): \ stack_push(ip->stack, (funge_cell)y); \ break; /// This function handles string mode. FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL static inline CON_RETTYPE handle_string_mode(funge_cell opcode, instructionPointer * restrict ip) { if (opcode == '"') { ip->mode = ipmCODE; } else if (opcode != ' ') { ip->stringLastWasSpace = false; stack_push(ip->stack, opcode); } else if (opcode == ' ') { if ((!ip->stringLastWasSpace) || (setting_current_standard == stdver93)) { ip->stringLastWasSpace = true; stack_push(ip->stack, opcode); // More than one space in string mode take no tick in concurrent Funge. } else { ReturnFromexecute_instruction(true); } } ReturnFromexecute_instruction(false); } /// This function handles fingerprint instructions. FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL static inline void handle_fprint(funge_cell opcode, instructionPointer * restrict ip) { if (FUNGE_UNLIKELY(setting_disable_fingerprints)) { warn_unknown_instr(opcode, ip); ip_reverse(ip); } else { int_fast8_t entry = (int_fast8_t)(opcode - 'A'); if ((ip->fingerOpcodes[entry].top > 0) && ip->fingerOpcodes[entry].entries[ip->fingerOpcodes[entry].top - 1]) { // Call the fingerprint. ip->fingerOpcodes[entry].entries[ip->fingerOpcodes[entry].top - 1](ip); } else { warn_unknown_instr(opcode, ip); ip_reverse(ip); } } } #ifdef CONCURRENT_FUNGE FUNGE_ATTR_FAST CON_RETTYPE execute_instruction(funge_cell opcode, instructionPointer * restrict ip, ssize_t * threadindex) #else FUNGE_ATTR_FAST CON_RETTYPE execute_instruction(funge_cell opcode, instructionPointer * restrict ip) #endif { // First check if we are in string mode, and do special stuff then. if (ip->mode == ipmSTRING) { ReturnIfCon(handle_string_mode(opcode, ip)); // Next: Is this a fingerprint opcode? } else if ((opcode >= 'A') && (opcode <= 'Z')) { handle_fprint(opcode, ip); // OK a core instruction. // Find what one and execute it. } else { switch (opcode) { case ' ': { do { ip_forward(ip); } while (fungespace_get(&ip->position) == ' '); ip->needMove = false; ReturnFromexecute_instruction(true); } case 'z': break; case ';': { do { ip_forward(ip); } while (fungespace_get(&ip->position) != ';'); ReturnFromexecute_instruction(true); } case '^': ip_go_north(ip); break; case '>': ip_go_east(ip); break; case 'v': ip_go_south(ip); break; case '<': ip_go_west(ip); break; case 'j': { // Currently need to do it like this or wrapping // won't work for j. funge_cell jumps = stack_pop(ip->stack); ip_forward(ip); if (jumps != 0) { funge_vector tmp; tmp.x = ip->delta.x; tmp.y = ip->delta.y; ip->delta.y *= jumps; ip->delta.x *= jumps; ip_forward(ip); ip->delta.x = tmp.x; ip->delta.y = tmp.y; } ip->needMove = false; break; } case '?': { // May not be perfectly uniform. // If this matters for you, contact me (with a patch). long int rnd = random() % 4; switch (rnd) { case 0: ip_go_north(ip); break; case 1: ip_go_east(ip); break; case 2: ip_go_south(ip); break; case 3: ip_go_west(ip); break; } break; } case 'r': ip_reverse(ip); break; case '[': ip_turn_left(ip); break; case ']': ip_turn_right(ip); break; case 'x': { funge_vector pos = stack_pop_vector(ip->stack); ip_set_delta(ip, &pos); break; } PUSHVAL('0', 0) PUSHVAL('1', 1) PUSHVAL('2', 2) PUSHVAL('3', 3) PUSHVAL('4', 4) PUSHVAL('5', 5) PUSHVAL('6', 6) PUSHVAL('7', 7) PUSHVAL('8', 8) PUSHVAL('9', 9) PUSHVAL('a', 0xa) PUSHVAL('b', 0xb) PUSHVAL('c', 0xc) PUSHVAL('d', 0xd) PUSHVAL('e', 0xe) PUSHVAL('f', 0xf) case '"': ip->mode = ipmSTRING; ip->stringLastWasSpace = false; break; case ':': stack_dup_top(ip->stack); break; case '#': ip_forward(ip); break; case '_': if_east_west(ip); break; case '|': if_north_south(ip); break; case 'w': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); if (a < b) ip_turn_left(ip); else if (a > b) ip_turn_right(ip); break; } case 'k': #ifdef CONCURRENT_FUNGE run_iterate(ip, &IPList, threadindex, false); #else run_iterate(ip, false); #endif break; case '-': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); stack_push(ip->stack, a - b); break; } case '+': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); stack_push(ip->stack, a + b); break; } case '*': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); stack_push(ip->stack, a * b); break; } case '/': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); if (b == 0) stack_push(ip->stack, 0); else stack_push(ip->stack, a / b); break; } case '%': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); if (b == 0) stack_push(ip->stack, 0); else stack_push(ip->stack, a % b); break; } case '!': stack_push(ip->stack, !stack_pop(ip->stack)); break; case '`': { funge_cell a, b; b = stack_pop(ip->stack); a = stack_pop(ip->stack); stack_push(ip->stack, a > b); break; } case 'g': { funge_vector pos; funge_cell a; pos = stack_pop_vector(ip->stack); a = fungespace_get_offset(&pos, &ip->storageOffset); stack_push(ip->stack, a); break; } case 'p': { funge_vector pos; funge_cell a; pos = stack_pop_vector(ip->stack); a = stack_pop(ip->stack); fungespace_set_offset(a, &pos, &ip->storageOffset); break; } case '\'': ip_forward(ip); stack_push(ip->stack, fungespace_get(&ip->position)); break; case 's': ip_forward(ip); fungespace_set(stack_pop(ip->stack), &ip->position); break; case '$': stack_discard(ip->stack, 1); break; case '\\': stack_swap_top(ip->stack); break; case 'n': stack_clear(ip->stack); break; case ',': { funge_cell a = stack_pop(ip->stack); // Reverse on failed output if (FUNGE_UNLIKELY(cf_putchar_maybe_locked((int)a) != (unsigned char)a)) ip_reverse(ip); break; } case '.': // Reverse on failed output if (FUNGE_UNLIKELY(printf("%" FUNGECELLPRI " ", stack_pop(ip->stack)) < 0)) ip_reverse(ip); break; case '~': { funge_cell a; if (input_getchar(&a)) { stack_push(ip->stack, a); } else { ip_reverse(ip); } break; } case '&': { funge_cell a; ret_getint gotint = rgi_noint; while (gotint == rgi_noint) gotint = input_getint(&a, 10); if (gotint == rgi_success) { stack_push(ip->stack, a); } else { ip_reverse(ip); } break; } case 'y': run_sys_info(ip); break; case '{': { funge_cell count; funge_vector pos; count = stack_pop(ip->stack); ip_forward(ip); pos.x = ip->position.x; pos.y = ip->position.y; ip_backward(ip); if (!stackstack_begin(ip, count, &pos)) ip_reverse(ip); break; } case '}': if (ip->stackstack->size == 1) { ip_reverse(ip); } else { funge_cell count; count = stack_pop(ip->stack); if (!stackstack_end(ip, count)) ip_reverse(ip); } break; case 'u': if (ip->stackstack->size == 1) { ip_reverse(ip); } else { funge_cell count; count = stack_pop(ip->stack); stackstack_transfer(count, ip->stackstack->stacks[ip->stackstack->current], ip->stackstack->stacks[ip->stackstack->current - 1]); } break; case 'i': run_file_input(ip); break; case 'o': run_file_output(ip); break; case '=': run_system_execute(ip); break; case '(': case ')': { // TODO: Handle Funge-109 style too. funge_cell fpsize = stack_pop(ip->stack); // Check for sanity (because we won't have any fingerprints // outside such a range. This prevents long lockups here. if (fpsize < 1) { ip_reverse(ip); } else if (FUNGE_UNLIKELY(setting_disable_fingerprints)) { stack_discard(ip->stack, (size_t)fpsize); ip_reverse(ip); } else { funge_cell fprint = 0; if (FUNGE_UNLIKELY((fpsize > 8) && setting_enable_warnings)) { diag_warn_format("WARN: %c (x=%" FUNGECELLPRI " y=%" FUNGECELLPRI "): count is very large(%" FUNGECELLPRI "), probably a bug.\n", (char)opcode, ip->position.x, ip->position.y, fpsize); } while (fpsize--) { fprint <<= 8; fprint += stack_pop(ip->stack); } if (opcode == '(') { if (!manager_load(ip, fprint)) ip_reverse(ip); } else { if (!manager_unload(ip, fprint)) ip_reverse(ip); } } break; } #ifdef CONCURRENT_FUNGE case 't': *threadindex = iplist_duplicate_ip(&IPList, *threadindex); break; #endif /* CONCURRENT_FUNGE */ case '@': #ifdef CONCURRENT_FUNGE if (IPList->top == 0) { fflush(stdout); exit(0); } else { *threadindex = iplist_terminate_ip(&IPList, *threadindex); //if (IPList->top == 0) IPList->ips[*threadindex].needMove = false; } #else exit(0); #endif /* CONCURRENT_FUNGE */ break; case 'q': // We do the wrong thing here when fuzz testing to reduce false positives. #ifdef FUZZ_TESTING exit(0); #else exit((int)stack_pop(ip->stack)); #endif break; default: warn_unknown_instr(opcode, ip); ip_reverse(ip); } } ReturnFromexecute_instruction(false); } #ifdef CONCURRENT_FUNGE FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL static inline void thread_forward(instructionPointer * restrict ip) { assert(ip != NULL); if (ip->needMove) ip_forward(ip); else ip->needMove = true; } #endif FUNGE_ATTR_FAST FUNGE_ATTR_NORET static inline void interpreter_main_loop(void) { #ifdef CONCURRENT_FUNGE while (true) { ssize_t i = IPList->top; while (i >= 0) { bool retval; funge_cell opcode; opcode = fungespace_get(&IPList->ips[i].position); # ifndef DISABLE_TRACE if (FUNGE_UNLIKELY(setting_trace_level != 0)) { if (setting_trace_level > 8) { fprintf(stderr, "tix=%zd tid=%" FUNGECELLPRI " x=%" FUNGECELLPRI " y=%" FUNGECELLPRI ": %c (%" FUNGECELLPRI ")\n", i, IPList->ips[i].ID, IPList->ips[i].position.x, IPList->ips[i].position.y, (char)opcode, opcode); stack_print_top(IPList->ips[i].stack); } else if (setting_trace_level > 3) { fprintf(stderr, "tix=%zd tid=%" FUNGECELLPRI " x=%" FUNGECELLPRI " y=%" FUNGECELLPRI ": %c (%" FUNGECELLPRI ")\n", i, IPList->ips[i].ID, IPList->ips[i].position.x, IPList->ips[i].position.y, (char)opcode, opcode); } else if (setting_trace_level > 2) fprintf(stderr, "%c", (char)opcode); } # endif /* DISABLE_TRACE */ retval = execute_instruction(opcode, &IPList->ips[i], &i); thread_forward(&IPList->ips[i]); if (!retval) i--; } } #else /* CONCURRENT_FUNGE */ while (true) { funge_cell opcode; opcode = fungespace_get(&IP->position); # ifndef DISABLE_TRACE if (FUNGE_UNLIKELY(setting_trace_level != 0)) { if (setting_trace_level > 8) { fprintf(stderr, "x=%" FUNGECELLPRI " y=%" FUNGECELLPRI ": %c (%" FUNGECELLPRI ")\n", IP->position.x, IP->position.y, (char)opcode, opcode); stack_print_top(IP->stack); } else if (setting_trace_level > 3) { fprintf(stderr, "x=%" FUNGECELLPRI " y=%" FUNGECELLPRI ": %c (%" FUNGECELLPRI ")\n", IP->position.x, IP->position.y, (char)opcode, opcode); } else if (setting_trace_level > 2) fprintf(stderr, "%c", (char)opcode); } # endif /* DISABLE_TRACE */ execute_instruction(opcode, IP); if (IP->needMove) ip_forward(IP); else IP->needMove = true; } #endif /* CONCURRENT_FUNGE */ } #ifndef NDEBUG // Used with debugging for freeing stuff at end of the program. // Not needed, but useful to check that free functions works, // and for detecting real memory leaks. static void debug_free(void) { # ifdef CONCURRENT_FUNGE iplist_free(IPList); # else ip_free(IP); # endif sysinfo_cleanup(); fungespace_free(); } #endif FUNGE_ATTR_FAST void interpreter_run(const char *filename) { if (FUNGE_UNLIKELY(!fungespace_create())) { DIAG_FATAL_FORMAT_LOC("Couldn't create funge space: %s", strerror(errno)); } #ifndef NDEBUG atexit(&debug_free); #endif if (FUNGE_UNLIKELY(!fungespace_load(filename))) { diag_fatal_format("Failed to process file \"%s\": %s", filename, strerror(errno)); } #ifdef CONCURRENT_FUNGE IPList = iplist_create(); if (FUNGE_UNLIKELY(IPList == NULL)) { DIAG_FATAL_LOC("Couldn't create instruction pointer list!?"); } #else IP = ip_create(); if (FUNGE_UNLIKELY(IP == NULL)) { DIAG_FATAL_LOC("Couldn't create instruction pointer!?"); } #endif { #ifdef HAVE_clock_gettime struct timespec tv; if (FUNGE_UNLIKELY(clock_gettime(CLOCK_REALTIME, &tv))) { diag_fatal_format("clock_gettime() failed (needed for random seed): %s", strerror(errno)); } // Set up randomness srandom((unsigned int)tv.tv_nsec); #else struct timeval tv; if (FUNGE_UNLIKELY(gettimeofday(&tv, NULL))) { diag_fatal_format("gettimeofday() failed (needed for random seed): %s", strerror(errno)); } // Set up randomness srandom((unsigned int)tv.tv_usec); #endif } interpreter_main_loop(); }