Syntax Check fast gut

Fragen zu Tkinter.
Antworten
lianaK
User
Beiträge: 10
Registriert: Montag 27. Juli 2009, 21:23
Wohnort: kassel

Hallo alle zusammen,

habe letzte Woche dieses calculator Mini Programm geschrieben.. läuft soweit alles gut calculator geht gut nur die Syntax controlle muckt herum wenn ich

Code: Alles auswählen

S=" hello " + Str(c) 


Eingebe. Endergebnis der Kalkulation stimmt , aber etwas fehlt nur was?

Hier mein Code example

Code: Alles auswählen

 

# a=10 : b=20 : c=30 : s="Hello" # #GO and valid syntax
# a=10 : b=20 : c=30 : s="Hello" : s=c+b ' #GO but invalid syntax
# a=10 : b=20 : c=30 : s="Hello " + str(c) #GO but syntax not valid

import tkinter as tk
from tkinter import filedialog

variables = {}  # Initialize variables dictionary

def calculate():
    line = entry.get()
    statements = line.split(":")
    for statement in statements:
        parts = statement.split("=")
        var_name = parts[0].strip()
        var_value = eval(parts[1].strip(), variables.copy())
        variables[var_name] = var_value
    output_text.delete(1.0, tk.END)  # Clear previous output
    output_text.insert(tk.END, variables.get('s', 'Invalid input'))

def syntax_check():
    line = entry.get()
    statements = line.split(":")
    valid_syntax = True
    
    for statement in statements:
        parts = statement.split("=")
        var_name = parts[0].strip()
        
        # Check if it's a string assignment
        if len(parts) == 2 and parts[1].strip().startswith('"') and parts[1].strip().endswith('"'):
            continue
        
        # Check if it's a string assignment with concatenation of a variable value
        if len(parts) == 2 and '+' in parts[1].strip():
            operands = parts[1].strip().split('+')
            for operand in operands:
                operand = operand.strip()
                if operand.startswith('"') and operand.endswith('"'):
                    continue
                elif operand in variables:
                    if not isinstance(variables[operand], str):
                        variables[operand] = str(variables[operand])  # Convert to string
                    continue
                else:
                    valid_syntax = False
                    break
            if not valid_syntax:
                break
    
        # Check if it's a numerical assignment with multiplication of two variables (a*b)
        if len(parts) == 2 and '*' in parts[1].strip():
            operands = parts[1].strip().split('*')
            for operand in operands:
                operand = operand.strip()
                if not operand.isdigit() and not operand.replace('.', '', 1).isdigit():
                    if operand not in variables:
                        valid_syntax = False
                        break
            if not valid_syntax:
                break
    
    if valid_syntax:
        output_text.delete(1.0, tk.END)
        output_text.insert(tk.END, "Syntax is valid")
    else:
        output_text.delete(1.0, tk.END)
        output_text.insert(tk.END, "Invalid syntax")


def clear_input():
    entry.delete(0, tk.END)

def load_file():
    file_path = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("Python files", "*.py")])
    if file_path:
        with open(file_path, 'r') as file:
            input_text = file.read()
            entry.delete(0, tk.END)
            entry.insert(tk.END, input_text)

def save_file():
    file_path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files", "*.txt"), ("Python files", "*.py")])
    if file_path:
        with open(file_path, 'w') as file:
            file.write(entry.get())

# Create Tkinter window
window = tk.Tk()
window.title("Expression Calculator")

# Input box with yellow background
entry = tk.Entry(window, width=50, bg="yellow")
entry.pack(pady=10)

# Syntax Check Button
syntax_check_button = tk.Button(window, text="Syntax Check", command=syntax_check)
syntax_check_button.pack()

# Calculate Button
calculate_button = tk.Button(window, text="Calculate", command=calculate)
calculate_button.pack()

# Clear Input Button
clear_input_button = tk.Button(window, text="Clear Input", command=clear_input)
clear_input_button.pack()

# Load File Button
load_file_button = tk.Button(window, text="Load File", command=load_file)
load_file_button.pack()

# Save File Button
save_file_button = tk.Button(window, text="Save File", command=save_file)
save_file_button.pack()

# Output box
output_text = tk.Text(window, height=5, width=50)
output_text.pack(pady=10)

window.mainloop()
Über Hilfe freue ich mich danke Gruß liana
save our souls
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

„läuft soweit alles gut” ist ein dehnbarer Begriff. Die erste Frage wäre, was denn laufen soll? Also wie ist die komplette Syntax, die unterstützt werden soll? An welcher Stelle ist das Parsen von Funktionsaufrufen definiert?

Das Programm hat noch einige Probleme, die das Finden Deines eigentlichen Problems nur schwierig möglich machst. GUI-Programmierung ist kompliziert, dazu sollte man Grundlegende Programmierkenntnisse haben, wie Funktionen und Klassen.
Außerdem muß man sich immer an ein paar Regeln halten: Benutze keine globalen Variablen. Alles was eine Funktion braucht, bekommt sie über ihre Argumente; Ergebnisse werden per `return` zurückgegeben; schreibe kurze einfache Funktionen, die ausführlich getestet sind; trenne GUI und eigentliche Funktionalität.
Eine Funktion sollte eine Aufgabe haben, und nicht viele. `continue` stört den Programmfluss, und sollte vermieden werden.

Im Detail machst Du vieles Doppelt; das aufspalten in Statements, das Zerlegen eines Statements. Das sollten alles Funktionen sein, die dann nur einmal existieren.
Ein Parser ist immer ähnlich aufgebaut; man zerlegt den Text in atomare Einheiten (Token) und prüft dann, ob die Abfolge der Token gültig ist. Du prüfst an vielen Stellen, ob eine Einheit in String, eine Variable oder eine Zahl ist. Bei komplexerer Syntax führt das zu expotentiellem Aufwand.
`eval` darf man nicht benutzen. Du schreibst doch schon einen Parser, dann nutze das Ergebnis davon auch, um den Ausdruck auszuwerten.
Ein Parser muß viele Zustände korrekt verarbeiten, daher ist es dort besonders wichtig, alles mit genügend Unit-Tests abzudecken. Man muß auch alle Randfälle berücksichtigen.
Ganz offensichtlich ist, dass Du den Randfall ›ein String enthält :, = oder "‹ nicht berücksichtigst.

Um also einen Parser von Grund auf zu lernen, fange an, den String in seine Einzelteile zu zerlegen, schreibe einen Tokenizer. Schreibe Unit-Tests, die auch prüfen, ob alle möglichen Inhalte von Strings zu den richtigen Tokens führen.
Als nächstes kannst Du einen Parser schreiben, der einen Mathematischen Ausdruck prüft: ein Ausdruck besteht aus einem oder mehreren Summanden, die mit + oder - verknüpft sind, ein Summand kann wiederum ein Produkt aus einem oder mehreren Elementen sein, die per * oder / verknüpft sind, wobei ein Element entweder eine Zahl oder eine Folge von "(", Ausdruck, ")" ist. Hier siehst Du, dass Du relativ schnell in eine Rekursion kommst, Du also innerhalb Deines Ausdrucks wieder einen Ausdruck findest. Rekursion ist kein Problem, wenn jedes der oben beschriebenen Schritte eigene Funktionen sind, also parse_expression, parse_sum, parse_product, usw.
Und auch hier, sollte jede dieser Funktionen wieder mit Unit-Tests auf Korrektheit geprüft werden.
lianaK
User
Beiträge: 10
Registriert: Montag 27. Juli 2009, 21:23
Wohnort: kassel

Danke Sirius für deine Hinweise und Kritik glaube ich sollte dann doch einen kleinen Parser schreiben und ja bin Anfängerin mit Python und lerne gerne dazu. Stimmt ein klarer Funktionsaufbau ist wichtig und ich dachte dass wäre alles viel einfacher :)

Gruss liana
save our souls
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei es da auch Bibliotheken gibt die einem beim schreiben von Parsern helfen. PyParsing, beispielsweise. Je nach dem was das Lernziel hier sein soll, würde das die Sache vielleicht vereinfachen. Natürlich nicht wenn das Lernziel Lexer/Parser selber schreiben ist.

Ich sehe auch gerade das am Ende `eval()` zum Einsatz kommt. Warum die Anweisungen mit einem Doppelpunkt trennen, wenn Python dafür das Semikolon verwendet? Dann könnte man einfach `exec()` verwenden, und falls vorheriger Syntaxcheck nötig ist `compile()`:

Code: Alles auswählen

In [244]: source = 'a=10 ; b=20 ; c=30 ; s="Hello " + str(c) #GO but syntax not valid'

In [245]: variables = {"__builtins__": {}}

In [246]: exec(source, variables)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[246], line 1
----> 1 exec(source, variables)

File <string>:1

NameError: name 'str' is not defined

In [247]: variables = {"__builtins__": {"str": str}}

In [248]: exec(source, variables)

In [249]: variables
Out[249]: {'__builtins__': {'str': str}, 'a': 10, 'b': 20, 'c': 30, 's': 'Hello 30'}

In [250]: exec("*ungültige Syntax*", variables)
Traceback (most recent call last):

  File /usr/local/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3550 in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  Cell In[250], line 1
    exec("*ungültige Syntax*", variables)

  File <string>:1
    *ungültige Syntax*
               ^
SyntaxError: invalid syntax

In [251]: compile(source, "<file>", "exec")
Out[251]: <code object <module> at 0x7f3d494fe550, file "<file>", line 1>

In [252]: compile("*ungültige Syntax*", "<file>", "exec")
Traceback (most recent call last):

  File /usr/local/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3550 in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  Cell In[252], line 1
    compile("*ungültige Syntax*", "<file>", "exec")

  File <file>:1
    *ungültige Syntax*
               ^
SyntaxError: invalid syntax
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten