C Taschenrechner

asdfman

Commander
Registriert
März 2008
Beiträge
2.315
Mahlzeit.

Ich habe vor, in C einen Skriptinterpreter zu schreiben und bis jetzt bin ich so weit, dass er
zumindest mal als Taschenrechner zu gebrauchen ist. Ich würde mich freuen, wenn ihr mal
drüber guckt und mir Bugs meldet (die vermutlich noch zuhauf vorhanden sind).

Aktueller Sourcecode
Vorkompiliertes Binary(Windows)

Die Bedienung ist relativ einfach.

dim <ID> <N> deklariert ein Array mit dem Identifier ID und N Elementen.
set <ID> <EXP> weist der Variablen mit dem Identifier ID den Wert des Ausdrucks EXP zu.
print <EXP> gibt den Wert des Ausdrucks EXP aus.
Eine leere Zeile beendet den Interpreter/Taschenrechner.

Falls das irgendwie unklar erklärt ist, hier mal ein Beispieldurchlauf:
>> dim a 2
OK.
>> set a 1
OK.
>> set a(a-1) a
OK.
>> set a(1) (a(0)+9)*(a+2)
OK.
>> print a(1)
30
OK.

(Man beachte, dass die Variable a und das Array a() unterschiedliche Dinge sind.)

Ich würde mich über jede Form der Anregung, was den Evaluator für mathematische Aus-
drücke angeht, freuen. Wenn euch die Syntax der Eingaben nicht gefällt, kann ich das ver-
stehen, aber das bleibt eh nicht so und da kümmere ich mich selbst noch drum.

€: Ach, der Evaluator kennt im Moment die Operatoren +, -, *, /, sowie
&, |, ^ (bitweises AND, OR, XOR) und % (Modulo).
 
Zuletzt bearbeitet:
Die Doppeldeutigkeit von Variable und Array gefällt mir nicht ganz.
Was an Operatoren noch fehlt: Mehrdimensionale Arrays und dann Matrizenoperationen (wenigstens + * transpose()), sin cos tan sqrt pow also alles was der Taschenrechner so bietet ^^.
Ein Schmankerl wäre natürlich auch die Unterstützung von komplexen Zahlen sowie inv() auf Matrizen (also intervieren), oder aber die Matlab-Operatoren / und \.

Vielleicht auch ganz nützlich: round und trunc, damit man die Arrays auch immer richtig ansprechen kann.
Wenns ein Scriptinterpreter wird: size(), length()...

EDIT: Seh grad, sind ja nur Ganzzahlen (wie langweilig :D).
Wie wärs mit Typdefinitionen oder nem automatischen Castsystem für verschiedenste Datentypen.
Wie kann ich den Interpreter beenden?
 
Es geht mir erstmal um den Evaluator für mathematische Ausdrücke. Trigonometrische Funktionen z.B.
sind ja keine mathematischen Operationen im eigentlichen Sinn. Die werden sicher in einem anderen
Modul des Interpreters folgen.

Mehrdimensionale Arrays klingen sehr sinnvoll. Müsste mir mal Gedanken darüber machen, wie ich die
implementiere, also schiebe ich das erst einmal etwas nach hinten.

Dass der Evaluator nur mit Integern umgehen kann, lag an meiner Faulheit, weil ich das für am einfachsten
zu implementieren hielt und dann Dezimalzahlen aus dem Blick verloren habe. Das kommt dann auf jeden
Fall als Nächstes, weil ich es am wichtigsten von deinen Anregungen finde. Danke für die Erinnerung :D

Meinst du mit automatischem Castsystem, dass 1.5+0.5 zu einer Integer-2 gecastet werden? Bzw eine
Integer-3 durch Integer-2 zu Float-1.5? Klingt als wäre das geradezu notwendig. Kommt rein :3

Den Interpreter beendet man mit einer leeren Zeile. Einfach an der Eingabeaufforderung Enter drücken.
 
Wie wäre es mit eckigen Klammern für Array-Indices?
Sonst ist:
set a(a-1) -1
kaum von
set a (a-1)-1
zu unterscheiden.
 
Makefile:
kein all-Target.
clean (und all) ist kein phony target
Compile-Flags:
-fno-omit-frame-pointer -> sonst macht debuggen auf x64 keinen Spaß
-ansi -pedantic -> gehört zum guten Ton
-Wextra -> kann man getrost auch per default anmachen

var.c:
// -> C++-Kommentare, kompiliert nicht mit pedantic

errno.h -> Dateiname ist ne schlechte Idee, weil wegen C-Standard-Header <errno.h>


Zuviel Code um das mal ernsthaft durchzuschauen (TLDR ;)) Ein paar Unsauberheiten mit signed/unsigned spuckt schon der Compiler aus. Dazu Vermischung von z.B. int und size_t in der main. Valgrind war soweit ruhig, ein paar kleine Leaks die ich mir jetzt nicht im Detail angeschaut hab.

Parser schreibt man niemals selber, immer generieren lassen. Sonst stehst du direkt mit einem Bein in der Hölle. ;)

Der Meldung "Array redimensioned." sieht man nicht an, dass sie ein Fehler ist. Würde ich als "Erfolg, Array dimensionen geändert" verstehen.

Unittest-Suite und dazugehöriges check-Target wäre natürlich auch fein.


Weiter im Text (hab doch noch weiter gelesen *g*):
* Einrückung unsauber (Mischung aus non-tabs und tabs)
* strcpy -> von denen, die ich gesehen hab, lässt sich alles problemlos auf strncpy umbauen
* Viel copy und paste beim Speicherhandling. Kann nach meinem ersten Eindruck viel besser zusammengefasst werden, indem man ein paar Management-Funktionen dazu baut.
* var.c:366 (dimint) -> mknewvar -> unchecked malloc (auch wenn addtovarlist nix böses damit macht) und falscher Rückgabewert (ERROR_NONE statt ERROR_MALLC)
* help.c:11 (strtoup) -> implizite Annahme von ASCII
 
Zuletzt bearbeitet:
>Makefile: Extrawünsche hinzugefügt
>C++-Kommentare zu C-Kommentaren gemacht.
>errno.h in errlist.h umbenannt

>Valgrind ist bei mir zufrieden.
>Parser schreibt man niemals selber -> Ich will das aber lernen ;<
>Allen Fehlermeldungen explizit "ERROR:" vorangestellt.


>Einrückung unsauber: Kam vom hin- und herpasten zwischen VS und vim. Sollte jetzt passen.
>strcpy: zu strncpy() auch an Stellen, an denen strcpy() nichts falsch machen dürfte geändert.
>Viel copy und paste beim Speicherhandling: Muss ich mir mit mehr Zeit genauer ansehen.
>var.c:366 (dimint): Korrigiert.
>implizite Annahme von ASCII: Tja, Pech, liebe Chinesen ;<

Änderungen hier: https://github.com/SirDzstic/int
 
>Parser schreibt man niemals selber -> Ich will das aber lernen ;<
Hehe :) Ist ja auch gut, das mal gemacht zu haben. Die Lektion sollte aber sein: nicht selber schreiben. Man baut viel zu viele Bugs ein.

Offensichtlicher weiterer Bug:
Crash: div-by-zero mit Modulo möglich.

Integer-Namen dürfen mit % enden - funktioniert nicht:
Code:
>> dim a% 1
 OK.
>> set a%(0) 5
 Syntax error.
>> set a% 5
 OK.
>> print a%   
 0
 Syntax error.
>> print a%(0)
Floating point exception (core dumped)

Da hast du schon eine Doppeldeutigkeit von Namen und Operatoren drin.
 
Zuletzt bearbeitet:
Haha, das habe ich tatsächlich nie ausprobiert. Ich sollte das ganze eh mal in Tokens zerlegen und dabei
zwischen dem Modulooperator und den Endungen für Variablenbezeichner unterscheiden, dann erledigen
sich solche Probleme von allein.

€: Trotzdem peinlich.
Ergänzung ()

Okay, % als Suffix für Integer geht jetzt und ich mache mich als nächstes an Support für Dezimalzahlen
und deren implizite Typumwandlung. Testen weiter erwünscht. Ich kann auch gern ein aktuelles Windows
Binary bauen, falls es jemanden interessiert.

€: Noch zu dem Herrn, der [] für Indizes vorgeschlagen hat: Kann ich machen, aber das Problem, das du
angesprochen hast, soll sich früher oder später eh erledigen, weil ich doch gerne = als Zuweisungsoper-
ator einbauen würde am ende, so dass "SET" eh wegfällt.
 
Zuletzt bearbeitet:
getestet mit tarball auf Basis von bdb4ea1:

Code:
$ bin/int 
>> print a% + 5
Segmentation fault (core dumped)

Edit:
Zweiten Fehler entfernt, kam wohl durch meine Basteleien ;)

Da wird offensichtlich -1 als int an strncpy übergeben. -> Konversion nach size_t: 18446744073709551615

Solltest mal die int vs. size_t Geschichte sauber machen und immer und überall auf overflows und underflows checken.


N bisschen gedebuggt:

Hier fehlt ein Check:
src/eval.c in buildtree:
Code:
        opr = getoper(exp, &oprpos);
        if (opr != ERROR_NONE) {
          *status = opr;
          return NULL;
        }
Der Fehlercheck fehlte.
 
Zuletzt bearbeitet:
Ok, habe den Check etwas modifiziert eingebaut. geoper() schmeißt nur -1 als Fehler, ansonsten gibt es den
Eintrag in der Operatortabelle zurück.
Ergänzung ()

So. Jetzt habe ich Support für Dezimalzahlen eingebaut.
Endet eine Variable auf %, wird sie als Integer betrachtet, auf $ als String und ohne Endung als Dezimalzahl.

Vermutlich haben sich wieder 934943 bugs eingeschlichen.
Und ganz vielleicht guckt es sich sogar nochmal jemand an :o

€: Habe nochmal ein Windowsbinary gebaut:
https://dl.dropbox.com/u/13226560/medien/int-0.2.0.exe
 
Zuletzt bearbeitet:
Die Windowsbinaries sind das wichtigste, weil kaum jemand macht sich die Mühe, das mal schnell zu kompilieren... :D
Na ja, eine Unterscheidung von Variablen am Namen nach Typ halt ich für ... unelegant.

BTW: Ich finde es schlecht, wenn Operatoren als Name(-ssuffix) verwendet werden dürfen, weil dann kommts auf Leerzeichen an, find ich unschön.

Was machst du eig. wenn jemand einen String (der ruhig auch Groß-kleinschreibung unterstützten dürfte) mit Zahlen verrechnet, bei mir hat der einfach nur die Zahl genommen...
 
Hier ist nochmal der "Herr (woraus schließt du das?), der [] für Indizes vorgeschlagen hat".
Spätestens, wenn du Funktionen zulassen willst, musst du dir Gedanken darüber machen entweder Arrays und Funktionen mit gleichem Namen nicht zuzulassen oder [] zu verwenden. Wenn die Funktion sin() existiert, dann
Code:
sin = 0      // sin als Variable geht
sin(0) = 1   // sin als Array geht, wenn Funktionsaufrufe auf linker Seite der Zuweisung nicht erlaubt sind
sin = sin(0) // die rechte Seite wäre mehrdeutig, Arrayzugriff oder Funktionsaufruf?
 
Hancock schrieb:
Die Windowsbinaries sind das wichtigste, weil kaum jemand macht sich die Mühe, das mal schnell zu kompilieren... :D
Ich würde ungern einfach Binaries herunterladen und ausführen. Da ist mir Quellcode lieber :/

Na ja, eine Unterscheidung von Variablen am Namen nach Typ halt ich für ... unelegant.

BTW: Ich finde es schlecht, wenn Operatoren als Name(-ssuffix) verwendet werden dürfen, weil dann kommts auf Leerzeichen an, find ich unschön.
Habe ich (offensichtlich) von BASIC geklaut und die kommen doch auch damit klar. Außerdem ist
es in jeder Programmiersprache doof, Leerzeichen innerhalb von Variablenbezeichnern zu haben.
Wüsste nicht, welche Sprache das erlaubt.

Was machst du eig. wenn jemand einen String (der ruhig auch Groß-kleinschreibung unterstützten dürfte) mit Zahlen verrechnet, bei mir hat der einfach nur die Zahl genommen...
Dann müsste er einen Type mismatch error schmeißen. Tut er aber nicht. Sehe ich mir an.

r0b0t schrieb:
Hier ist nochmal der "Herr (woraus schließt du das?), der [] für Indizes vorgeschlagen hat".
Spätestens, wenn du Funktionen zulassen willst, musst du dir Gedanken darüber machen entweder Arrays und Funktionen mit gleichem Namen nicht zuzulassen oder [] zu verwenden.
Ich benutze halt das generische Maskulinum. Mit dem Rest hast du Recht. [] ist natürlich
einfacher zu implementieren, also mache ich das mal demnächst.
 
asdfman schrieb:
Habe ich (offensichtlich) von BASIC geklaut und die kommen doch auch damit klar. Außerdem ist
es in jeder Programmiersprache doof, Leerzeichen innerhalb von Variablenbezeichnern zu haben.
Wüsste nicht, welche Sprache das erlaubt.

Tut jetzt nichts zur Sache, aber Tcl erlaubt das zum Beispiel. Man muß den Bezeichner dann natürlich in Quotes / Braces setzen, damit der Interpreter das Leerzeichen nicht als Delimiter interpretiert.

Code:
set {variable name with spaces} 12235
puts ${variable name with spaces}

Nur weil du gefragt hast. ;)
 
Sehe da überhaupt keine Zweideutigkeit. Egal wie man da Leerzeichen setzt.
a+++b ist da doch etwas völlig anderes.
 
Die Mehrdeutigkeit ist absolut vorhanden.

1.) a%%b
2.) a%b

Was ist gemeint?
1.) a% modulo b? Oder vielleicht ist ein Syntax-Fehler gemeint?
2.) a% gefolgt von Variable? (Syntax-Fehler) Oder a modulo b?

Wenn du jeweils die korrekten Fälle als die gültigen annimmst, wie sieht dann die Parsing-Regel aus? Versuchen wir die mal zu formulieren:

Wenn Variable gefolgt von %, dann modulo. -> Funktioniert nicht wegen Fall 1
Wenn Variable gefolgt von %, dann Integer. -> Funktioniert nicht wegen Fall 2
Wenn Variable gefolgt von %, dann: variable% bekannt? Dann variable%, sonst modulo -> Funktioniert nicht, weil variable% und variable definiert sein können
Wenn Variable gefolgt von %, dann: rechts von % steht Variable? Dann modulo, sonst: Rechts von % steht %: dann: rechts von % steht variable: dann variable% modulo variable. Sonst: Fehler oder sowas: Sonst Fehler

Ich vermute, du willst den letzten Fall. Anhand der Komplexität der Regel dürfte schon klar sein, dass das eher bitter ist? ;) Außerdem ist die Regel rekursiv: a%%b%%c%%d%%e. Ich habe keine wirkliche Ahnung davon, aber das könnte schon eine kontext-sensitive Grammatik sein und braucht einen LALR-Parser? Wer das studiert hat und meint, das ist Bullshit, schlägt mich bitte mit nem Knüppel auf den Kopf :)

Dann gäbe es auch noch das hier:
a%-b

Was ist gemeint? a modulo unäres minus b? a% minus b? Ohne Operator-Prioritäten ist das nicht lösbar. (Nebenbei, modulo mit einer negativen Zahl ist undefined behaviour)

Mein Vorschlag wäre, den Parser komplett neu zu machen, z.B. auf Basis einer Stack Machine / Push Down Automat:
http://en.wikipedia.org/wiki/Pushdown_automaton

Außerdem kommt noch das Typ-Problem dazu. Du hast jetzt schon 6 Typen (int, string, float, intarr, strarr, decarr). Jetzt würde ich mir vorstellen, dass man Arrays der gleichen Dimension miteinander verrechnen können soll (-> Vektorrechnung). Dürfte äußerst kompliziert werden im bestehenden Code. Wenn jetzt noch komplexe Zahlen dazu kommen sollen, wird es echt mies vom Programmieraufwand.

Nebenbei: warum float und nicht double? Double ist der Typ, der ideal zur drunter liegenden Plattform passt. Floats sind meist nur unnötig unpräzise und vor allem lahm.
 
Zuletzt bearbeitet:
Die Operatoren werden von rechts nach links gesucht. Das erste, auf dass er trifft, was wie ein Operator
aussieht, wird wie einer behandelt:
a%%b%%c%%d%%e -> a% mod b% mod c% mod d% mod e.
a%-b -> a% - b

Wobei letzterer Fall wenn man sich ganz stark zwingen will eine Zweideutigkeit sein könnte. Unäres - wird
übrigens noch nicht richtig erkannt und behandelt. Wenn du mich jetzt mit einem klaren Beispiel überzeugen
kannst, dass es gar nicht möglich ist, zu entscheiden, wann ein - unär oder binär ist (oder es sonst einen
Ausdruck gibt, den der aktuelle Evaluator nicht entscheiden kann), könnte ich erwägen, den Evaluator neu
zu schreiben. Sonst versuche ich erst einmal den unären - Operator korrekt zu erkennen.

Außerdem gibt es Operatorprioritäten. Siehe operlist in oper.c. Der Dritte Wert einer Spalte gibt die Priorität
an. Kleinere Zahlen bedeuten niedrigere Priorität.

-- Rhabarberrhabarber. Keine Lust meinen schön geschriebenen Text von oben zu löschen, aber folgendes:

Die Suche nach Operatoren von rechts nach links gibt einen Bug, wenn der letzte Operand vom Typ Integer
ist. Bei print a%-b% hält er das letzte % für den Modulooperator und schmeißt einen Syntaxfehler.
Ich gehe davon aus, dass dieses Problem mit dem Aktuellen Aufbau des Evaluators nicht zu beheben ist und
ich vermutlich einen Tokenizer schreiben muss und dann einen neuen Evaluator. Nicht sicher, ob ich mir diese
Arbeit machen will.
 
Zurück
Oben