PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Visual Basic "Aufgabe rechnen lassen"


Geldmann3
2010-05-01, 13:31:18
Ich habe in Visual Basic ein einfaches Form, eine Textbox, und einen Button. In die Textbox soll der Nutzer irgendeine Rechnung schreiben können.
Bsp:5+6-2
In der Textbox soll das Ergebnis der zuvor eingegebenen Rechnung erscheinen wenn der Button geklickt wird.
Bsp.9
Wie kann ich dies bewerkstelligen?

Coda
2010-05-01, 13:56:53
Dafür musstest du den String in einen Syntaxbaum umwandeln und den dann auswerten. Das ist aber nicht so ganz einfach, prinzipiell wird es hier erklärt: http://en.wikipedia.org/wiki/Recursive_descent_parser

Es gibt hier aber z.B. auch was fertiges für .NET: http://blueanalysis.com/acidlibrary.php

Pinoccio
2010-05-01, 13:57:13
Muss es Infix-Notation sein? Post-(oder prä-)fix-Notation (http://de.wikipedia.org/wiki/Umgekehrte_Polnische_Notation) lässt sich leichter auswerten.

Vielleicht ist auch eval() (http://vb-tec.de/eval.htm) dein Freund. ;-)

mfg

Coda
2010-05-01, 13:59:34
Vielleicht ist auch eval() (http://vb-tec.de/eval.htm) dein Freund. ;-)
Du willst beliebigen vom Benutzer eingegeben Code ausführen? Err... Keine gute Idee.

Monger
2010-05-01, 14:02:54
Ein schönes Beispiel dafür, dass scheinbar total triviale Probleme manchmal ziemlich aufwändig zu programmieren sind...


Was in der Textbox steht, ist ja erstmal nicht mehr als ein Text. Diesen Text musst du auseinander brechen, und analysieren was hier Operator (+ - etc.) ist, und was Operand. Das macht man am geschicktesten mit regulären Ausdrücken.
Hässlich wird die ganze Sache, wenn die Reihenfolge der Operatoren beachtet werden muss (Klammer vor Multiplikation vor Addition etc.).

Nicht umsonst rechnen die einfachen Taschenrechner sofort das Zwischenergebnis aus, statt die Eingabe einer gesamten Formel zu erlauben. Letzteres ist nämlich gar nicht so unkompliziert.

Edit: bin mal wieder vieeel zu langsam!

huha
2010-05-01, 14:07:04
Stell die Rechnung als Binärbaum auf und arbeite den dann rekursiv ab.
Der Vorteil dabei ist, daß du auch relativ komplizierte Rechnungen richtig hinkriegst, ohne dich um die Details kümmern zu müssen.

Deine Beispielrechnung sieht dann z.B. so aus (i, ii aufstellen, iii, iv rekursiv lösen):

(i)
(+)
/ \
5 6

(ii)
(-)
/ \
(+) 2
/ \
5 6

(iii)
(-)
/ \
11 2

(iv)
9


-huha

Geldmann3
2010-05-01, 14:08:20
Du willst beliebigen vom Benutzer eingegeben Code ausführen? Err... Keine gute Idee.
Wenn ich die Eingabemöglichkeiten beschränke sollte das doch kein großes Risiko darstellen. Oder??
---
Naja, zuerst geht es mir darum es überhaupt hinzubekommen.
Methode 1: MS-Access den Ausdruck berechnen lassen
Hierfür muß im VB-Projekt eine Referenz auf "Microsoft Access 8.0 Object Library" o.ä. gesetzt sein.
Muss dafür Microsoft Access installiert sein?

Coda
2010-05-01, 14:18:11
Wenn ich die Eingabemöglichkeiten beschränke sollte das doch kein großes Risiko darstellen. Oder??
Um die Eingabemöglichkeiten entsprechend zu beschränken musst du es fast wieder vollständig parsen. Dann kannst du das mit dem eval gleich lassen.

Geldmann3
2010-05-01, 14:20:11
Um die Eingabemöglichkeiten entsprechend zu beschränken musst du es fast wieder vollständig parsen. Dann kannst du das mit dem eval gleich lassen.
Warum parsen? Wenn es doch von anfang an nur möglich ist, Zahlen und Rechenzeichen einzugeben.

Coda
2010-05-01, 15:18:04
Entschuldige, ich habe unter eval etwas anderes erwartet. Das was da beschrieben wird geht natürlich - sofern Access installiert ist.

Das ist natürlich auch nicht gerade schön.

Gnafoo
2010-05-02, 00:18:07
Diesen Text musst du auseinander brechen, und analysieren was hier Operator (+ - etc.) ist, und was Operand. Das macht man am geschicktesten mit regulären Ausdrücken.
Hässlich wird die ganze Sache, wenn die Reihenfolge der Operatoren beachtet werden muss (Klammer vor Multiplikation vor Addition etc.).

Deswegen splittet man die Aufgabe im Compilerbau ja auch in zwei Teile:

1.) Lexikalische Analyse: mit den regulären Ausdrücken die Token erkennen (Sprich: Zahlen, Plus, Minus, Klammern, ...) und den String in einen Tokenstrom umwandeln.

2.) Syntaktische Analyse: den kontextfreien Part erkennen, bei dem die regulären Ausdrücke scheitern und einen entsprechenden Syntaxbaum aufbauen. Und dafür ist der rekursive Abstieg, wie Coda es schon gesagt hat vermutlich das einfachste. Zumindest für diese relativ kleine Grammatik.

Wobei man hier vermutlich auch alles in der syntaktischen Analyse zusammenfassen kann.

Funky Bob
2010-05-02, 11:51:45
Hallo,
was dem Threadstarter evtl. helfen könnte (Schlagwörter):
Domain Speceific Language
ANTLR
Groovy

antlr gibts leider nur für C# in der .NET-Familie...
Groovy nur für Java wenn ich das richtig sehe.
Aber dann findest du evtl. noch was für VB und brauchst nur die Sprache definieren und nicht den Parser coden.
Groovylink:
http://communitymapbuilder.org/display/GROOVY/Writing+Domain-Specific+Languages

MfG Rene

Matrix316
2010-05-02, 14:09:59
Wie wäre es mit zwei Textboxen und dazwischen eine dropdownlist wo man ein Rechenzeichen auswählt? Da reicht ein einfaches switch/case um zu berechnen, bzw. nicht mal das unbedingt.

Der_Donnervogel
2010-05-02, 15:23:17
Ich hätte noch einen schnellen Hack anzubieten, den ich gerade in wenigen Minuten umgesetzt habe, nachdem ich den Thread gesehen hatte.

Die Idee ist, die Auswertung von VBScript machen zu lassen. Der auszuwertende Term wird in ein dynamisch erzeugtes VBScript verpackt, das die Auswertung erledigt. Anschließend muss man nur noch das Ergebnis zurück lesen. Einfach noch zwei Textboxen und einen Button erzeugen und der folgende Code macht das gewünschte:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox2.Text = EvalString(TextBox1.Text)
End Sub

Private Function EvalString(ByVal TextToEvaluate As String) As String
Dim tempResult As String = Path.GetTempFileName
Dim tempVBScript As String = Path.GetTempFileName + ".vbs"
Dim write As StreamWriter = New StreamWriter(tempVBScript)
Dim read As StreamReader
Dim result As String
write.WriteLine("Dim evalVal")
write.WriteLine("Dim objFSO, objTextFile")
write.WriteLine("evalVal = " + TextToEvaluate)
write.WriteLine("Set objFSO = CreateObject(""Scripting.FileSystemObject"")")
write.WriteLine("Set objTextFile = objFSO.OpenTextFile(""" + tempResult + """, 2, True)")
write.WriteLine("objTextFile.WriteLine(evalVal)")
write.WriteLine("objTextFile.Close()")
write.WriteLine("WScript.Quit")
write.Close()
Process.Start(tempVBScript).WaitForExit()
read = New StreamReader(tempResult)
result = read.ReadLine
read.Close()
File.Delete(tempResult)
File.Delete(tempVBScript)
Return result
End Function

Anzumerken wäre noch, dass hiermit natürlich Code-Injection Tür und Tor geöffnet ist. Außerdem könnte man sicher noch ein paar Sachen optimieren. Ich vermute z.B. dass man das Ergebnis auch ziemlich sicher über den Rückgabewert des Scripts an VB.Net zurückgeben kann und nicht den Umweg über das File gehen müsste. Allerdings habe ich in den Code nicht viel Zeit investiert. Vermutlich brauchte das Coden nicht viel mehr Zeit als diese Zeilen hier zu schreiben. ;)

robobimbo
2010-05-02, 21:12:55
Oder du kompilierst den VB Code zur Laufzeit, hier ein Beispiel für .NET < 4.0 - für 4.0 siehts ein klein wenig anders aus: http://www.activevb.de/cgi-bin/tippupload/show/34/Funktionswert_Rechner

Geldmann3
2010-05-07, 17:10:06
Die Idee ist, die Auswertung von VBScript machen zu lassen. Auf jeden Fall, eine geniale Idee. Ich werde es mal ausprobieren.

Geldmann3
2010-05-07, 22:01:54
@Der_Donnervogel

Wenn ich deinen Code in mein Projekt einfüge kommen folgende Fehlermeldungen:

Der Name "Path" wurde nicht deklariert.
-
Der Typ "StreamWriter" ist nicht definiert.
-
Der Name "File" wurde nicht deklariert.
-

Was muss ich tun , damit es funktioniert?

Der_Donnervogel
2010-05-08, 00:59:05
Es fehlt schlicht und ergreifend der Import von System.IO da ich nur die Funktionen kopiert habe und die Klasse drum herum weg gelassen habe (ich habe das in einer Testklasse gemacht wo auch noch andere Tests drinnen stehen).

Hier der vollständige Code:
Imports System.IO

Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox2.Text = EvalString(TextBox1.Text)
End Sub

Private Function EvalString(ByVal TextToEvaluate As String) As String
Dim tempResult As String = Path.GetTempFileName
Dim tempVBScript As String = Path.GetTempFileName + ".vbs"
Dim write As StreamWriter = New StreamWriter(tempVBScript)
Dim read As StreamReader
Dim result As String
write.WriteLine("Dim evalVal")
write.WriteLine("Dim objFSO, objTextFile")
write.WriteLine("evalVal = " + TextToEvaluate)
write.WriteLine("Set objFSO = CreateObject(""Scripting.FileSystemObject"")")
write.WriteLine("Set objTextFile = objFSO.OpenTextFile(""" + tempResult + """, 2, True)")
write.WriteLine("objTextFile.WriteLine(evalVal)")
write.WriteLine("objTextFile.Close()")
write.WriteLine("WScript.Quit")
write.Close()
Process.Start(tempVBScript).WaitForExit()
read = New StreamReader(tempResult)
result = read.ReadLine
read.Close()
File.Delete(tempResult)
File.Delete(tempVBScript)
Return result
End Function
End Class


Man kann das aber eigentlich auch schnell selber heraus finden. Wenn man aufs Rufezeichen bei der Fehlermeldung klickt, dann wird einem der richtige Import bereits angeboten.

Geldmann3
2010-05-08, 15:15:50
Vielen Dank! Es funktioniert perfekt!

huha
2010-05-08, 15:42:20
Ich weiß gar nicht, wo ich anfangen soll, aber: Die "Lösung" ist eine einzige Katastrophe, auf so vielen Ebenen. Wir können nur hoffen, daß die niemals für irgendwas Wichtiges eingesetzt wird.

-huha

Geldmann3
2010-05-08, 22:36:05
Warum?
So schlimm?

huha
2010-05-08, 22:50:33
Ja. Anstatt das sinnvoll zu machen, führst du eine Funktion in deinem Programm ein, die zwingend beispielsweise zwei Schreibzugriffe benötigt (lahm! Ggf. Probleme mit speziellen Datenträger-Konfigurationen oder Rechten!) und dann ein externes Programm zur Auswertung aufruft (Quell zahlreicher Probleme, Sicherheitslücken etc.--außerdem muß es natürlich installiert sein). Es ist vom Konzept her genauso bescheuert wie die Auswertung mit eval direkt in VB, allerdings noch viel schlimmer, da lahmer, fehleranfälliger und noch schwieriger abzusichern. Sollte der Code von irgendeiner anderen Person benutzt werden als von dir oder sogar für den Produktiveinsatz vorgesehen sein, würde ich dir dringend raten, dir andere Möglichkeiten zu überlegen, wie du das angehst. Wie es richtig gemacht wird, wurde ja bereits gesagt.

-huha

Der_Donnervogel
2010-05-09, 14:26:18
Schnelle Hacks haben es an sich, nicht die beste Lösung darzustellen, sondern nur diejenige die am schnellsten implementiert ist. ;)

Eine saubere Lösung schaut natürlich ganz anders aus. Bei Java hätte ich gesagt, nimm JavaCC. Dort gibts AFAIR ein Tutorial wo ein einfacher Rechner damit erzeugt wird. Im Prinzip wäre das ja fast schon die Lösung. Keine Ahnung ob es so etwas auch für VB gibt. Eine ganz kurze Googlesuche hat Tiny Parser Generator (http://www.codeproject.com/KB/recipes/TinyPG.aspx) zutage gefördert. Auf den ersten Blick schaut das nach dem ersten Schritt zu einer sauberen Lösung aus. ... die ist dann aber nicht mehr in 5 Minuten programmiert. Wenn der TS aber schon dran scheitert heraus zu finden dass ein "Import" fehlt, habe ich Bedenken ob er mit einem Parsergenerator klar kommt.

Coda
2010-05-09, 16:03:05
Wenn es sich um Visual Basic .NET dreht, kann man auch einfach ANTLR verwenden das C# ausspuckt. Ist doch eh egal bei .NET in welcher Sprache der generierte Code ist.

Geldmann3
2010-05-09, 17:15:58
Also ich merke mir, eine Antomkraftwerk-Steuerung sollte ich damit aus Sicherheitsgründen nicht programmieren. Und auf Rechnern die vb Scripts nicht ausführen können/dürfen oder sehr beschränkte Schreibrechte haben sieht es auch schlecht aus.

Der_Donnervogel
2010-05-09, 18:29:20
In einer Umgebung wo es um Sicherheit geht hat der Code nichts verloren. Das Problem ist dass man auf diese Art dem User erlaubt nicht nur zu rechnen, sondern beliebigen Code auszuführen. Kann man ganz einfach testen, einfach im Eingabefeld statt einer Rechnung folgendes eingeben:
0 : msgbox("Hallo, dein Computer wurde gerade mit Schadcode infiziert!")
Anstatt eine Messagebox auszugeben könnte man hier alles mögliche machen. Sofern (was ich hoffen will) allerdings nur lokale Benutzer drauf zugreifen können, ist es irrelevant. Wenn man VBScript vom Programm aus aufrufen kann, kann man auch selber per Texteditor eine vbs-Datei anlegen und ausführen. Der einzige Punkt wäre, wenn das Programm mehr Rechte hätte als der Nutzer. Dann könnte er diese Schnittstelle verwenden um sich mehr Rechte zuzuschanzen.

Das mit den Rechten könnte auch ein Problem sein. Allerdings wird man üblicherweise im temporären Verzeichnis Schreibzugriff haben. Der Code verwendet ja kein hart codiertes Verzeichnis sondern lässt sich den Ort und Namen der temporären Datei von der Umgebung geben. Sofern der User also ein temp-Verzeichnis besitzt funktioniert es. Die Sache mit VBScript ist auch eine Rechtefrage. Auf einer Standardinstallation von Windows sollte es aber funktionieren.

Wenn das Programm nichts kritisches ist, dann ist die Lösung ok. Ansonsten würde ich empfehlen eine bessere Lösung zu wählen. Es gibt sie ja, sie sind nur aufwändiger umzusetzen.