Syntax-Überprüfung

Hallo,
da es meine 1. Aufgabe hier ist, bin ich nicht ganz sichern, ob alles so stimmt, vorallem nicht mit Schwierigkeitsgrad.

Die Aufgabe
Baue ein Programm, welches einen (C o.ä.)-Sourcecode auf seine allgemeine Syntax-Richtigkeit untersucht.
Dabei soll überprüft werden, ob Klammern ({,(,[,],),}) ausreichen (und evt. richtig) gesetzt sind.
Das gleiche gilt für Anführungszeichen.
Dabei sollen Kommentare (z.B. // und /* ... */) und ähnliche Besonderheiten berücksichtigt werden.
Weiter Besonderheiten der Sprache, z.B. ; die einen Befehl begrenzen, sind optional.

Das Programm soll min. ausgeben, ob der Sourcecode Fehler enthält.
Optional noch wieviele und an welcher Stelle (Zeile:Zeichen).
Dies kann für Profis noch beliebig verfeinert werden, z.B. dass man die Reihnfolge von Klammern beachtet und ähnliches.

Da diese ja vom Sourcecode-Typ abhängt, bitte Angeben, für welche Sprache das Programm ist.
 
Ich denk mal du meinst den Source-Code von C++ Programmen, oder?
Vll sollte man da noch ein Prüfen von Semikolon-Zeichen einbauen, dass ist bei C++ schon sehr wichtig.
 
Hallo,
am besten eine C-ähnliche* Sprache, aber der Code muss ja immer auf das Programm abgestimmt sein.

z.B. ist in PHP ein # ein Kommetar, was in C nicht zutrifft.

Und dann dieses Programm so gut wie möglich Programmieren ;)


* Am besten keine Sprachen wie Assembler oder Basic, dafür enthalten diese Sprachen zuwenig prüfbare Merkmale.
 
hi

Da es zu in diesem Thread noch nie eine antwort gab hab ich mich mal ein bisschen rumgebastelt.
Mein programm testet zwar kein C, aber ich hoffe es ist auch ok wenn es nur php testet.

Es testet auf:

1. Ob man ausreichend Anführungszeichen benutzt hat.
2 ob man ausreichend Klammern benutzt hat.

PHP:
<?php
//Dieses Programm wurde nach der Aufgabenstellung geschrieben:
// http://www.hackerboard.de/thread.php?threadid=20946

//Achtung dieses programm kontrolliert nur php!
//Sachen die dieses Programm testet:
//  1.op man zu viele oder zu wenige " verwendet hat
//           (wenn Fehler mit Zeilen angabe)
//  2.op man zu wenige Klammern "{[()]}" benutzt hat
//           (leider ohne Zeilenangabe, waere mir sonst zu umstaendlich geworden)


//Ich übernehme keinerlei Haftung wenn das Programm eine Datei zerstören sollte.
//  <- Das sollte zwar nicht passieren aber warnen schadet ja nie ;-)

$file = "test_file.php"; //hier datei namen angeben

$zeilen=0;
$z = 0;
$Klammer[0] = 0;
$Klammer[1] = 0;
$Klammer[2] = 0;
$e=0;

if (is_file($file)) {
  $handle=fopen($file,"r");
  while (!feof($handle)) {
    $line = fgets($handle, 4096);
    if ($line != "") {
  	  $error1=0;
	    for ($j=0; $j<strlen($line); $j++) {
			  if ($j != 0) {
				  if ((substr($line,$j-1,1) == "\\") && (substr($line,$j,1) == "\\")) {
					  $kom = 1;
						$j = strlen($line);
					} else {
					  $kom = 0;
					}
				} else {
				  $kom = 0;
				}
				if ($kom == 0) {
  		    if (substr($line,$j,1) == "\"") {
	  		    if (substr($line,$j-1,1) != "\\") {
	    	   		if ($error1 == 1) {
		        	  $error1 = 0;
		      	  } else {
			          $error1 = 1;
	 			  	  }
	      	  }
		  	  }
  		    if (substr($line,$j,1) == "{") {
					  $Klammer[0]++;
					}
  		    if (substr($line,$j,1) == "[") {
					  $Klammer[1]++;
					}
  		    if (substr($line,$j,1) == "(") {
					  $Klammer[2]++;
					}
  		    if (substr($line,$j,1) == ")") {
					  $Klammer[2]--;
					}
  		    if (substr($line,$j,1) == "]") {
					  $Klammer[1]--;
					}
  		    if (substr($line,$j,1) == "}") {
					  $Klammer[0]--;
					}
  		  }
			}
	  	if ($error1 != 0) {
		    $error_1[$z] = $zeilen;
			  $z++;
	  	}
		}
	  $zeilen++;
    //fclose($handle);
  }
	if ($Klammer[0] != 0) {
	  if ($Klammer[0] > 0) {
		  print "Es sind ".$Klammer[0]." \"{\" Klammern zu viel<br>\n";
		}
	  if ($Klammer[0] < 0) {
		  $Klammer[0] = $Klammer[0] * -1;
		  print "Es sind ".$Klammer[0]." \"}\" Klammern zu viel<br>\n";
		}
	  $e=1;
	}
	if ($Klammer[1] != 0) {
	  if ($Klammer[1] > 0) {
		  print "Es sind ".$Klammer[1]." \"[\" Klammern zu viel<br>\n";
		}
	  if ($Klammer[1] < 0) {
		  $Klammer[1] = $Klammer[1] * -1;
		  print "Es sind ".$Klammer[1]." \"]\" Klammern zu viel<br>\n";
		}
	  $e=1;
	}
	if ($Klammer[2] != 0) {
	  if ($Klammer[2] > 0) {
		  print "Es sind ".$Klammer[2]." \"(\" Klammern zu viel<br>\n";
		}
	  if ($Klammer[2] < 0) {
		  $Klammer[2] = $Klammer[2] * -1;
		  print "Es sind ".$Klammer[2]." \")\" Klammern zu viel<br>\n";
		}
	  $e=1;
	}
	if (isset($error_1[0])) {
	  $e=1;
	  for($i=0; $i<count($error_1); $i++) {
		  print "Anfürungszeichen vergessen oder zu viel in zeile:".$error_1[$i]."<br>\n";
		}
	}
} else {
  $e=1;
  print "Die von ihnen angegebene Datei wurde nicht gefunden.".$file;
}

if ($e = 0) {
  print "Gratuliere! Das script konnte keine Fehler finden.";
}
?>


Jon2
 
Hallo,
nein, nur ein Programm, welches Testen, ob die Syntax soweit einwandfrei ist.

Bsp:
Wenn du 3 { Klammern öffnest, dann musst du diese auch wieder schließen.
Du musst alle ( schließen, bevor ein ; auftaucht.

(Natürlich muss man Strings und Kommentare berücksichtigen).
 
Das ist aber durchaus ein Parser, ein abgespeckter Parser. Parser werden uebrigens meist generiert und nicht von Hand geschrieben da das viel zu fehlertraechtig waere.

Die folgende Scanner.lex und Parser.cup generieren einen solchen Parser fuer Kommentarklammern in der Form /**/ unter Benutzung von JLex und javaCup.

Scanner.lex
Code:
import java_cup.runtime.Symbol;

%%

%cup

WHITE_SPACE_CHAR=[\r\n\ \t\b\012]

%%

"/*"  { return new Symbol(sym.OPENB); }
"*/"  { return new Symbol(sym.CLOSEB); }
","  { return new Symbol(sym.COMMA); }
\^d  { return new Symbol(sym.EOF); }

[a-zA-Z0-9\\\"]+ { return new Symbol(sym.WORD); }

{WHITE_SPACE_CHAR}+ { }

. { System.err.println("Illegal character: "+yytext()); }

Parser.cup
Code:
import java_cup.runtime.*;

/* Terminalsymbole */
terminal COMMA, WORD;
terminal OPENB, CLOSEB;

/* Nichtterminalsymbole */
non terminal Integer comment_sequence, comment, content;

/* Produktionen */

start with comment_sequence;

comment_sequence ::= comment:cmt
                     {: System.out.println("ok; Tiefe: " + (cmt.intValue())); :}
                   |  comment_sequence COMMA comment:cmt
                     {: System.out.println("ok; Tiefe: " + (cmt.intValue())); :}
                   ;

comment ::= OPENB content:ct CLOSEB
            {: RESULT = new Integer(ct.intValue()+1); :}
          ;

content ::= /* leer */
            {: RESULT = new Integer(0); :}
          | WORD content:ct
            {: RESULT = new Integer(ct.intValue()); :}
          | comment:cmt content:ct
            {: RESULT = (cmt.intValue() > ct.intValue() ? cmt : ct ); :}
          ;
 
Das wäre eine einfache Prüfung:
Code:
%prüfung an sich
%innerhalb der Klammern kann ein weiterer Ausdruck stehen
embedded_expression--> "{",expression,"}",!.
embedded_expression--> "(",expression,")",!.
embedded_expression--> "[",expression,"]",!.
% oder ein kommentar
comment --> "/*",ignore_all,"*/",!.      %dann ignoriere alle zeichen dazwischen
comment --> "//",ignore_line,!.

expression --> embedded_expression,expression,!.    %ein Ausdruck kann eingeklammert sein
expression --> not_reserved,expression,!.           %oder es ist irgendein Zeichen
expression --> comment,expression,!.                %oder ein Kommentar
expression --> [],!.                                %akzeptiere auch leere Worte

ignore_line --> "\n",!.                             %zähle die Zeile, fertig mit //
ignore_line --> [_],ignore_line,!.                  %sonst gehe zum nächsten Zeichen
ignore_line --> [].                                 %akzeptiere auch leere Kommentare

ignore_all --> comment,ignore_all,!.                %im Kommentar kann noch ein Kommentar sein
ignore_all --> [In], \+a_comment(In),ignore_all,!.  %sofern kein Kommentarende, weiter
ignore_all --> [].                                  %auch leere Kommentare sind ok

not_reserved -->[In],\+a_comment(In),{\+member(In,"{}()[]")}. %nicht reserviertes?
a_comment(In1)-->[In2],{[In1]=="*",[In2]=="/";
                        [In1]=="/",[In2]=="*";
                        [In1]=="/",[In2]=="/"}.

status([]):-write('Code ist ok!'),nl,!.
status(_):-write('Fehlerhaft!'),nl,!.

% validierung
validate(Input,Remain):- expression(Input,Remain).
validate(Filename):-read_file_to_codes(Filename,Input,[]),
                        validate(Input,Remain),status(Remain).
also die Zeichzen {}()[] // und /**/. Natürlich wird auch die "richtige" Klammerung
geprüft (sowas wie {[}] zieht nicht - denn das ist ein Fehler ;) )
Verschachtelte /* Kommentare */werden nicht unterstützt. In Kommentaren selbst darf natürlich alles stehen - es wird nicht berücksichtigt.
gestartet wird es mit "validate('code.java').
Er gibt aber nur ok oder fehlerhaft aus - das ist ja nicht der Sinn der Sache:
Code:
expr --> new_line,expr.                          %zähle die Zeilen für die Fehlerausgabe
expr --> "{", open_("{",Line),expr, "}",         %ein Ausdruck kann in Klammern
          close_("{",Line), expr,!.              %stehen - und einen Nachfolger haben
expr --> "(", open_("(",Line),expr, ")",         %die Öffnungen/Schließungen merken
          close_("(",Line),expr,!.               %wir uns mal
expr --> "[", open_("[",Line),expr, "]",
          close_("[",Line), expr,!.
expr --> "\"", open_("\"",Line),ign_str,"\"",
          close_("\"",Line), expr,!.
expr --> "\'", open_("\'",Line),ign_chr,"\'",
          close_("\'",Line), expr,!.
expr --> "/*",open_("/*",Line),ign_all,"*/",
          close_("/*",Line),expr,!.
expr --> "//", ign_line, expr,!.

expr --> not_reserved,expr,!.                    %oder ein (nicht reserviertes) Zeichen
expr --> not_reserved,!.                         %gefolgt von einer Expression
expr --> [],max_pos,!.                           %oder es ist leer -> potentielle
expr --> [],!.                                   %Fehlerstelle merken

ign_all -->new_line,ign_all,!.                   %zähle die Zeilen
ign_all --> [In],\+a_comment(In),ign_all,!.
ign_all -->[],max_pos,!.                         %Kommentar kann auch leer sein
ign_all -->[],!.

ign_str -->new_line,ign_str,!.                   %zähle die Zeilen
ign_str -->[In],{[In]\="\""},ign_str,!.
ign_str --> [],max_pos,!.
ign_str -->[],!.                                 %String kann auch leer sein

ign_chr -->new_line,ign_chr,!.                   %zähle die Zeilen
ign_chr --> [In], {[In]\="\'"},ign_chr,!.
ign_chr --> [],max_pos,!.
ign_chr -->[],!.

ign_line -->new_line,!.                          %Zeilenkommentar -> endet mit Newline
ign_line -->[_],ign_line,!.                      %ignoriere andere Zeichen
ign_line -->[],!.                                %Zeilenkommentar kann leer sein

new_line --> "\n", {b_getval(line,Line),         %Speichere Zeilennummern global
             Newline is Line+1,b_setval(line, Newline)}.

not_reserved --> [In],\+a_comment(In),{\+member(In,"{}[]()\n\"\'")},!.
reserved --> [In],a_comment(In);{member(In,"{}[]()\n\"\'")},!.      %reservierte zeichen
a_comment(In1)-->[In2],{[In1]=="*",[In2]=="/";
                        [In1]=="/",[In2]=="*"},!.


max_pos([E|List],[E|List]):-                     %Speichere die Zeichen und Zeilennummern
                   b_getval(line,Line),          %an potentiellen Fehlerstellen
                   reserved([E|List],_),          %aber nur falls diese wichtig genug sind
                   asserta(error_pos(E,Line)).
                   
open_(El,Line) --> {b_getval(line,Line),         %merke ein Zeichen samt Zeile in DB
                   asserta(open_pos(El,Line))},!.
close_(El,Line)--> {retract(open_pos(El,Line))},!.  %entferne Zeichen aus DB

%Ausgabe
status([]):-write('Code ist ok!'),nl,!.             %sofern kein Remainder -> Erfolg
status(_):- findall(Line,error_pos(_,Line),List),   %sonst: Fehler -> suche nach Ursachen
            max_list(List,Line),error_pos(Elem,Line),
            writef('Ich kam bis Zeile: %q, Zeichen: %s.Und was stellte ich fest?\n',
            [Line,[Elem]]),discover,!.
            
discover:- open_pos(Elem,Line),
           writef('Zeichen %s, Zeile %q wurde nicht geschlossen!',
           [Elem,Line]),!.
discover:- write('Das ist hier fehl am Platz,'),
           write('denn es wurd nie geöffnet!\n').

validate(Input,Rem):-retractall(error_pos(_,_)),    %entferne alte Einträge aus DB
                     retractall(open_pos(_,_)),     %setze Zeilencounter wieder auf 1
                     b_setval(line,1),              %und überprüfe die Eingabe
                     expr(Input,Rem),               %expr liefert einen Remainder zurück
                     status(Rem).                %ist dieser leer, wurde alles geparst.

validate(Filename):-
                  read_file_to_codes(Filename,Input,[]), validate(Input,_).
ist richtig viel - dafür aber werden zusätzlich noch "Strings" und 'Strings' unterstützt (in diesen darf natürlich auch alles stehen).
Mehr als die Hälfte des Codes ist nur für eine schöne Fehlermeldung ;)
Getestet mit Javaquellcodes. Parsen von 30000 Zeilen dauert ca 9 Sek.
Beispielinput:
Code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;




public class Main
{
	public static void main(String[] args)
	{

		if (args.length<=0) 
		{
			File file=new File("./hackish/TestClean/Declarations.hs");
			try
			{
				FileInputStream fis = new FileInputStream(file);
				byte[] buffer = new byte[(int) file.length()];
				fis.read(buffer, 0, (int) file.length());
				String src = new String(buffer);
				fis.close();
				Compiler compiler = new Compiler();
				
				compiler.parse(src);
				//compiler.compile();
				System.out.println(compiler.compile());
				System.out.print(compiler.getCompileError());
			} catch (Exception ex)
			{
				ex.printStackTrace(;
			}
			System.exit(0);
		}
		
		File file = new File(args[0]);
		Compiler compiler=null;
		try
		{
			FileInputStream fis = new FileInputStream(file);
			byte[] buffer = new byte[(int) file.length()];
			fis.read(buffer, 0, (int) file.length());
			String src = new String(buffer);
			fis.close();
			compiler = new Compiler();
			compiler.parse(src);
			File outFile=new File(file.getName()+".j");
			FileOutputStream fos=
				new FileOutputStream(outFile);
			fos.write(compiler.compileAll().getBytes());
			fos.close();
			if (compiler.getCompileError()!=null)
			{
				System.err.println(compiler.getCompileError());
			}
			//jasmin.Main.assemble(outFile.getPath());
		} catch (Exception e)
		{
			System.out.println(e);
			if (compiler==null) return;
			if (compiler.getCompileError()!=null)
			{
				System.err.println(compiler.getCompileError());
			}
		}

			
	}
}
Ausgabe dazu:

45 ?- validate('bsp.txt').
Ich kam bis Zeile: 32, Zeichen: }.Und was stellte ich fest?
Zeichen (, Zeile 31 wurde nicht geschlossen!
 
also ich würde einfach einen Stack erstellen, der von einer globalen funktion bei auftreten eines zu parsenden zeichens wie '[' oder '{' oder zeichenketten wie "/*" mit einem entsprechenden objekt gefüllt wird, das das zeichen (und andere Sachen, wie z.B. die farbe, in der der parser den sourcecode färben soll) enthält.
das oberste Stack element gibt immer den identifier des geschachteltesten Blockes, in dem wir uns gerade befinden, an. Falls wir nun im text auf ein Schlusszeichen wie '}', ']' oder "*/" stoßen, poppen wir das oberste element von Stack :)
Vorher haben wir aber natürlich geprüft, ob das oberste Element überhaupt diesen Identifier enthält. Falls nicht, dann hatten wir evtl. sowas wie das hier: "[ { ] }" -> Ein Fehler.

edit: solche sachen wie Kommentare bis zum Ende der Zeile ( "// kommentar" ) können hiermit natürlich nicht ohne weitere anstrengungen geprüft werden. Das gleiche gilt auch für abschließende Semikolons (seltsame pluralform), die ja das ende einer anweisung bedeuten; was eine solche Anweisung ist und wann sie zu ende ist, kann dieses Programm aber nicht wissen.
 
Jep, das wäre die einfachste Variante. Allerdings würde es in echten Sourcen viel rumgemeckere geben, weil man damit weder Kommentare, noch Strings oder andere Sachen berücksichtigen würde ;) :
Bsp:
Code:
public class Main
{
  //nichts machen ;-)
   print("nichts :-)");
  /* hier wollte ich das Nichtsmachen anders machen 
    { print(Nichts); */
}
Der Code ist valide - allerdings würde die einfache Stack-basierte Methode es nicht feststellen können. Man müsste noch "Stringmodus"/"Commentarmodus" einfügen - und da wäre man eigentlich auch schon bei einem einfachen Parser.
 
vllt ein Ansatz

hab mal was angefangen.
hier der C++ Code:

Code:
/*
Mein Syntax Test:
- prüft wiviele Klammern geöffnet bzw. geschlossen wurden,
- prüft ob ein (") Anführungszeichen, nach dem es (geöffnet) wurde auch wieder geschlossen wird, in der gleichen Zeile
- prüft ob mehr Semikolon als Zeilen existieren, okay ist jetzt ziemlich ungenau aber immerhin ein Test

Habe alles in die Main gestopft da es mir ehrlich gesagt zu umständlich wäre es in Funtionen zu packen,
und mir würden auch keine passenden einfallen, würde alles meiner Meinung nach nur unnötig verkomplizieren,
der schlechte Programmierstil sei mir verziehen, bin ja noch ein Rookie...
evtl. werde ich noch weiter daran arbeiten.. oder vllt ja auch jemand anders aber das wär wohl eher unwahrscheinlich ;)
*/
#include <iostream>
#include <string>
#include <fstream>
#include <windows.h>
#include <conio.h>
using namespace std;


int main()
{
    ifstream datein;
    bool comment = false, checkit = false;
    char zeichen;
    string eingabe;
    int zeile = 1;
    short rka = 0, rkz = 0, gska = 0, gskz = 0, eka = 0, ekz = 0, semi = 0, err = 0; 

    cout << "Syntax \x9A" << "berpr\x81" << "fung von C++ Quelltexten" << endl;
    cout << "Welche Datei m\x94" << "chten Sie testen: ";
    cin >> eingabe;

    datein.open(eingabe.c_str(), ios_base::in);

    if(!datein)
    {
        cerr << "Datei konnte nicht ge\x94" << "ffnet werden!" << endl;
        cerr << "Dr\x81" << "cken Sie eine beliebige Taste um das Programm zu beenden" << endl;
        getch();
        return -1;
    }


    while (!datein.eof())
    {
        datein.get(zeichen);
        cout << zeichen;

        if(zeichen == '\n')
        {
            if(comment == true)
            {
                err++;
            }
            comment = false;
            checkit = false;
            zeile++;
        }
        
        // hier beginnt die syntax-überprüfung, ich mache es in der main-function weil einfacher ...
        if (zeichen == '(')
            rka++;
        if (zeichen == ')')
            rkz++;
        if (zeichen == '{')
            gska++;
        if (zeichen == '}')
            gskz++;
        if (zeichen == '[')
            eka++;
        if (zeichen == ']')
            ekz++;
        
        if (zeichen == ';')
            semi++;
        
        if (zeichen == '"' && checkit == false)
        {
            comment = true;
            checkit = true;
        }

        if (zeichen == '"' && checkit == true)
        {
            comment = false;
            checkit = false;
        }

    }
    datein.close();
    system("CLS");
    // Ergebnisse werden auf dem Bildschirm ausgegeben...

    cout << "gefundene Fehler / wenn nichts angezeigt wird bist du ein guter Coder ;-)" << endl;
    
    if(rka != rkz)
    {
        cout << "Syntax Fehler - es wurden " << rka << " ( gefunden und " << rkz << " )" << endl;
    }

    if(gska != gskz)
    {
        cout << "Syntax Fehler - es wurden " << gska << " { gefunden und " << gskz << " } " << endl;
    }

    if(eka != ekz)
    {
        cout << "Syntax Fehler - es wurden " << eka << " [ gefunden und " << ekz << " ]" << endl;
    }

    if(semi > zeile)
        cout << "Sie haben mehr ; (Semikolon) als Zeilen benutzt, checken Sie das nochmal durch..." << endl;

    cout << "Es wurden " << err << " weitere Fehler enteckt (Anf\x81" << "hrungszeichen, ...)" << endl;

    cout << endl << "Dr\x81" << "cken Sie eine beliebige Taste um das Programm zu beenden" << endl;
    getch();
    return 0;
}
 
Soo langweilig heut nacht^^

Sehr simple Prüfung nach dem Prinzip des Kellerautomaten. Schaut nur nach ({[ und ignoriert Strings ('\'', '"' und Konsorten werden als Fehler gewertet - deswegen kriegt er auch seinen eigenen Quellcode nicht validiert) oh und nach ; schaut er auch ( () regel)
Erkennt die C++ Operatoren ->,<< und >> korrekt als nicht-klammerung an

A c h t u n g : Akut Makroverseuchter und Halb-OOP Code ahead!
Code:
#include <string>
#include <ios>
#include <iostream>
#include <fstream>
#include <stack>

enum stack_type
{
    ST_CURLY_BRACKETS = 0,
    ST_ROUND_PARENTHESIS = 1,
    ST_SQUARE_BRACKETS = 2,
    ST_CHEVRON = 3
};

#define STACK_TYPE_COUNT 4

struct stack_location
{
    // Der GCC will wohl dafür einen expliziten Konstruktor
    stack_location(stack_type t, uint32_t c)
    {
        this->type = t;
        this->line = c;
    }

    stack_type type;
    uint32_t line;
};

struct context
{
    std::ifstream file;
    std::string filename;
    std::stack<stack_location*> stack[STACK_TYPE_COUNT];
    uint32_t line;
};

std::string STtoName(stack_type t)
{
    switch(t)
    {
        case ST_CURLY_BRACKETS: return "curly braces"; break;
        case ST_ROUND_PARENTHESIS: return "round parenthesis"; break;
        case ST_SQUARE_BRACKETS: return "square brackets"; break;
        case ST_CHEVRON: return "chevrons"; break;
        default: return "unknown"; break;
    }
}

#define CREATE_CASE_STMT_PUSH(type) \
    ctx.stack[type].push( new stack_location(type, ctx.line) ); \
    break;
#define CREATE_CASE_STMT_POP(type) \
    if(ctx.stack[type].size() == 0) \
    { \
        std::cout << "kellerat: fatal: too many closing " << STtoName(type) << " in line " << ctx.line << std::endl; \
        return 100; \
    } \
    else \
    { \
        ctx.stack[type].pop(); \
    } \
    break;

#define CREATE_STMT(chra, chrb, type) \
    case chra: CREATE_CASE_STMT_PUSH(type) \
    case chrb: CREATE_CASE_STMT_POP(type)

#define CREATE_CHECK(type) \
    if(ctx.stack[type].size() % 2) \
    { \
        std::cout << "kellerat: fatal: not all " << STtoName(type) << " closed" << std::endl; \
    }

int main(int argc, char** argv)
{
    context ctx;
    ctx.line = 0;

    if(argc == 2)
    {
        ctx.filename = argv[1];
        ctx.file.open(ctx.filename.c_str(), std::ifstream::in);

        if(!ctx.file.is_open())
        {
            std::cout << "kellerat: cannot open " << ctx.filename << std::endl;
            return 100;
        }

        while(!ctx.file.eof())
        {
            char c = static_cast<char>(ctx.file.get());

            // zeilenzähler
            if(c == '\n')
            {
                ctx.line++;
                continue;
            }

            // ignoriere << und ->
            if( (c == '<' && (ctx.file.peek() == '<')) ||
                (c == '>' && (ctx.file.peek() == '>')) ||
                (c == '-' && (ctx.file.peek() == '>'))
               )
            {
                ctx.file.get();
                continue;
            }

            // ignoriere strings
            if( (c == '\'') ||
                (c == '"')
               )
            {
                char cstr = c;

                while(static_cast<char>(ctx.file.get()) != cstr)
                {
                    if(ctx.file.eof())
                    {
                        std::cout << "kellerat: fatal: string not closed in line " << ctx.line << std::endl;
                        return 100;
                    }
                }

                continue;
            }

            switch(c)
            {
                CREATE_STMT('{', '}', ST_CURLY_BRACKETS)
                CREATE_STMT('[', ']', ST_SQUARE_BRACKETS)
                CREATE_STMT('(', ')', ST_ROUND_PARENTHESIS)
                CREATE_STMT('<', '>', ST_CHEVRON)

                default: break;
            }

            if( (c == ';') && ( (ctx.stack[ST_ROUND_PARENTHESIS].size() % 2) || ctx.stack[ST_ROUND_PARENTHESIS].size() != 0) )
            {
                std::cout << "kellerat: fatal: semicolon before all round paranthesis are closed in line " << ctx.line << std::endl;
                return 100;
            }
        }

        CREATE_CHECK(ST_CURLY_BRACKETS)
        CREATE_CHECK(ST_ROUND_PARENTHESIS)
        CREATE_CHECK(ST_SQUARE_BRACKETS)
        CREATE_CHECK(ST_CHEVRON)
    }
    else
    {
        std::cout << "kellerat <file name>" << std::endl;
        return 100;
    }

    return 0;
}
 
Zuletzt bearbeitet:
So ich habe jetzt nochmal etwas dran gefummelt, sodass er jetzt auch Kommentare erkennt. Er merkts auch, wenn man versucht Kommentare zu schachteln... Außerdem wird jetzt nicht bei dem ersten Fehler abgebrochen, sondern stumpf weitergeparst, was bei einer offenen Klammer (() sehr witzig aussehen kann :D
Der Stringparser ist aber noch sehr kaputt aktuell... ich hör an dieser Stelle aber mal auf, das wird mir sonst irgendwie noch zu ernsthaft x)

Code:
#include <string>
#include <ios>
#include <iostream>
#include <fstream>
#include <stack>

enum stack_type
{
    ST_CURLY_BRACKETS = 0,
    ST_ROUND_PARENTHESIS = 1,
    ST_SQUARE_BRACKETS = 2,
    ST_CHEVRON = 3,
    ST_COMMENT = 4
};

#define STACK_TYPE_COUNT 5

struct stack_location
{
    // Der GCC will wohl dafür einen expliziten Konstruktor
    stack_location(stack_type t, uint32_t c)
    {
        this->type = t;
        this->line = c;
    }

    stack_type type;
    uint32_t line;
};

struct context
{
    std::ifstream file;
    std::string filename;
    std::stack<stack_location*> stack[STACK_TYPE_COUNT];
    uint32_t line;
};

std::string STtoName(stack_type t)
{
    switch(t)
    {
        case ST_CURLY_BRACKETS: return "curly braces"; break;
        case ST_ROUND_PARENTHESIS: return "round parenthesis"; break;
        case ST_SQUARE_BRACKETS: return "square brackets"; break;
        case ST_CHEVRON: return "chevrons"; break;
        case ST_COMMENT: return "comments"; break;
        default: return "unknown"; break;
    }
}

#define CREATE_CASE_STMT_PUSH(type) \
    ctx.stack[type].push( new stack_location(type, ctx.line) ); \
    break;
#define CREATE_CASE_STMT_POP(type) \
    if(ctx.stack[type].size() == 0) \
    { \
        std::cout << "kellerat: fatal: too many closing " << STtoName(type) << " in line " << ctx.line << std::endl; \
    } \
    else \
    { \
        delete ctx.stack[type].top(); \
        ctx.stack[type].pop(); \
    } \
    break;

#define CREATE_STMT(chra, chrb, type) \
    case chra: CREATE_CASE_STMT_PUSH(type) \
    case chrb: CREATE_CASE_STMT_POP(type)

#define CREATE_CHECK(type) \
    if(ctx.stack[type].size() % 2) \
    { \
        std::cout << "kellerat: fatal: not all " << STtoName(type) << " closed" << std::endl; \
    } \
    while(ctx.stack[type].size() > 0) \
    { \
        delete ctx.stack[type].top(); \
        ctx.stack[type].pop(); \
    }

#define LINEJUMP \
    ctx.line++; \
    continue;

int main(int argc, char** argv)
{
    context ctx;
    ctx.line = 1;

    if(argc == 2)
    {
        ctx.filename = argv[1];
        ctx.file.open(ctx.filename.c_str(), std::ifstream::in);

        if(!ctx.file.is_open())
        {
            std::cout << "kellerat: fatal: cannot open " << ctx.filename << std::endl;
            return 100;
        }

        char c;

        while( (!ctx.file.eof()) )
        {
            c = static_cast<char>(ctx.file.get());

            // ignoriere << und ->
            if( (c == '<' && (ctx.file.peek() == '<')) ||
                (c == '>' && (ctx.file.peek() == '>')) ||
                (c == '-' && (ctx.file.peek() == '>'))
               )
            {
                ctx.file.get();
                continue;
            }

            // kommentare überspringen
            if(c == '/' && (ctx.file.peek() == '/'))
            {
                while(static_cast<char>(ctx.file.get()) != '\n')
                {
                    // nothing to do here
                }

                LINEJUMP
            }

            // ignoriere strings
            if( (c == '\'') ||
                (c == '"')
               )
            {
                while(static_cast<char>(ctx.file.get()) != c)
                {
                    if(ctx.file.eof())
                    {
                        std::cout << "kellerat: fatal: string not closed in line " << ctx.line << std::endl;
                        break;
                    }
                }

                continue;
            }

            switch(c)
            {
                CREATE_STMT('{', '}', ST_CURLY_BRACKETS)
                CREATE_STMT('[', ']', ST_SQUARE_BRACKETS)
                CREATE_STMT('(', ')', ST_ROUND_PARENTHESIS)
                CREATE_STMT('<', '>', ST_CHEVRON)

                case '/':
                    if(ctx.file.peek() == '*')
                    {
                        ctx.file.get();

                        if(ctx.stack[ST_COMMENT].size())
                        {
                            std::cout << "kellerat: fatal: found a nested comment block in line " << ctx.line << std::endl;
                        }

                        CREATE_CASE_STMT_PUSH(ST_COMMENT)
                    }
                    break;
                case '*':
                    if(ctx.file.peek() == '/')
                    {
                        ctx.file.get();
                        CREATE_CASE_STMT_POP(ST_COMMENT)
                    }
                    break;

                case ';':
                    if((ctx.stack[ST_ROUND_PARENTHESIS].size() % 2) || ctx.stack[ST_ROUND_PARENTHESIS].size() != 0)
                    {
                        std::cout << "kellerat: fatal: semicolon before all round paranthesis are closed in line " << ctx.line << std::endl;
                    }
                    break;
                case '\n':
                    LINEJUMP
                    break; // nur für codestil... XDDDD

                default:
                    break;
            }
        }

        CREATE_CHECK(ST_CURLY_BRACKETS)
        CREATE_CHECK(ST_ROUND_PARENTHESIS)
        CREATE_CHECK(ST_SQUARE_BRACKETS)
        CREATE_CHECK(ST_CHEVRON)
        CREATE_CHECK(ST_COMMENT)
    }
    else
    {
        std::cout << "kellerat <file name>" << std::endl;
        return 100;
    }

    return 0;
}
 
Zuletzt bearbeitet:
Zurück
Oben