/* SCHEME.H */

#ifndef _SCHEME_H
#define _SCHEME_H

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#if USE_UNICODE
#include "utf8proc.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

// Default values for #define'd symbols

// If used as standalone interpreter
#ifndef STANDALONE
#define STANDALONE 1
#endif

#if USE_NO_FEATURES
#define USE_MATH 0
#define USE_CHAR_CLASSIFIERS 0
#define USE_STRING_PORTS 0
#define USE_ERROR_HOOK 0
#define USE_COLON_HOOK 0
#define USE_DL 0
#define USE_PLIST 0
#define USE_UNICODE 0
#endif

// Leave it defined if you want continuations, and also for the Sharp Zaurus.
// Undefine it if you only care about faster speed and not strict Scheme compatibility.
#define USE_SCHEME_STACK

// USE_DL: whether to add support for loading foreign extensions
// via load-extension.

#ifndef USE_UNICODE		// If Unicode support is needed
#define USE_UNICODE 1
#endif

#if USE_DL
#define USE_INTERFACE 1
#endif

#ifndef USE_MATH		// If math support is needed
#define USE_MATH 1
#endif

#ifndef USE_CHAR_CLASSIFIERS	// If char classifiers are needed
#define USE_CHAR_CLASSIFIERS 1
#endif

#ifndef USE_STRING_PORTS	// Enable string ports
#define USE_STRING_PORTS 1
#endif

#ifndef USE_PLIST		// Enable plists
#define USE_PLIST 0
#endif

// To force system errors through user-defined error handling (see *error-hook*)
#ifndef USE_ERROR_HOOK
#define USE_ERROR_HOOK 1
#endif

// Enable qualified qualifier
#ifndef USE_COLON_HOOK
#define USE_COLON_HOOK 1
#endif

// Generate interface headers
#ifndef USE_INTERFACE
#define USE_INTERFACE 0
#endif

// Show error line in file
#ifndef SHOW_ERROR_LINE
#define SHOW_ERROR_LINE 1
#endif

// Define if DOS/Windows
#ifndef STDIO_ADDS_CR
#define STDIO_ADDS_CR 0
#endif

// Memory limits, by default enough
// for list of over 100k elements
// also controlled by env vars of the same name

#ifndef CELL_SEGSIZE
#define CELL_SEGSIZE    20000
#endif
#ifndef CELL_NSEGMENT
#define CELL_NSEGMENT   12
#endif

// INTERNAL USE MACROS:
// INTERNAL USE MACROS:
// INTERNAL USE MACROS:

#ifndef _MSC_VER
#define SCHEME_EXPORT
#else
#ifdef _SCHEME_SOURCE
#define SCHEME_EXPORT __declspec(dllexport)
#else
#define SCHEME_EXPORT __declspec(dllimport)
#endif
#endif

#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#elif __STDC_VERSION__ >= 202311L
#define UNUSED [[maybe_unused]]
#else
#define UNUSED
#endif

// Used for documentation purposes, to signal functions in 'interface'
#define INTERFACE

	// Data structures and types
	typedef struct scheme scheme;
	typedef struct cell *pointer;
#if USE_UNICODE
	typedef utf8proc_int32_t char_t;
#else
	typedef char char_t;
#endif

	typedef void *(*func_alloc)(size_t);
	typedef void (*func_dealloc)(void *);

	// num, for generic arithmetic
	typedef struct num {
		bool is_fixnum;
		union {
			long long ivalue;
			double rvalue;
		} value;
	} num;

	SCHEME_EXPORT scheme *scheme_init_new(void);
	SCHEME_EXPORT scheme *scheme_init_new_custom_alloc(func_alloc malloc,
							   func_dealloc free);
	SCHEME_EXPORT int scheme_init(scheme * sc);
	SCHEME_EXPORT int scheme_init_custom_alloc(scheme * sc, func_alloc,
						   func_dealloc);
	SCHEME_EXPORT void scheme_deinit(scheme * sc);
	void scheme_set_input_port_file(scheme * sc, FILE * fin);
	void scheme_set_input_port_string(scheme * sc, char *start,
					  char *past_the_end);
	SCHEME_EXPORT void scheme_set_output_port_file(scheme * sc, FILE * fin);
	void scheme_set_output_port_string(scheme * sc, char *start,
					   char *past_the_end);
	SCHEME_EXPORT void scheme_load_file(scheme * sc, FILE * fin);
	SCHEME_EXPORT void scheme_load_named_file(scheme * sc, FILE * fin,
						  const char *filename);
	SCHEME_EXPORT void scheme_load_string(scheme * sc, const char *cmd);
	SCHEME_EXPORT pointer scheme_apply0(scheme * sc, const char *procname);
	SCHEME_EXPORT pointer scheme_call(scheme * sc, pointer func,
					  pointer args);
	SCHEME_EXPORT pointer scheme_eval(scheme * sc, pointer obj);
	void scheme_set_external_data(scheme * sc, void *p);
	SCHEME_EXPORT void scheme_define(scheme * sc, pointer env,
					 pointer symbol, pointer value);

	typedef pointer(*foreign_func) (scheme *, pointer);

	pointer _cons(scheme * sc, pointer a, pointer b, bool immutable);
	pointer mk_integer(scheme * sc, long long num);
	pointer mk_real(scheme * sc, double num);
	pointer mk_symbol(scheme * sc, const char *name);
	pointer gensym(scheme * sc, char_t *pattern);
	pointer mk_string(scheme * sc, const char *str);
	bool is_string(pointer p);
	pointer mk_counted_string(scheme * sc, const char *str, size_t len);
	pointer mk_empty_string(scheme * sc, int len, char fill);
	char *string_value(pointer p);
	pointer mk_character(scheme * sc, int c);
	pointer mk_foreign_func(scheme * sc, foreign_func f);
	void putstr(scheme * sc, const char *s);
	pointer pair_car(pointer p);
	int list_length(scheme * sc, pointer a);
	bool eqv(pointer a, pointer b);


// INTERPRETER INTERNALS
// INTERPRETER INTERNALS
// INTERPRETER INTERNALS

#ifndef MAXFIL
#define MAXFIL 64
#endif

#ifndef LINESIZE
#define LINESIZE 1024
#endif

#ifndef STRBUFF_INITIAL_SIZE
#define STRBUFF_INITIAL_SIZE 128
#endif
#ifndef STRBUFF_MAX_SIZE
#define STRBUFF_MAX_SIZE 65536
#endif
#ifndef AUXBUFF_SIZE
#define AUXBUFF_SIZE 256
#endif

enum scheme_port_kind {
		PORT_FREE = 0,
		PORT_FILE = 1,
		PORT_STRING = 2,
		PORT_SRFI6 = 4,
		PORT_INPUT = 16,
		PORT_OUTPUT = 32,
		PORT_SAW_EOF = 64
	};

	typedef struct port {
		unsigned char kind;
		union {
			struct {
				FILE *file;
				int closeit;
#if SHOW_ERROR_LINE
				int curr_line;
				char *filename;
#endif
			} stdio;
			struct {
				char *start;
				char *past_the_end;
				char *curr;
			} string;
		} rep;
	} port;

/* cell structure */
	struct cell {
		unsigned int _flag;
		union {
			struct {
				char_t *_svalue;
				size_t _length;
			} _string;
			struct {
				uint8_t *_bytes;
				size_t _length;
			} _bvector;
			struct {
				pointer *_data;
				pointer _name;
				size_t _length;
			} _struct;
			num _number;
			port *_port;
			foreign_func _ff;
			struct {
				struct cell *_car;
				struct cell *_cdr;
			} _cons;
			struct {
				pointer *_elems;
				size_t _length;
			} _vector;
		} _object;
	};

	// Interpreter state
	struct scheme {
// arrays for segments
		func_alloc malloc;
		func_dealloc free;

// return code
		int retcode;

		char **alloc_seg;
		pointer *cell_seg;
		int last_cell_seg;
		int backchar;

// We use 4 registers.
		pointer args;	// register for arguments of function
		pointer envir;	// stack register for current environment
		pointer code;	// register for current code
		pointer dump;	// stack register for next evaluation

		bool interactive_repl;	// are we in an interactive REPL?

		struct cell _sink;
		pointer sink;	// when mem. alloc. fails
		struct cell _NIL;
		pointer NIL;	// special cell representing empty cell
		struct cell _HASHT;
		pointer T;	// special cell representing #t
		struct cell _HASHF;
		pointer F;	// special cell representing #f
		struct cell _EOF_OBJ;
		pointer EOF_OBJ;	// special cell representing end-of-file object
		pointer oblist;	// pointer to symbol table
		pointer global_env;	// pointer to global environment
		pointer c_nest;	// stack for nested calls from C

// global pointers to special symbols
		pointer LAMBDA;	// pointer to syntax lambda
		pointer QUOTE;	// pointer to syntax quote

		pointer QQUOTE;	// pointer to symbol quasiquote
		pointer UNQUOTE;	// pointer to symbol unquote
		pointer UNQUOTESP;	// pointer to symbol unquote-splicing
		pointer FEED_TO;	// =>
		pointer COLON_HOOK;	// *colon-hook*
		pointer ERROR_HOOK;	// *error-hook*
		pointer SHARP_HOOK;	// *sharp-hook*
		pointer COMPILE_HOOK;	// *compile-hook*

		pointer free_cell;	// pointer to top of free cells
		long fcells;	// # of free cells

		pointer inport;
		pointer outport;
		pointer save_inport;
		pointer loadport;

		port load_stack[MAXFIL];	// Stack of open files for port -1 (LOADing)
		int nesting_stack[MAXFIL];
		int file_i;
		int nesting;

		char gc_verbose;	// if gc_verbose is not zero, print gc status
		bool no_memory;	// Whether memory allocation has failed

		char linebuff[LINESIZE];
		char *strbuff;
		int strbuff_size;

		FILE *tmpfp;
		int tok;
		bool print_flag;
		pointer value;
		pointer other_values;	// list with values after the first/main
		int op;

		void *ext_data;	// For the benefit of foreign functions
		long gensym_cnt;

		struct scheme_interface *vptr;
		void *dump_base;	// pointer to base of allocated dump stack
		int dump_size;	// number of frames allocated for dump stack
	};

// opcode names
	enum scheme_opcode {
#define _OP_DEF(name, minarity, maxarity, types, op) op,
#include "scheme-ops.h"
		OP_MAXDEFINED,
	};

#define cons(sc, a, b) _cons(sc, a, b, false)
#define immutable_cons(sc, a, b) _cons(sc, a, b, true)

#if USE_INTERFACE
	struct scheme_interface {
#define _INTERFACE(RETTYPE, NAME, REAL_NAME, ...) RETTYPE(*NAME)(__VA_ARGS__);
#include "interface.h"
	};
#endif

#if !STANDALONE
	typedef struct scheme_registerable {
		foreign_func f;
		const char *name;
	} scheme_registerable;

	void scheme_register_foreign_func_list(scheme * sc,
					       scheme_registerable * list,
					       int n);

#endif				// !STANDALONE

#ifdef __cplusplus
}
#endif
#endif
