{ ************************GRCALC************************ }

{
  Copyright (C) 2002 Marcello Barnaba <vjt@openssl.it>,
  licenziato secondo i termini della GNU GPL versione 2
  (www.gnu.org/gpl.html) o, ad opzione dell`utente,
  qualsiasi versione successiva.

  programma per visualizzare una semplice funzione matematica
  a schermo, ispirato dalla `Calcolatrice Grafica' del MacOS
  e da GNUplot (http://www.gnu.org/software/gnuplot).
}

program graphic_calc;

{ direttive per il coprocessore matematico. }
{IFDEF CPU87}
{$N+;$E-}
{ELSE}
{$N-;$E+}
{ENDIF}

{ disabilita l`inserimento nell`eseguibile dei simboli di debug. }
{$D-}

{ crt per clrscr, graph per la totalita` delle funzioni grafiche }
uses crt, graph;

{ linka i driver video nell`eseguibile, in modo da non essere
  costretti a distribuire il .BGI assieme al .EXE. }
procedure EGAVGADriver; external;
procedure CGADriver; external;
procedure HERCDriver; external;
{$L egavga.obj}
{$L cga.obj}
{$L herc.obj}

{ dichiarazioni forward per le funzioni da inserire nelle tabelle
  const. }
{$F+}
function calc_add(x, y : real) : real; forward;
function calc_sub(x, y : real) : real; forward;
function calc_mul(x, y : real) : real; forward;
function calc_div(x, y : real) : real; forward;
function calc_sin(x : real) : real; forward;
function calc_cos(x : real) : real; forward;
function calc_tan(x : real) : real; forward;
function calc_cotan(x : real) : real; forward;
function calc_asin(x : real) : real; forward;
function calc_acos(x : real) : real; forward;
function calc_atan(x : real) : real; forward;
function calc_ln(x : real) : real; forward;
function calc_exp(x : real) : real; forward;
function calc_sqrt(x : real) : real; forward;
{$F-}

type

	calc_func_t = string; { tipo della stringa che contiene la funzione inserita dall`utente }
	calc_error_t = shortint; { tipo del codice di errore gestito dalla calc_perror() }
	calc_term_type_t = shortint; { tipo del codice che identifica un tipo di termine all`interno della struttura 'calc_term_t' }
	calc_term_value_t = real; { tipo del valore di un termine }

	{ tipo puntatore a funzione per le funzioni matematiche }
	calc_func_handler_t = function(x : real) : real;
	{ tipo puntatore a funzione per gli operatori }
	calc_oper_handler_t = function(x, y : real) : real;

	calc_term_t_p = ^calc_term_t; { puntatore ad una struttura calc_term,
									per la creazione della lista collegata }
	calc_term_t = record { struttura termine, pesante in termini di memoria,
						   poiche` contiene tutti i possibili elementi che
						   possono identificare un termine: sia il valore,
						   che un operatore non possiede, sia due puntatori
						   a funzione, per le funzioni e gli operatori, ed
						   un puntatore alla struttura successiva.
						   alternativamente sarebbe stato possibile
						   utilizzare un oggetto base e poi utilizzare
						   l`incapsulamento e l`ereditarieta` per creare
						   strutture polimorfiche, ma a mio parere il
						   programma sarebbe diventato troppo complesso
						   e pesante. }
		term_type : calc_term_type_t; { codice tipo del termine }
		term_value : calc_term_value_t; { valore del termine, nel
										  caso in cui questo termine sia un numero }
		term_func_handler : calc_func_handler_t; { puntatore a funzione per
												   funzione matematica }
		term_oper_handler : calc_oper_handler_t; { puntatore a funzione per
												   operatore }
		term_next : calc_term_t_p;               { elemento successivo, nil
												   in caso di ultimo elem. }
	end;

	calc_parse_state_t = shortint; { tipo dello stato interno del parser }

const

	{ versione. }
	CALC_VERSION = '1.0';

	{ costanti per la gestione degli errori. }
	CALC_NO_ERROR = 1;
	CALC_ERROR_INITGRAPH = 2;
	CALC_ERROR_BADFUNC = 3;
	CALC_ERROR_BADINTEGER = 4;
	CALC_ERROR_FUNCTIONNOTDEFINED = 5;

	CALC_ERRORS = 5; { numero errori. }

	calc_errlist : array[1..CALC_ERRORS] of string = (
		'Nessun errore.',
		'Non sono riuscito ad inizializzare la modalita` grafica.',
		'Errore di sintassi nella funzione.',
		'Intero non valido.',
		'Funzione non definita in questo punto.'
	);

	{ tipi di termini. }
	CALC_TERM_TYPE_NUMBER = 1;
	CALC_TERM_TYPE_FUNCTION = 2;
	CALC_TERM_TYPE_OPERATOR = 3;
	CALC_TERM_TYPE_VARIABLE = 4;
	CALC_TERM_TYPE_BRACKETOPEN = 5;
	CALC_TERM_TYPE_BRACKETCLOSE = 6;

	{ costanti per gli operatori. }
	CALC_TERM_TYPE_OPERATOR_ADD = 1;
	CALC_TERM_TYPE_OPERATOR_SUB = 2;
	CALC_TERM_TYPE_OPERATOR_MUL = 3;
	CALC_TERM_TYPE_OPERATOR_DIV = 4;

	{ costanti che descrivono il numero di funzioni e operatori supportati. }
	CALC_FUNX = 10;
	CALC_OPS = 4;

	{ stati del parser. }
	CALC_PARSE_IDLE = 1;			{ inizio della stringa. }
	CALC_PARSE_READNUMBER = 2;		{ il parser sta leggendo un numero. }
	CALC_PARSE_READFUNCTION = 3;	{ il parser sta leggendo una funzione (sin, cos, ..) }
	CALC_PARSE_READOPERATOR = 4;	{ il parser sta leggendo un operatore (*, /, ..) }
	CALC_PARSE_READVARIABLE = 5;	{ il parser sta leggendo 'x' }
	CALC_PARSE_BRACKETOPEN = 6;		{ il parser sta leggendo '(' }
	CALC_PARSE_BRACKETCLOSE = 7;    { il parser sta leggendo ')' }

	{ settaggi grafici. }
	CALC_GRAPH_BAR_SHIFT = 4;		{ dimensione della barra relativa al font corrente. }
	CALC_GRAPH_DEFAULT_ZOOM = 50;	{ fattore di zoom di default. }
	CALC_GRAPH_LINE_COLOR = yellow;	{ colore della curva. }
	CALC_GRAPH_DELAY = 100;          { tempo di inattivita` tra il disegno di un pixel ed un altro. }
	CALC_GRAPH_CARTHESIO_TICK_HALF_LENGTH = 3; { meta` della dimensione di una stanghetta del piano cartesiano. }
	CALC_GRAPH_PROGRESSBAR_COLOR_DEFINED = blue; { colore della barra inferiore quando la funz. e` definita. }
	CALC_GRAPH_PROGRESSBAR_COLOR_NOTDEFINED = lightgray; { colore della barra inferiore quando la funz. non e` definita. }
	CALC_GRAPH_PROGRESSBAR_COLOR_OOB = red; { colore della barra inferiore
											  quando la funz. e` definita ma va oltre i limiti dello schermo. }
	CALC_GRAPH_PROGRESSBAR_WIDTH = 8; { spessore della barra inferiore. }

	{ costanti per calc_setgraph() }
	CALC_INIT = 0; { inizializza la mod. grafica. }
	CALC_CRT = 1;  { ripristina la mod. terminale. }
	CALC_GRAPH = 2;{ ripristina la mod. grafica. }
	CALC_CLOSE = 3;{ chiudi la mod. grafica. }

	{ tabella degli operatori }
	calc_oper_table : array [1..CALC_OPS] of calc_oper_handler_t =
		(calc_add, calc_sub, calc_mul, calc_div);

	{ tabella delle funzioni }
	calc_func_table : array [1..CALC_FUNX] of record { tipo della tabella delle funzioni supportate }
		func_name : string[5]; { nome della funzione }
		func_handler : calc_func_handler_t; { puntatore alla funzione gestore }
	end = (
		(func_name : 'sin'; func_handler : calc_sin),
		(func_name : 'cos'; func_handler : calc_cos),
		(func_name : 'tan'; func_handler : calc_tan),
		(func_name : 'asin'; func_handler : calc_asin),
		(func_name : 'acos'; func_handler : calc_acos),
		(func_name : 'atan'; func_handler : calc_atan),
		(func_name : 'ln'; func_handler : calc_ln),
		(func_name : 'e'; func_handler : calc_exp),
		(func_name : 'cotan'; func_handler : calc_cotan),
		(func_name : 'sqrt'; func_handler : calc_sqrt)
	);

var
	calc_func : calc_func_t; { stringa che contiene la funzione come scritta dall`utente. }
	calc_term : calc_term_t_p; { testa della lista collegata di termini. }
	calc_errno : calc_error_t; { codice errore attuale. }
	calc_zoom : integer; { valore di zoom nella visualizzazione corrente. }
	maxx, maxy : word; { x ed y massimi nella visualizzazione corrente. }

{ permetti le chiamate FAR per queste funzioni, in modo
  che possano essere chiamate tramite puntatori a funzione }
{$F+}

{ funzioni per il calcolo del valore delle operazioni base. }
function calc_add(x, y : real) : real;
begin calc_add := x + y; end;
function calc_sub(x, y : real) : real;
begin calc_sub := x - y; end;
function calc_mul(x, y : real) : real;
begin calc_mul := x * y; end;
function calc_div(x, y : real) : real;
begin
	calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
	calc_div := 0;
	if y <> 0 then begin
		calc_div := x / y;
		calc_errno := CALC_NO_ERROR;
	end;
end;

{ funzioni per il calcolo del valore delle funz. matematiche. }
function calc_tan(x : real) : real;
begin calc_tan := sin(x) / cos(x); end;

function calc_cotan(x : real) : real;
var y : real;
begin
	calc_cotan := 0;
	y := sin(x);
	if y <> 0 then
		calc_cotan := cos(x) / y
	else
		calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
end;

{ operazione di asin trovata sulla FAQ del newsgroup comp.lang.pascal }
function calc_asin(x : real) : real;
var _sqr, _sqrt : real;
begin
	_sqr := (1 - sqr(x));
	calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
	calc_asin := 0;

	if _sqr > 0 then begin
		_sqrt := sqrt(_sqr);
		if _sqrt <> 0 then begin
			calc_asin := arctan( x / _sqrt );
			calc_errno := CALC_NO_ERROR;
		end;
	end;
end;

{ operazione di asin trovata sulla FAQ del newsgroup comp.lang.pascal }
function calc_acos(x : real) : real;
var _sqr : real;
begin
	calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
	calc_acos := 0;

	if x <> 0 then begin
		_sqr := (1 - sqr(x));
		if _sqr > 0 then begin
			calc_acos := arctan(sqrt(_sqr) / x);
			calc_errno := CALC_NO_ERROR;
		end;
	end;
end;

function calc_sin(x : real) : real;
begin calc_sin := sin(x); end;

function calc_cos(x : real) : real;
begin calc_cos := cos(x); end;

function calc_atan(x : real) : real;
begin calc_atan := arctan(x); end;

function calc_exp(x : real) : real;
begin calc_exp := exp(x); end;

function calc_ln(x : real) : real;
begin
	calc_ln := 0;
	if x > 0 then
		calc_ln := ln(x)
	else
		calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
end;

function calc_sqrt(x : real) : real;
begin
	calc_sqrt := 0;
	if x > 0 then
		calc_sqrt := sqrt(x)
	else
		calc_errno := CALC_ERROR_FUNCTIONNOTDEFINED;
end;
{$F-}

{ procedura che stampa a schermo la descrizione
  di un errore identificato da un codice, ispirata
  alla perror(3) C ISO. ho scelto di passare l`errno
  come parametro poiche` non ci sono moltissimi errori
  da gestire in questo programma. }
procedure calc_perror(err : calc_error_t);
begin
	writeln('Errore: ', calc_errlist[err]);
end;

{ procedura per il rilascio della memoria occupata
  dalla lista dei termini della funzione. }
procedure cleanup;
var p, q : calc_term_t_p;
begin
	p := calc_term;
	while p^.term_next <> nil do begin
		q := p^.term_next;
		dispose(p);
		p := q;
	end;
	writeln;
end;

function calc_parse_func(f : calc_func_t) : boolean;
var i : shortint; { contatore principale }
var done : boolean; { abbiamo terminato il parsing ? }
var p : calc_term_t_p; { puntatore temporaneo per allocare gli elementi della lista }
var state : calc_parse_state_t; { stato interno del parser }
var token_buf : string; { buffer temporaneo per conservare
						  pezzi di stringa (come 'sin', 'cos')
						  per poi processarli in seguito con la func2id() }
var wasspace : boolean; { bool di utilita` che indica se il precedente char
						  era uno spazio (utile nel parsing di, ad es.
						  sin cos tan x }
var ret : boolean;

	{ setta il fallimento del parsing e conseguente ritorno false
	  della procedura genitore }
	procedure fail;
	begin
		done := true;
		ret := false;
	end;

	{ prende in input un nome di funzione matematica (sin, cos, tan . .)
	  e ritorna un ID da inserire in una struttura calc_term_t }
	function func2id(s : string) : shortint;
	var i : shortint;
	begin
		func2id := 0;
		for i := 1 to CALC_FUNX do begin
			if s = calc_func_table[i].func_name then begin
				func2id := i;
				exit;
			end;
		end;

	end;

	{ cambia lo stato interno del parser, effettuando gli
	  opportuni controlli di sintassi e salvando i dati
	  dello stato precedente nella struttura correntemente
	  puntata da p, e alloca il successore di p facendolo
	  puntare nella p corrente a term_next }
	procedure changestate(newstate : calc_parse_state_t);

		{ alloca un nuovo elemento della lista, e aggiorna
		  il valore corrente di p a questo nuovo elemento }
		procedure new_p;
		begin
			new(p^.term_next);
			p := p^.term_next;
			p^.term_next := nil;
		end;

	var good_syntax : boolean;
	var id : shortint;
	var val_code : integer; { valore di ritorno della val() }
	begin
		{ controllo sintassi. }
		good_syntax := false;
		case newstate of
			CALC_PARSE_READNUMBER : good_syntax :=
							(state = CALC_PARSE_READFUNCTION) or
							(state = CALC_PARSE_READOPERATOR) or
							(state = CALC_PARSE_BRACKETOPEN) or
							(state = CALC_PARSE_IDLE);
			CALC_PARSE_READOPERATOR : good_syntax :=
							(state = CALC_PARSE_READNUMBER) or
							(state = CALC_PARSE_BRACKETCLOSE) or
							(state = CALC_PARSE_READVARIABLE);
			CALC_PARSE_READFUNCTION : good_syntax :=
							(state = CALC_PARSE_IDLE) or
							(state = CALC_PARSE_READOPERATOR) or
							(state = CALC_PARSE_READFUNCTION) or
							(state = CALC_PARSE_BRACKETOPEN);
			CALC_PARSE_READVARIABLE : good_syntax :=
							(state = CALC_PARSE_IDLE) or
							(state = CALC_PARSE_READOPERATOR) or
							(state = CALC_PARSE_READFUNCTION) or
							(state = CALC_PARSE_BRACKETOPEN);
			CALC_PARSE_BRACKETOPEN : good_syntax :=
							(state = CALC_PARSE_IDLE) or
							(state = CALC_PARSE_READFUNCTION) or
							(state = CALC_PARSE_READOPERATOR) or
							(state = CALC_PARSE_BRACKETOPEN);
			CALC_PARSE_BRACKETCLOSE : good_syntax :=
							(state = CALC_PARSE_READNUMBER) or
							(state = CALC_PARSE_READVARIABLE) or
							(state = CALC_PARSE_BRACKETCLOSE);
			CALC_PARSE_IDLE : begin
								good_syntax :=
									 (state = CALC_PARSE_READVARIABLE) or
									 (state = CALC_PARSE_BRACKETCLOSE) or
									 (state = CALC_PARSE_READNUMBER);
								done := true;
							end;
		end;

		{ salvataggio termine corrente }
		if good_syntax then begin
			case state of
				CALC_PARSE_READNUMBER : begin
					new_p;
					p^.term_type := CALC_TERM_TYPE_NUMBER;
					val(token_buf, p^.term_value, val_code);
					if val_code = 0 then begin
						p^.term_func_handler := nil;
						p^.term_oper_handler := nil;
						token_buf := '';
						state := newstate;
					end
					else
						fail;
				end;
				CALC_PARSE_READFUNCTION : begin
					id := func2id(token_buf);
					if id <> 0 then begin

						new_p;

						p^.term_type := CALC_TERM_TYPE_FUNCTION;
						p^.term_value := 0;
						p^.term_oper_handler := nil;
						p^.term_func_handler :=
							calc_func_table[id].func_handler;

						token_buf := '';
						state := newstate;
					end
					else
						fail;
				end;
				CALC_PARSE_READOPERATOR : begin
					new_p;
					p^.term_type := CALC_TERM_TYPE_OPERATOR;
					p^.term_value := 0;
					p^.term_func_handler := nil;
					case token_buf[1] of
						'*' : p^.term_oper_handler :=
							 calc_oper_table[CALC_TERM_TYPE_OPERATOR_MUL];
						'+' : p^.term_oper_handler :=
							 calc_oper_table[CALC_TERM_TYPE_OPERATOR_ADD];
						'/' : p^.term_oper_handler :=
							 calc_oper_table[CALC_TERM_TYPE_OPERATOR_DIV];
						'-' : p^.term_oper_handler :=
							 calc_oper_table[CALC_TERM_TYPE_OPERATOR_SUB];
					end;
					token_buf := '';
					state := newstate;
				end;

				CALC_PARSE_READVARIABLE : begin
					new_p;
					p^.term_type := CALC_TERM_TYPE_VARIABLE;
					p^.term_value := 0;
					p^.term_oper_handler := nil;
					p^.term_func_handler := nil;
					state := newstate;
				end;
				CALC_PARSE_BRACKETOPEN : begin
					new_p;
					p^.term_type := CALC_TERM_TYPE_BRACKETOPEN;
					p^.term_value := 0;
					p^.term_oper_handler := nil;
					p^.term_func_handler := nil;
					state := newstate;
				end;
				CALC_PARSE_BRACKETCLOSE : begin
					new_p;
					p^.term_type := CALC_TERM_TYPE_BRACKETCLOSE;
					p^.term_value := 0;
					p^.term_oper_handler := nil;
					p^.term_func_handler := nil;
					state := newstate;
				end;
				CALC_PARSE_IDLE : state := newstate;
			end; { fine salvataggio termine corrente }

		end { fine controllo sintassi }
		else
			fail;
	end;

	{ verifica che le parentesi siano bilanciate }
	function check_brackets : boolean;
	var len : shortint;
	var i : shortint;
	var j : shortint;
	begin
		len := length(f);
		j := 0;
		check_brackets := false;

		for i := 1 to len do begin
			if f[i] = '(' then
				inc(j)
			else if f[i] = ')' then
				dec(j);
		end;

		if j = 0 then
			check_brackets := true;
	end;
	{ fine sottoprocedure }
begin

	{ metti a \0 il char successivo alla lunghezza della stringa. }
	f[length(f) + 1] := #0;

	{ inizializza lo stato interno del parser. }
	state := CALC_PARSE_IDLE;

	{ inizializza il contatore principale e
	  il buffer che conterra` pezzi della stringa
	  in input per processarli in seguito. }
	i := 0;
	token_buf := '';

	{ inizializza i puntatori e la lista collegata,
	  creando la testa che poi andra` tagliata.
	  questo per alleggerire l`algoritmo di aggiungimento
	  di elementi alla lista. se non venisse allocata una testa
	  subito, poiche` c`e` l`esigenza di conservare l`indirizzo
	  del primo elemento della lista per poi processarla in seguito
	  e scorrerla, quando verra` visualizzata la funzione, bisognerebbe
	  inserire una struttura di selezione binaria (if) nel ciclo
	  della procedura changestate, in modo che se la testa della lista
	  non e` ancora definita (e` nil), l`elemento che viene allocato
	  diventa la testa. allocando prima una testa fittizia, abbiamo la
	  certezza che durante il parsing il primo elemento e` gia` allocato,
	  risparmiando il controllo, e dovremo solo aggiungere nuovi elementi
	  alla lista. alla fine del parsing, se la sintassi e` corretta viene
	  tagliata la testa e calc_term viene fatto puntare al secondo elemento
	  della lista, che diventa il primo e la lista e` allocata
	  correttamente. }
	new(p);
	calc_term := p; { testa. }
	p^.term_next := nil;

	{ presumi che la funzione abbia sintassi corretta. }
	ret := true;

	{ inizia ad iterare. }
	wasspace := false;
	done := false;
	while not done do begin
		inc(i);
		case upcase(f[i]) of

			'X' : changestate(CALC_PARSE_READVARIABLE);

			'0'..'9' :
				begin
					if state <> CALC_PARSE_READNUMBER then
						changestate(CALC_PARSE_READNUMBER);
					token_buf := token_buf + f[i];
				end;

			'*', '+',
			'/', '-':
				begin
					changestate(CALC_PARSE_READOPERATOR);
					token_buf := token_buf + f[i];
				end;

			'A'..'Z' :
				begin
					if (state <> CALC_PARSE_READFUNCTION) or
						((state = CALC_PARSE_READFUNCTION) and wasspace) then begin
						changestate(CALC_PARSE_READFUNCTION);
						wasspace := false;
					end;
					token_buf := token_buf + f[i];
				end;
			'(' : changestate(CALC_PARSE_BRACKETOPEN);
			')' : changestate(CALC_PARSE_BRACKETCLOSE);

			' ' : wasspace := true;

			#0 : changestate(CALC_PARSE_IDLE);

			else
				fail;
		end;
	end;

	{ taglia via la testa della lista. }
	p := calc_term^.term_next;
	dispose(calc_term);
	calc_term := p;

	{ verifica che le parentesi siano bilanciate. }
	if ret then
		ret := check_brackets;

	if not ret then begin
		calc_errno := CALC_ERROR_BADFUNC;
                cleanup;
        end;

	calc_parse_func := ret;
end;

{ converti un intero in una stringa. }
function int2str(i : integer) : string;
var buf : string;
begin
	str(i, buf);
	int2str := buf;
end;

{ cuore del disegno del grafico: procedura che
  a seconda del valore x che gli viene passato,
  ritorna il valore corrispondente della y,
  scorrendo la lista dei termini ed effettuando
  le operazioni necessarie }

function get_y_value(x : real; var p : calc_term_t_p) : real;
var current_value : calc_term_value_t;
var current_oper : calc_oper_handler_t;
var currently_operating : boolean;

{ funzione ricorsiva per evalutare il valore di
  una singola o di una serie di funzioni (es. sin cos tan x)
  matematiche, partendo dall`elemento che gli viene passato
  tramite puntatore al termine corrente. }

function evaluate_func : calc_term_value_t;
var current_func : calc_func_handler_t;
var ret : calc_term_value_t;
begin

	current_func := p^.term_func_handler;
	p := p^.term_next;
	case p^.term_type of
		CALC_TERM_TYPE_FUNCTION :
			ret := current_func(evaluate_func);

		CALC_TERM_TYPE_VARIABLE :
			ret := current_func(x);

		CALC_TERM_TYPE_NUMBER :
			ret := current_func(p^.term_next^.term_value);

		CALC_TERM_TYPE_BRACKETOPEN :
			begin
				p := p^.term_next;
				ret := current_func(get_y_value(x, p));
			end;
	end;
	evaluate_func := ret;
end;

begin
	current_value := 0;
	current_oper := nil;
	currently_operating := false;
	while p <> nil do begin
		case p^.term_type of
			CALC_TERM_TYPE_NUMBER:
				begin
					if currently_operating then begin
						current_value := current_oper(current_value,
							p^.term_value);
						currently_operating := false;
					end
					else
						current_value := p^.term_value;
				end;
			CALC_TERM_TYPE_OPERATOR:
				begin
					current_oper := p^.term_oper_handler;
					currently_operating := true;
				end;
			CALC_TERM_TYPE_FUNCTION:
				begin
					if currently_operating then begin
						current_value := current_oper(current_value,
							evaluate_func);
						currently_operating := false;
					end
					else
						current_value := evaluate_func;
				end;
			CALC_TERM_TYPE_VARIABLE:
				begin
					if currently_operating then begin
						current_value := current_oper(current_value, x);
						currently_operating := false;
					end
					else
						current_value := x;
				end;
			CALC_TERM_TYPE_BRACKETOPEN:
				begin
					p := p^.term_next;
					if currently_operating then begin
						current_value := current_oper(current_value,
							get_y_value(x, p));
						currently_operating := false;
					end
					else
						current_value := get_y_value(x, p);
				end;
			CALC_TERM_TYPE_BRACKETCLOSE:
				begin
					get_y_value := current_value;
					exit;
				end;

		end;
		if p <> nil then
			p := p^.term_next;
	end;
	get_y_value := current_value;
end;

{ procedura per il disegno del grafico e
  zoom di esso }

procedure draw_graph;
var x, y : integer;
var vp : viewporttype;
var relmaxx, relmaxy : word;
var barcolor : shortint;
var p : calc_term_t_p;
begin
	getviewsettings(vp);
	with vp do begin
		relmaxx := (x2 - x1) div 2;
		relmaxy := (y2 - y1) div 2;
		x := ((x2 - x1) div -2);

		repeat
			p := calc_term;
			y := round(get_y_value(x / calc_zoom, p) * calc_zoom);
			if calc_errno = CALC_ERROR_FUNCTIONNOTDEFINED then begin
				barcolor := CALC_GRAPH_PROGRESSBAR_COLOR_NOTDEFINED;
				calc_errno := CALC_NO_ERROR;
			end
			else if (y > relmaxy) or (y < -relmaxy) then
				barcolor := CALC_GRAPH_PROGRESSBAR_COLOR_OOB
			else begin
				putpixel(x + relmaxx, relmaxy - y,
						 CALC_GRAPH_LINE_COLOR);
				barcolor := CALC_GRAPH_PROGRESSBAR_COLOR_DEFINED;
			end;

			setcolor(barcolor);
			line(x + relmaxx, (y2 - y1) - CALC_GRAPH_PROGRESSBAR_WIDTH, x + relmaxx, (y2 - y1));
			inc(x);
			delay(CALC_GRAPH_DELAY);
		until x > relmaxx;
	end;
end;

{ disegna il piano cartesiano. }
procedure draw_carthesio;
var vp : viewporttype;
var ts : textsettingstype;
var x, y, i : integer;
var relmaxx, relmaxy : word;
var th, tw : integer;
var str : string;
var increment : integer;
begin
	getviewsettings(vp);
	gettextsettings(ts);

	with vp do begin
		relmaxx := x2 - x1;
		relmaxy := y2 - y1;

		moveto(relmaxx div 2, relmaxy);
		lineto(relmaxx div 2, 0);
		moveto(0, relmaxy div 2);
		lineto(relmaxx, relmaxy div 2);

		increment := 40 div calc_zoom;
		if increment = 0 then increment := 1;

		settextstyle(ts.font, horizdir, ts.charsize);

		y := relmaxy div 2;
		x := relmaxx div 2;
		i := increment;
		while x < relmaxx do begin
			x := x + (increment * calc_zoom);
			{ stampa il tick. }
			line(x, y + CALC_GRAPH_CARTHESIO_TICK_HALF_LENGTH,
				 x, y - CALC_GRAPH_CARTHESIO_TICK_HALF_LENGTH);
			line(relmaxx - x, y + CALC_GRAPH_CARTHESIO_TICK_HALF_LENGTH,
				 relmaxx - x, y - CALC_GRAPH_CARTHESIO_TICK_HALF_LENGTH);
			{ stampa il numero. }
			str := int2str(i);
			th := textheight(str);
			tw := textwidth(str) div 2;
			outtextxy(x - tw, y + th, str);
			outtextxy(relmaxx - x - tw, y + th, '-' + str);

			i := i + increment;
		end;

		settextstyle(ts.font, vertdir, ts.charsize);

		i := increment;
		x := relmaxx div 2;
		while y < relmaxy do begin
			y := y + (increment * calc_zoom);
			{ stampa il tick. }
			line(x - 2, y, x + 2, y);
			line(x - 2, relmaxy - y, x + 2, relmaxy - y);
			{ stampa il numero. }
			str := int2str(i);
			th := textheight(str) * 2;
			tw := textwidth(str) div 2;
			outtextxy(x + th, y - tw, '-' + str);
			outtextxy(x + th, relmaxy - y - tw, str);

			i := i + increment;
		end;
		settextstyle(ts.font, ts.direction, ts.charsize);
	end;
end;

{ disegna una barra con del testo all`interno. }
procedure draw_bar(x1, y1, x2, y2 : word; str : string);
begin
	rectangle(x1, y1, x2, y2);
	outtextxy((x2 - textwidth(str)) div 2,
			  (y1 + (textheight(str) div 2)), str);
end;

{ procedura principale che disegna il piano,
  disegna la barra di stato, disegna il grafico,
  attende un input dall`utente e rilascia la memoria. }
procedure calc_graph_main;
var bar_width : word;
begin

	setwritemode(copyput); { usa MOV per disegnare i pixel a video }
	bar_width := textheight(calc_func) + CALC_GRAPH_BAR_SHIFT * 2;

	draw_bar(0, 0, maxx, bar_width, { barra superiore }
			 'y = ' + calc_func + ' [fattore di zoom: ' +
			 int2str(calc_zoom) + 'x]');

	draw_bar(0, (maxy - bar_width), maxx, maxy, { barra inferiore }
			 'risoluzione: ' + int2str(maxx + 1) +
			 ' x ' + int2str(maxy + 1));

	{ setta il viewport al piano cartesiano }
	setviewport(0, bar_width + 1, maxx, maxy - bar_width - 1, clipOn);
	{ disegna gli assi }
	draw_carthesio;
	{ disegna il grafico }
	setwritemode(xorput);
	draw_graph;
	{ attendi un input dall`utente }
	readkey;
	{ libera la memoria }
	cleanup;
end;

{ funzione per verificare la presenza di una modalita`
  grafica utilizzabile, per tornare in modalita` terminale
  e per settare nuovamente la modalita` grafica, utilizzando
  la risoluzione + elevata possibile.
  ritorna false in caso di fallimento }
function calc_setgraph(todo : shortint) : boolean;
var gd, gml, gmh : integer;
begin
	calc_setgraph := false;
	case todo of
		CALC_INIT :
			begin
				registerbgidriver(@EGAVGAdriver);
				registerbgidriver(@CGADriver);
				registerbgidriver(@HERCDriver);

				gd := detect;

				if gd = grOk then begin
					calc_setgraph := true;
					initgraph(gd, gml, '');
					maxx := getmaxx;
					maxy := getmaxy;
				end
				else
					calc_errno := CALC_ERROR_INITGRAPH;
			end;
		CALC_CRT : restorecrtmode;
		CALC_GRAPH :
			begin
				detectgraph(gd, gml);
				getmoderange(gd, gml, gmh);
				setgraphmode(gmh);
				maxx := getmaxx;
				maxy := getmaxy;
			end;
		CALC_CLOSE : closegraph;
	end;
end;

{ procedura per leggere i dati in input dall`utente }

function calc_read_data : boolean;
var buf : string;
var val_code : integer;
begin
	calc_errno := CALC_NO_ERROR;
	write('Inserire la funzione da disegnare [invio per uscire]: ');
	readln(calc_func);
	if calc_func <> '' then begin
		val_code := 0;
		repeat
			write('Inserire il fattore di zoom [default: ',
				CALC_GRAPH_DEFAULT_ZOOM, ' X]: ');
			readln(buf);
			calc_zoom := CALC_GRAPH_DEFAULT_ZOOM;
			if(buf <> '') then begin
				val(buf, calc_zoom, val_code);
				if val_code <> 0 then
					calc_errno := CALC_ERROR_BADINTEGER
				else begin
					calc_zoom := abs(calc_zoom);
					if calc_zoom > 1000 then
						val_code := 1;
				end;
			end;
		until val_code = 0;
		calc_read_data := true;
	end
	else
		calc_read_data := false;
end;

{ un simpatico banner :) }
procedure calc_banner;
begin
	writeln('** GRCALC versione ', CALC_VERSION, '.');
	writeln('** Copyright (C) 2002 Marcello Barnaba.');
end;

{$F+}
procedure calc_exitproc;
begin
	writeln;
	writeln('Grazie per aver utilizzato questo programma !');
	write('Premi un tasto qualsiasi per continuare . . .');
	readkey;
end;
{$F-}

{ main. verifica la presenza della modalita` grafica, e se presente,
  inizia un ciclo in cui viene chiesto all`utente la funzione che
  vuol disegnare, viene fatto il controllo della sintassi e viene
  quindi disegnata. per uscire e` sufficiente non immettere alcuna
  funzione. se la modalita` grafica non e` disponibile, il programma
  esce immediatamente. }
begin

	calc_errno := CALC_NO_ERROR;
	exitproc := @calc_exitproc;

	if calc_setgraph(CALC_INIT) then begin
		calc_setgraph(CALC_CRT);
		calc_banner;
		writeln('* Hardware grafico disponibile');
		writeln;

		while calc_read_data do begin
			if not calc_parse_func(calc_func) then begin
				calc_perror(calc_errno);
				writeln;
			end
			else begin
				calc_setgraph(CALC_GRAPH);
				calc_graph_main;
			end;
			calc_setgraph(CALC_CRT);
		end;
		calc_setgraph(CALC_CLOSE);
	end
	else
		calc_perror(calc_errno);
end.
