[C] Kalkulator liczb rzeczywistych

0

Witam!

Na zaliczenie podstaw programowania dostałem do napisania następujący program:

"Napisać kalkulator liczb rzeczywistych:

  1. wczytywanie do 20 znaków w postaci działań i liczb
  2. zachowanie kolejności działań
  3. obsługa +,-,*,/,^
  4. zgłaszanie błędów"

Chodzi o to, aby kalkulator potrafił policzyć np. coś takiego:

2*4-(2.1+8)^(3-2)/18

zachowując naturalną kolejność działań, tj: nawiasy, potęgowanie, mnożenie/dzielenie, dodawanie/odejmowanie.
Ma być przy tym odporny na błędy typu dzielenie przez zero, zapisy rozpoczynające lub kończące się działaniami, itp.

Na początku trzeba zająć się przygotowaniem łańcucha znaków podanego przez użytkownika. Na dobry początek napisałem coś takiego:

#include <stdio.h>
#include <ctype.h>
#define MAX_DLUGOSC_WYRAZENIA 20
char* analizuj_wyrazenie(void);

int main()
{
  printf("%s\n", analizuj_wyrazenie()); //dla testu funkcji analizuj_wyrazenie
  system("PAUSE");    
  return 0;
}

char* analizuj_wyrazenie(void)
{
      int wyrazenie_poprawne=0;
      int blad_skladni;
      int wczytane_znaki;
      int indeks_tablicy;
      char znak_tymczasowy;
      static char zapis_ifiksowy[MAX_DLUGOSC_WYRAZENIA];
            
      while(wyrazenie_poprawne!=1)
      {
         blad_skladni=0;
         wczytane_znaki=0;
         indeks_tablicy=0;
         wyrazenie_poprawne=1;
         
         printf("Wprowadz wyrazenie, ktore chcesz obliczyc:\n");
         
         while((znak_tymczasowy=getchar())!='\n' && wczytane_znaki<MAX_DLUGOSC_WYRAZENIA)
         {
            if(isdigit(znak_tymczasowy) ||
                   znak_tymczasowy=='+' || znak_tymczasowy=='-' ||
                   znak_tymczasowy=='*' || znak_tymczasowy=='/' ||
                   znak_tymczasowy=='^' || znak_tymczasowy=='.' ||
                   znak_tymczasowy=='(' || znak_tymczasowy==')')
            {
               zapis_ifiksowy[indeks_tablicy]=znak_tymczasowy;
               indeks_tablicy++;
               wczytane_znaki++;
            }
            else
            {
               if(blad_skladni!=1) 
               {
                  printf("!!! Syntax ERROR !!! - blad skladni!\n");
                  blad_skladni=1;
                  wczytane_znaki++;
                  wyrazenie_poprawne=0;
               }
            }
         }
      }
      return zapis_ifiksowy;
}

Wydaje mi się jednak, że nie jest to do końca dobrze, ponieważ jeśli deklaruję tablicę, zawierającą 20 znaków, a zapełnię ją np. 10, to pozostałe miejsce zostanie uzupełnione przypadkowymi znakami.
Podobno lepiej jest to zrobić przez wskaźniki i dynamiczną alokację pamięci. Próbowałem tak zrobić, ale mam z tym ogromne problemy... Czy ktoś może mi w tym pomóc?

Z góry dziękuję za zainteresowanie tematem.

0

Strasznie kombinujesz zwykłe czytanie stringa z klawiatury. Wczytaj całego od razu za pomocą scanf() i dopiero potem sprawdzaj poprawność.
Bo tak jak jest, użytkownikowi będą wyskakiwać komunikaty przy każdej głupiej literówce, bez możliwości poprawienia backspace'em.

Obawiam się, że nie będziesz wiedział co dalej. Parę słów do wygóglania: parser arytmetyczny, stos, maszyna stanów.

0

Tia, zabawa zacznie się dopiero po wczytaniu stringa. Stojące przed Tobą zadanie nie jest trywialne, jak już wspomniał Azarien. Alternatywnym uniwersalnym sposobem rozwiązania jest zbudowanie w pamięci drzewa składni. Węzły mógłbyś zaimplementować w formie struktur. Jeden typ struktur mógłby reprezentować wyrażenie: zarówno operatory (+, - itd.), jak i wartości (liczby). Powiedzmy pole operator typu char przechowywałoby znak operatora ('+', '-' itd.), a pole value wartość liczbową (int lub double). Gdy pole operator ustawione byłoby na znak 0 (nie '0', tylko 0), to oznaczałoby to, że powinieneś spojrzeć na pole value. Struktury powinny też mieć pole (w formie tablicy) lub pola będące wskaźnikami do argumentów. Np. 2+3 to byłaby struktura reprezentująca operator '+', której jeden argument wskazywałby na strukturę reprezentującą wartość 2, a drugi -- 3

W każdym razie, samo przeglądanie łańcucha znaków możesz zrealizować dwuetapowo. Najpierw analizować tekst leksykalnie i przerobić go na ciąg tokenów. Tokenami mogą być już te docelowe struktury, tyle że jeszcze bez ustawionych argumentów. Na tym etapie możesz zechcieć wprowadzić sobie dodatkowe (pseudo)argumenty: nawiasy otwierające i zamykające.

W etapie drugim analizujesz płaski ciąg tokenów i robisz z niego drzewo składni, co nazywamy analizą składniową. Musisz wziąć pod uwagę priorytet operatorów i nawiasy (nawiasów używasz tylko do wskazania kolejności parsowania i nie wstawiasz ich do drzewa!). Np. wyrażenie 2 + 3*4 zamieniasz na drzewo:

[+]
|-[2]
|-[*]
  |-[3]
  |-[4]

Mając już takie drzewo łatwo obliczyć jego wartość. Robisz funkcję calculate() i startujesz w korzeniu. Jeśli bieżący węzeł jest wartością (liczbą), to zwracasz tę wartość (poznajesz to po tym, że operator == 0). Jeśli bieżący węzeł jest operatorem, to w zależności od typu operatora zwracasz calculate(argument_lewy) + calculate(argument_prawy), calculate(argument_lewy) - calculate(argument_prawy) itd.

W sumie analizę leksykalną i składniową możesz zrobić w jednym rzucie, choć w zwykłej notacji infixowej nie jest to takie proste jak w tej sztucznej odwrotnej notacji polskiej. Poza tym podzielenie analizy na trzy funkcje: analiza leksykalna, analiza składniowa i obliczanie umożliwia stosunkowo łatwe dostosowanie kalkulatora do obliczania innych notacji. Wystarczy tylko zmienić funkcję odpowiedzialną za analizę składniową.

0

To czego szukasz to algorytm ONP a dokadniej Odwrotna Notacja Polska (Reverse Polish Notation). Wiecej w google, ale latwiej znajdziesz taka biblioteke pod c++ niz c

0

@Piotr Zegar:
Odwrotna Notacja Polska...? Chyba coś Ci się pomyliło. Autor tematu wyraźnie napisał i pokazał, że chodzi o najzwyklejszą, klasyczną notację infiksową. ONP jest chyba najprostszą notacją do implementacji, ale najwyraźniej prowadzący zajęcia z podstaw programowania ZAKAZAŁ jej użycia. I autor tematu musi użyć zapisu wrostkowego. Dobrze o tyle, że jest dla ludzi znacznie bardziej naturalny i powszechniejszy niż ONP.

0

@bswierczynski: gdzie to masz napisane? ONP jest najprostszym rozwiązaniem tego problemu, ma najwięcej gotowców/przykładów w necie, i wreszcie nigdzie w treści postów autora wątku nie widzę niczego o nieużywaniu ONP. ale może nieuważnie przeczytałem treść wątku.

0

@ŁF:
Napisał to @pzielak w swoim poście.

pzielak napisał(a)

Chodzi o to, aby kalkulator potrafił policzyć np. coś takiego:

2*4-(2.1+8)^(3-2)/18

Kalkulator parsujący wyrażenia ONP nie policzy wyrażenia 2*4-(2.1+8)^(3-2)/18. Do tego potrzebny jest kalkulator z parserem notacji infixowej.

0

Ja bym się upierał, że fragment

2*4-(2.1+8)^(3-2)/18
zachowując naturalną kolejność działań, tj: nawiasy,
mocno sugeruje, że ONP oraz NP nie są dopuszczalne</quote></quote>

0

@bogdans_niezalogowany:
@ŁF:
Sama treść zadania -- tzn. tekst ujęty w cudzysłowach -- chyba nawet nie wyklucza tej ONP. Ale to, co dopisał autor -- że powinien zostać obliczony ten przykładowy ciąg zapisany w notacji infixowej -- już w 100% wyklucza napisanie kalkulatora obsługującego jedynie ONP. Taki parser po prostu nie obsłuży tego przykładowego ciągu.

Pozostaje pytanie, czy ten przykładowy ciąg to tylko komentarz autora tematu? Czy podał go na zajęciach prowadzący? Jeśli podał, to można założyć, że jest to część polecenia przekazana jedynie ustnie. Jeśli jednak prowadzący nic o tym nie mówił, to może i można zastosować ONP... choć może być to źle odebrane przez prowadzącego. Zawsze można go spytać, czy jest taka możliwość.

Tak czy siak, parser ONP jest wyraźnie prostszy, ale parser notacji infixowej jest za to bardziej przydatny i chyba więcej można się na nim nauczyć.

0

Zwróciliście uwagę, że w komentarzu jest mowa o zachowaniu naturalnej kolejności działań (między innymi nawiasów). Jak to pogodzić z ONP?

0

Komentarza nie da się pogodzić z ONP. Samą treść zadania może się i da. Pytanie, czy komentarz został napisany w oparciu o polecenie ustne wydane przez prowadzącego, czy to autor tematu tak to sobie sam wymyślił. No i pytanie o ambicje autora. Też pisałem kiedyś kalkulator na zajęciach i niektórzy stosowali ONP, a niektórzy nie. Ja zastosowałem notację infixową i chyba nawet bawiłem się w jednoargumentowe operatory (np. jednoargumentowy minus, czy plus [który w zasadzie nic nie robi]).

0

To ja może sprostuję. Generalnie chciałem zrobić w ten sposób, że wczytuję znaki podane przez użytkownika do tablicy. Następnie, jeśli wyrażenie jest poprawne, to przerabiam je na ONP i przy użyciu tej notacji obliczam wartość wprowadzonego wyrażenia.

Widzę, że temat wzbudził spore zainteresowanie. Od razu chcę wszystkim podziękować za chęć pomocy :)

@Azarien: zastanawiałem się nad scanf, ale jeśli we wczytywanym łańcuchu znajdzie się spacja, to scanf dojdzie do niej, a resztę pominie, np.:
"2+3 -5" scanf potraktuje jako "2+3". Chyba, że się mylę, bo jestem osobą, która zajmuje się C od 2,5 miesiąca...

P.S. Podpytałem się prowadzącego, jak najlepiej zrealizować to zadanie. Czy ifiksowo, czy poprzez Odwrotną Notację Polską. W odpowiedzi dostałem ONP.

0

Zerkij jednym okiem na ten kod i potraktuj go jako jeden ze sposobów. Algorytm jest prosty, na stos leci wszystko jak leci z małymi wyjątkami:

  1. Zamknięcie nawiasu usuwa ze stosu cały aktualny ciąg należący do nawiasu
  2. Dodanie operatora usuwa (oblicza) wszystkie operatory na stosie, jeśli tylko ich poziom 'ważności' jest niższy lub równy aktualnemu:
stos: 1+2
dodajemy + // lub jakikolwiek mocniejszy operator od tego między 1 a 2
1+2 zostaje zamienione na 3
i dopiero teraz dodajemy +
  1. Zmiana znaku (znak minusa) jest wprowadzana tuż po wykryciu dopisania liczby, jeżeli przed minusem jest jakikolwiek operator:
stos: 1+-
dodajemy 2
stos zmienia się na "1+" a "2" zmienia znak

Zatem miejsce na stosie jest oszczędzane, co można obliczyć jest obliczane.

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <windows.h>

#define USE_FLOAT     // włącz obsługę liczb rzeczywistych
#define SKIP_SPACES   // ignoruj spacje

#ifdef USE_FLOAT
	typedef float FINT;
#else
	typedef int FINT;
#endif

// typy tokenów na stosie
typedef enum {TYPE_ERROR=0, TYPE_INTEGER, TYPE_OPERATOR } TYPE;

// sposób przechowywania tokenów na stosie
typedef struct {
	TYPE type;
	char *src;
	union {
		int optype; // zawsze INT dla TYPE_OPERATOR
		FINT value; // INT lub FLOAT dla TYPE_INTEGER
	};
} NODE;

// numery błędów
typedef enum {
	ERROR_EMPTYSTACK=1,      // funkcja calcEval nie akceptuje pustego stosu
	ERROR_MISSINGOPERATOR, // nie znaleziono operatora w g_map
	ERROR_MISSINGINTEGER,    // operator wymaga dwóch liczb
	ERROR_INTEGEREXPECTED,   // na stosie pozostał operator zamiast liczby
	ERROR_BRACENOTOPENED,    // zamknięcie nawiasu bez otwarcia
	ERROR_BADOPERATOR,       // tylko '-' i '(' można dodać do pustego stosu
	ERROR_BADTOKEN,          // nieznany token - ani operator, ani cyfra
	ERROR_STACKOVERFLOW,     // brak miejsca na stosie, zwiększ STACKSIZE (jest poniżej)
	ERROR_BRACENOTCLOSED,    // niezamknięty nawias
	ERROR_EMPTYBRACE,        // pusty nawias
	ERROR_OPERATOREXPECTED   // oczekiwano operator przed liczbą
} CALCERROR;

// błędy o indeksach ERRORSTR[CALCERROR]
char *ERRORSTR[] = {"ok",
	"funkcja calcEval nie akceptuje pustego stosu",
	"nie znaleziono operatora na stosie",
	"operator wymaga dwoch liczb",
	"na stosie pozostal operator zamiast liczby",
	"zamkniecie nawiasu bez otwarcia",
	"tylko '-' i '(' mozna dodac do pustego stosu",
	"nieznany token - ani operator, ani cyfra",
	"brak miejsca na stosie",
	"nie zamkniety nawias",
	"pusty nawias",
	"oczekiwano operatora przed liczba"
};

#define STACKSIZE (4096/sizeof(NODE))

typedef FINT FUNC(FINT, FINT);

// mapowanie znaków na typy - to będzie tablica g_map [256] - dla 256 znaków ascii
typedef struct {
	TYPE type;
	int  level; // poziom pierwszeństwa: 0:niski
	FUNC *func;
} CHARMAP;

// pomocnicza struktura do obliczania wyrażeń w nawiasach
typedef struct {
	int spMin;
	unsigned char *text;
} RECURSIONPARAM;

static CHARMAP g_map[256];      // mapowanie znaków ascii na wewnętrzne typy i funkcje
static NODE g_stack[STACKSIZE]; // stos tokenów
static int  g_top;

// handlery operatorów
// printf pokazuje co jest aktualnie obliczne
	// operatory '%' oraz '^' nie działają z FLOAT
#ifdef USE_FLOAT
	FINT fnDodawanie(FINT a, FINT b)   {printf("func: %f+%f=%f\n", a,b,a+b); return a+b;}
	FINT fnOdejmowanie(FINT a, FINT b) {printf("func: %f-%f=%f\n", a,b,a-b); return a-b;}
	FINT fnDzielenie(FINT a, FINT b)   {printf("func: %f/%f=%f\n", a,b,a/b); return a/b;}
	FINT fnMnozenie(FINT a, FINT b)    {printf("func: %f*%f=%f\n", a,b,a*b); return a*b;}
	FINT fnReszta(FINT a, FINT b)      {printf("func: %f%%%f=%f\n",a,b,(FINT)((int)a%(int)b)); return (FINT)((int)a%(int)b);}
	FINT fnXor(FINT a, FINT b)         {printf("func: %f^%f=%f\n", a,b,(FINT)((int)a^(int)b)); return (FINT)((int)a^(int)b);}
#else
	FINT fnDodawanie(FINT a, FINT b)   {printf("func: %d+%d=%d\n", a,b,a+b); return a+b;}
	FINT fnOdejmowanie(FINT a, FINT b) {printf("func: %d-%d=%d\n", a,b,a-b); return a-b;}
	FINT fnDzielenie(FINT a, FINT b)   {printf("func: %d/%d=%d\n", a,b,a/b); return a/b;}
	FINT fnMnozenie(FINT a, FINT b)    {printf("func: %d*%d=%d\n", a,b,a*b); return a*b;}
	FINT fnReszta(FINT a, FINT b)      {printf("func: %d%%%d=%d\n",a,b,a%b); return a%b;}
	FINT fnXor(FINT a, FINT b)         {printf("func: %d^%d=%d\n", a,b,a^b); return a^b;}
#endif


// jednokrotna inicjalizacja - wypełnia tablicę mapującą znaki ascii na typy/funkcja
void calcInit(void)
{
	if (!g_map['0'].type)
	{
		for (int a='0'; a<='9'; a++)
		{
			g_map[a].type = TYPE_INTEGER;
		}
		g_map['^'].type  = TYPE_OPERATOR;
		g_map['^'].level = 0;
		g_map['^'].func  = fnXor;

		g_map['+'].type  = TYPE_OPERATOR; // '+' i '-' mają taki sam poziom
		g_map['+'].level = 1;
		g_map['+'].func  = fnDodawanie;

		g_map['-'].type  = TYPE_OPERATOR;
		g_map['-'].level = 1;
		g_map['-'].func  = fnOdejmowanie;

		g_map['%'].type  = TYPE_OPERATOR;
		g_map['%'].level = 2;
		g_map['%'].func  = fnReszta;

		g_map['*'].type  = TYPE_OPERATOR; // '*' i '/' mają taki sam poziom
		g_map['*'].level = 3;
		g_map['*'].func  = fnMnozenie;

		g_map['/'].type  = TYPE_OPERATOR;
		g_map['/'].level = 3;
		g_map['/'].func  = fnDzielenie;

		// indeksom 'literowym' można nadać typ TYPE_VARIABLE
		// a gdy po takim tokenie wystąpi nawias, to mamy funkcję...
	}
}
#define LEVEL_MAX 2 // najwyższy poziom ważności operatora

int calcEval(FINT *wynik, int spMin, char** ppv) // zwraca numer błędu
{
	// oblicza 'wartość' (int/float) tokenów na stosie, od pozycji spMin aż do g_top
	// w razie powodzenia, g_stack[spMin] będzie wynikiem, a g_top będzie równe spMin.
	// spMin to relatywny początek stosu - wymagany dla obsługi nawiasów
	if (!g_top)
	{
		// error: stos jest pusty
		*ppv = ""; // nie znamy pozycji w buforze
		return ERROR_EMPTYSTACK;
	}

	while (g_top > (spMin+1)) // od spMin to g_top
	{
		// znajdź pierwszy operator o najwyższym poziomie ważności
		int level = -1;
		int pos, posmax;
		FUNC *func;
		for (pos=g_top-1; pos>=spMin; pos--)
		{
			if (g_stack[pos].type == TYPE_OPERATOR) // mamy jakiś operator
			{
				// mamy operator
				int op = g_stack[pos].optype;
				int lvl = g_map[op].level;

				if (lvl > level) // zapamiętaj jego pozycję jeśli jest ważniejszy od poprzedniego
				{
					func   = g_map[op].func;
					posmax = pos;
					level  = lvl;
					if (level == LEVEL_MAX) break; // nie szukaj dalej
				}
			}
		}
		if (level == -1)
		{
			// error: nie znaleziono operatora, na stosie jest kilka liczb (huh?)
			*ppv = g_stack[pos].src;
			return ERROR_MISSINGOPERATOR;
		}
		// operator jest na pozycji posmax. Sprawdź czy po obu stronach jest liczba
		if ((g_stack[posmax-1].type != TYPE_INTEGER) || (g_stack[posmax+1].type != TYPE_INTEGER))
		{
			// error: brakuje liczby
			*ppv = g_stack[posmax].src;
			return ERROR_MISSINGINTEGER;
		}
		// oblicz: wynik wpisz na pozycji pierwszej liczby
		g_stack[posmax-1].value = func(g_stack[posmax-1].value, g_stack[posmax+1].value);
		// usuń operator i drugą liczbę ze stosu
		int removeCount = g_top - posmax;
		if (removeCount) memcpy(&g_stack[posmax], &g_stack[posmax+2], removeCount * sizeof(NODE));
		g_top -= 2;
	}
	// jeżeli na relatywnym początku stosu jest operator, zgłoś błąd
	if (!spMin && (g_stack[0].type != TYPE_INTEGER))
	{
		// error: oczekiwano liczby
		*ppv = g_stack[0].src;
		return ERROR_INTEGEREXPECTED;
	}
	*wynik = g_stack[spMin].value;
	return 0;
}

int calcParse(char *_text, void *reserved, FINT *wynik, char** ppv)
{
	// _text: wyrażenie matematyczne do obliczenia
	// reserved - zarezerwowane, musi być zerem (jeśli nie jest, to wskazuje na RECURSIONPARAM - obliczenie nawiasu)
	// wynik - no comment
	// ppv   - adres zmiennej typu char* która otrzymuje adres znaku (w buforze _text) który powoduje błąd
	NODE node;
	node.type = TYPE_ERROR;
	int  size, err;
	unsigned char *text = (unsigned char*)_text; // zmiana na unsigned dla indeksowania w g_map[]
	unsigned char *start = text;

	// jednokrotnie zainicjuj
	calcInit();

	if (!reserved) // jednokrotnie wyzeruj
	{
		g_top = 0;
		*ppv = NULL;
	}

	while (*text)
	{
#ifdef SKIP_SPACES
		while (*text==' ') text++;
#endif

		node.src = (char*)text; // zapisz początek tokenu
		node.type = g_map[*text].type; // i jego typ

		if (node.type == TYPE_INTEGER)
		{
#ifdef USE_FLOAT
			sscanf((char*)text, "%f%n", &node.value, &size);
#else
			sscanf((char*)text, "%d%n", &node.value, &size);
#endif
			text += size; // pomiń liczbę
integer:
			// jeżeli to liczba ze znakiem, zmień jej znak już teraz
			// stos: -liczba
			// stos: [cokolwiek][operator]-liczba
			if (g_top && (g_stack[g_top-1].type == TYPE_OPERATOR) && (g_stack[g_top-1].optype == '-'))
			{
				if (g_top == 1) // -5
				{
					g_top = 0; // usuń '-' ze stosu
					node.value = -node.value;
				}
				else if (g_stack[g_top-2].type == TYPE_OPERATOR) // 2*-3
				{
					g_top--; // usuń '-' ze stosu
					node.value = -node.value;
					// jeżeli zmieniamy znak wyrażenia w nawiasie "[tokeny]-(expression)" to
					// trzeba też przesunąć do tyłu  pozycję znaku '(' w nadrzędnej funkcji
					if (reserved) ((RECURSIONPARAM*)reserved)->spMin--;
				}
			}
			else if (g_top && (g_stack[g_top-1].type == TYPE_INTEGER))
			{
				// dwie kolejne liczby nie są dopuszczalne
				*ppv = node.src;
				return ERROR_OPERATOREXPECTED;
			}
		}
		else if (node.type == TYPE_OPERATOR)
		{
			if (!g_top && (*text != '-'))
			{
				// error: tylko '-' (i nawiasy) można dodać do pustego stosu
				*ppv = (char*)text;
				return ERROR_BADOPERATOR;
			}
			node.optype = *text;
			text++;
			// jeżeli poprzedni (aż do zera) operator jest mocniejszy lub równy aktualnemu "2+3+" lub "2*3+"
			// to można już conieco obliczyć zwalniając stos.
			int spMin = 0;
			if (reserved) spMin = ((RECURSIONPARAM*)reserved)->spMin;
			while (g_top >= (spMin+3))
			{
				int opPrev = g_stack[g_top-2].optype;
				if (g_map[opPrev].level >= g_map[node.optype].level)
				{
					err = calcEval((FINT*)&opPrev, spMin, ppv);
					if (err)
					{
						if (!*ppv) *ppv = (char*)text;
						return err;
					}
				}
				else
					break;
			}
		}
		else if (*text == '(')
		{
			// mamy otwarcie nawiasu. Użyjemy rekurencji by obliczyć wartość w nawiasie.
			// parametr 'reserved' użyjemy by zdobyć adres końca wyrażenia w nawiasie,
			// jako pozycję w buforze _text
			RECURSIONPARAM r;
			r.spMin = g_top; // zapamiętaj wierzchołek stosu bo chcemy obliczyć tylko od tego miejsca
			text++;
			// jeżeli _text jest pusty (np. gdy '(' jest ostatnim znakiem)
			if (!*text)
			{
				*ppv = (char*)text-1;
				return ERROR_BRACENOTCLOSED;
			}
			else if (*text == ')')
			{
				*ppv = (char*)text-1;
				return ERROR_EMPTYBRACE;
			}
			err = calcParse((char*)text, &r, &node.value, ppv);
			if (err)
			{
				if (!*ppv) *ppv = (char*)text;
				return err;
			}
			// a wynik potraktujemy jakby to była liczba. Skaczemy do handlera TYPE_INTEGER
			// żeby wykonać dodatkowe, opcjonalne akcje - np. zmiana znaku dla przykładu -(3+4)
			text = r.text; // text jest ustawiony za zamykającym nawiasem
			node.type = TYPE_INTEGER;
			// wynik już jest na stosie, więc ściągamy go
			g_top--;
			goto integer;
		}
		else if (*text == ')')
		{
			if (!reserved)
			{
				// zerowy poziom rekurencji - błąd, ')' bez '('
				*ppv = (char*)text;
				return ERROR_BRACENOTOPENED;
			}
			// zamknięcie nawiasu: obliczamy wszystko od początku nawiasu
			((RECURSIONPARAM*)reserved)->text = text+1;
			err = calcEval(wynik, ((RECURSIONPARAM*)reserved)->spMin, ppv);
			return err;
		}
		else
		{
			// error: nieznany token: odstęp, litera lub operator
			if (!*ppv) *ppv = (char*)text;
			return ERROR_BADTOKEN;
		}
		if (g_top >= STACKSIZE)
		{
			// error: przepełniony stos
			*ppv = (char*)text;
			return ERROR_STACKOVERFLOW;
		}
		g_stack[g_top].type  = node.type;
		g_stack[g_top].value = node.value;
		g_stack[g_top].src   = node.src;
		g_top++;
	}
	if (!reserved)
	{
		return calcEval(wynik, 0, ppv);
	}
	return calcEval(wynik, ((RECURSIONPARAM*)reserved)->spMin, ppv);
}

void StackDump(void)
{
	for (int a=0;a<g_top;a++)
	{
		NODE *node = &g_stack[a];
		printf("index: %d %s ", a, (node->type == TYPE_INTEGER) ? "LICZBA" : "OPERATOR");
		if (node->type == TYPE_INTEGER)
		{
#ifdef USE_FLOAT
			printf("%f\n", (double)node->value);
#else
			printf("%d\n", node->value);
#endif
		}
		else if (node->type == TYPE_OPERATOR)
		{
			printf("%c\n", node->optype);
		}
	}
}

int main()
{
	FINT wynik;
	char *pszError;
	//int err = calcParse("-2+-2*-2", 0, &wynik, &pszError);
	//int err = calcParse("2+2*2+5%2", 0, &wynik, &pszError);
	//int err = calcParse("2+2*2", 0, &wynik, &pszError);
	//int err = calcParse("2*2+2", 0, &wynik, &pszError);
	//int err = calcParse("1+-(2)", 0, &wynik, &pszError);
	//int err = calcParse("1 1", 0, &wynik, &pszError); // błąd

	// będzie błąd gdy USE_FLOAT nie jest zdefiniowane - nieznany token '.'
	int err = calcParse("2*4-(2.1+8)^(3-2)/18", 0, &wynik, &pszError); 

	if (err) // napisz jaki to błąd, który to znak, oraz wypisz zawartość stosu
	{
		printf("ERROR: %s \"%s\"\n", ERRORSTR[err], pszError);
#ifndef USE_FLOAT
		if (*pszError == '.') puts("brakuje #define USE_FLOAT");
#endif
		puts("\nDump stosu:");
		StackDump();
	}
	else // nie było błędu
	{
#ifdef USE_FLOAT
		printf("calcParse: wynik=%f\n", (double)wynik);
#else
		printf("calcParse: wynik=%d\n", wynik);
#endif
	}
}

Nie pisałem tego specjalnie żeby odpowiedzieć, miałem już ten kod, ale nie obsługiwał on nawiasów. Od razu mówię że liczba zmiennoprzecinkowa zaczynająca się od kropki nie jest uznawana jako liczba (w funkcji calcInit trzeba to dodać), oraz to, że zapewne brakuje obsługi kilku innych błędów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1