view interps/cfunge/cfunge-src/src/fingerprints/FILE/FILE.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 "FILE.h"
#include "../../stack.h"
#include "../../../lib/stringbuffer/stringbuffer.h"

#include <assert.h>
#include <stdio.h> /* fclose, fopen, fread, fwrite ... */
#include <unistd.h> /* fcntl, unlink */
#include <fcntl.h> /* fcntl */

// Based on how CCBI does it.

typedef struct sFungeFileHandle {
	FILE      * file;
	funge_vector buffvect; // IO buffer in Funge-Space
} FungeFileHandle;

#define ALLOCCHUNK 2
// Array of pointers
static FungeFileHandle** handles = NULL;
static size_t maxHandle = 0;

/// Used by allocate_handle() below to find next free handle.
FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED
static inline funge_cell findNextfree_handle(void)
{
	for (size_t i = 0; i < maxHandle; i++) {
		if (handles[i] == NULL)
			return (funge_cell)i;
	}
	// No free one, extend array..
	{
		FungeFileHandle** newlist = (FungeFileHandle**)cf_realloc(handles, (maxHandle + ALLOCCHUNK) * sizeof(FungeFileHandle*));
		if (!newlist)
			return -1;
		handles = newlist;
		for (size_t i = maxHandle; i < (maxHandle + ALLOCCHUNK); i++)
			handles[i] = NULL;
		maxHandle += ALLOCCHUNK;
		return (funge_cell)(maxHandle - ALLOCCHUNK);
	}
}

/// Get a new handle to use for a file, also allocates buffer for it.
/// @return Handle, or -1 on failure
FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED
static inline funge_cell allocate_handle(void)
{
	funge_cell h;

	h = findNextfree_handle();
	if (h < 0)
		return -1;

	handles[h] = cf_malloc(sizeof(FungeFileHandle));
	if (!handles[h])
		return -1;
	return h;
}

/// Free a handle. fclose() the file before calling this.
FUNGE_ATTR_FAST
static inline void free_handle(funge_cell h)
{
	if (!handles[h])
		return;
	// Should be closed first!
	if (handles[h]->file != NULL) {
		handles[h]->file = NULL;
	}
	cf_free(handles[h]);
	handles[h] = NULL;
}

/// Checks if handle is valid.
FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED
static inline bool valid_handle(funge_cell h)
{
	if ((h < 0) || ((size_t)h >= maxHandle) || (!handles[h])) {
		return false;
	} else {
		return true;
	}
}

/// C - Close a file
static void finger_FILE_fclose(instructionPointer * ip)
{
	funge_cell h;

	h = stack_pop(ip->stack);
	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	if (fclose(handles[h]->file) != 0)
		ip_reverse(ip);

	free_handle(h);
}

/// D - Delete specified file
static void finger_FILE_delete(instructionPointer * ip)
{
	char * restrict filename;

	filename = (char*)stack_pop_string(ip->stack, NULL);
	if (unlink(filename) != 0) {
		ip_reverse(ip);
	}

	stack_free_string(filename);
	return;
}


/// G - Get string from file (like c fgets)
static void finger_FILE_fgets(instructionPointer * ip)
{
	funge_cell h;
	FILE * fp;

	h = stack_peek(ip->stack);
	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	fp = handles[h]->file;

	{
		StringBuffer *sb;
		int ch;
		sb = stringbuffer_new();
		if (!sb) {
			ip_reverse(ip);
			return;
		}

		while (true) {
			ch = fgetc(fp);
			switch (ch) {
				case '\r':
					stringbuffer_append_char(sb, (char)ch);
					ch = fgetc(fp);
					if (ch != '\n') {
						ungetc(ch, fp);
						goto endofloop;
					}
				// Fallthrough intentional.
				case '\n':
					stringbuffer_append_char(sb, (char)ch);
					goto endofloop;

				case EOF:
					if (ferror(fp)) {
						clearerr(fp);
						ip_reverse(ip);
						stringbuffer_destroy(sb);
						return;
					} else {
						goto endofloop;
					}

				default:
					stringbuffer_append_char(sb, (char)ch);
					break;
			}
		}
	// Yeah, can't break two levels otherwise...
	endofloop:
		{
			char * str;
			size_t len;
			str = stringbuffer_finish(sb, &len);
			stack_push_string(ip->stack, (unsigned char*)str, len);
			stack_push(ip->stack, (funge_cell)len);
			free_nogc(str);
			return;
		}
	}
}

/// L - Get current location in file
static void finger_FILE_ftell(instructionPointer * ip)
{
	funge_cell h;
	long pos;

	h = stack_peek(ip->stack);
	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	pos = ftell(handles[h]->file);

	if (pos == -1) {
		clearerr(handles[h]->file);
		ip_reverse(ip);
		return;
	}

	stack_push(ip->stack, (funge_cell)pos);
}

/// O - Open a file (Va = i/o buffer vector)
static void finger_FILE_fopen(instructionPointer * ip)
{
	char * restrict filename;
	funge_cell mode;
	funge_vector vect;
	funge_cell h;

	filename = (char*)stack_pop_string(ip->stack, NULL);
	mode = stack_pop(ip->stack);
	vect = stack_pop_vector(ip->stack);

	h = allocate_handle();
	if (h == -1) {
		goto error;
	}

	switch (mode) {
		case 0: handles[h]->file = fopen(filename, "rb");  break;
		case 1: handles[h]->file = fopen(filename, "wb");  break;
		case 2: handles[h]->file = fopen(filename, "ab");  break;
		case 3: handles[h]->file = fopen(filename, "r+b"); break;
		case 4: handles[h]->file = fopen(filename, "w+b"); break;
		case 5: handles[h]->file = fopen(filename, "a+b"); break;
		default:
			free_handle(h);
			goto error;
	}
	if (!handles[h]->file) {
		free_handle(h);
		goto error;
	}
	fcntl(fileno(handles[h]->file), F_SETFD, FD_CLOEXEC, 1);
	if ((mode == 2) || (mode == 5))
		rewind(handles[h]->file);

	handles[h]->buffvect = vect;
	stack_push(ip->stack, h);
	goto end;
// Look... The alternatives to the goto were worse...
error:
	ip_reverse(ip);
end:
	stack_free_string(filename);
}

/// P - Put string to file (like c fputs)
static void finger_FILE_fputs(instructionPointer * ip)
{
	char * restrict str;
	funge_cell h;

	str = (char*)stack_pop_string(ip->stack, NULL);
	h = stack_peek(ip->stack);
	if (!valid_handle(h)) {
		ip_reverse(ip);
	} else {
		if (fputs(str, handles[h]->file) == EOF) {
			clearerr(handles[h]->file);
			ip_reverse(ip);
		}
	}
	stack_free_string(str);
}

/// R - Read n bytes from file to i/o buffer
static void finger_FILE_fread(instructionPointer * ip)
{
	funge_cell n, h;

	n = stack_pop(ip->stack);
	h = stack_peek(ip->stack);

	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	if (n <= 0) {
		ip_reverse(ip);
		return;
	} else {
		size_t bytes_read;
		FILE * fp = handles[h]->file;
		unsigned char * restrict buf = malloc_nogc((size_t)n * sizeof(unsigned char));
		if (!buf) {
			ip_reverse(ip);
			return;
		}

		if ((bytes_read = fread(buf, sizeof(unsigned char), (size_t)n, fp)) != (size_t)n) {
			// Reverse on less bytes read, but if feof() also write out to funge space below.
			ip_reverse(ip);
			if (ferror(fp)) {
				clearerr(fp);
				free_nogc(buf);
				return;
			}
		}
		{
			funge_vector v = handles[h]->buffvect;
			for (size_t i = 0; i < bytes_read; i++) {
				fungespace_set(buf[i], &v);
				v.x++;
			}
		}
		free_nogc(buf);
	}
}

/// S - Seek to position in file
static void finger_FILE_fseek(instructionPointer * ip)
{
	funge_cell n, m, h;

	n = stack_pop(ip->stack);
	m = stack_pop(ip->stack);
	h = stack_peek(ip->stack);

	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	switch (m) {
		case 0:
			if (fseek(handles[h]->file, (long)n, SEEK_SET) != 0)
				break;
			else
				return;
		case 1:
			if (fseek(handles[h]->file, (long)n, SEEK_CUR) != 0)
				break;
			else
				return;
		case 2:
			if (fseek(handles[h]->file, (long)n, SEEK_END) != 0)
				break;
			else
				return;
		default:
			break;
	}
	// An error if we got here...
	clearerr(handles[h]->file);
	ip_reverse(ip);
}

/// W - Write n bytes from i/o buffer to file
static void finger_FILE_fwrite(instructionPointer * ip)
{
	funge_cell n, h;

	n = stack_pop(ip->stack);
	h = stack_peek(ip->stack);

	if (!valid_handle(h)) {
		ip_reverse(ip);
		return;
	}

	if (n <= 0) {
		ip_reverse(ip);
		return;
	} else {
		FILE * fp = handles[h]->file;
		funge_vector v = handles[h]->buffvect;
		unsigned char * restrict buf = malloc_nogc((size_t)n * sizeof(char));

		for (funge_cell i = 0; i < n; i++) {
			buf[i] = (unsigned char)fungespace_get(&v);
			v.x++;
		}
		if (fwrite(buf, sizeof(unsigned char), (size_t)n, fp) != (size_t)n) {
			if (ferror(fp)) {
				clearerr(fp);
				ip_reverse(ip);
			}
		}
		free_nogc(buf);
	}
}

FUNGE_ATTR_FAST static inline bool init_handle_list(void)
{
	assert(!handles);
	handles = (FungeFileHandle**)cf_calloc(ALLOCCHUNK, sizeof(FungeFileHandle*));
	if (!handles)
		return false;
	maxHandle = ALLOCCHUNK;
	return true;
}

bool finger_FILE_load(instructionPointer * ip)
{
	if (!handles)
		if (!init_handle_list())
			return false;

	manager_add_opcode(FILE, 'C', fclose)
	manager_add_opcode(FILE, 'D', delete)
	manager_add_opcode(FILE, 'G', fgets)
	manager_add_opcode(FILE, 'L', ftell)
	manager_add_opcode(FILE, 'O', fopen)
	manager_add_opcode(FILE, 'P', fputs)
	manager_add_opcode(FILE, 'R', fread)
	manager_add_opcode(FILE, 'S', fseek)
	manager_add_opcode(FILE, 'W', fwrite)
	return true;
}