Autor Beitrag
delfiphan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Do 06.07.06 22:44 
Delphi Runtime-Compiler-Compiler (zur Laufzeit erstellter Compiler)
Hab mir mal die Mühe gemacht ein Delphi-Runtime-Compiler-Compiler-Framework zu programmieren. Damit sollte es möglich sein, viele verschiedene Grammatiken damit zu definieren und verwenden, die ohne Backtracking (eindeutig mit LR(1) lösbar, ohne Linksrekursionen) geparst werden können. Ich habe das eigentlich aus eigenem Interesse gebaut, ob sowas jemand direkt so brauchen kann ist eine andere Frage. Der Source wurde unter Delphi 2006 erstellt.

Das ganze besteht im Wesentlichen aus 2 Units:
RcCompiler
RcCompiler ist eine Implementierung von EBNF. Sie ermöglicht die Definition einer formalen Sprache. Das ganze ist noch auf einer relativ abstrakten Ebene weswegen die Unit auch nicht sehr viel macht.

RcEBNF
Da die Definition einer formalen Sprache mittels RcCompiler etwas umständlich ist ermöglicht diese Unit die Spezifikation einer Grammtik durch Eingabe eines Strings in EBNF-Syntax. RcEBNF verwendet selbst RcCompiler um den Eingabestring in eine Sprachdefinition für RcCompiler zu parsen und zusammenzustellen.

Allgemeines
Natürlich ist die Implementierung einer Sprache ein grosser Aufwand. Die Definition selbst ist zwar schnell gemacht, man muss natürlich aber noch angeben, was die einzelnen Bausteine machen. Man muss also noch Handlers programmieren (was die machen ist vollig frei: Bytecode erzeugen, direkt interpretieren, in eine andere Sprache übersetzen).

Beispiel der Demo
In der Demo kann man dann in einer zur Laufzeit definierten (extrem einfachen) Programmiersprache programmieren. Man kann dort nur Ausdrücke auswerten und diese einer Variablen zuweisen. Die Sprache wird zur Laufzeit über folgende Strings definiert:
ausblenden Definition der Demosprache
1:
2:
3:
4:
5:
6:
7:
befehl = "begin" [ befehl { ";" [ befehl ] } ] "end" | zuweisung.
zuweisung = bezeichner ":=" bool.
bool = summe [ ( "=" | "<>" | ">" | "<" | "=>" | "<=" ) summe ].
summe = produkt { ( "+"|"-") produkt }.
produkt = term { ("*"|"") term }.
term = termmitvorz | "(" bool ")" | bezeichner | zahl.
termmitvorz = ( "+"|"-" ) term.

(Der Tokenizer liefert die Terminal Symbols bezeichner und zahl als Tokens. Diese sind über Regular Expressions definiert)

Zu jeder Zeile muss man im Framework noch einen Handler angeben, der eine konkrete Zeile verarbeitet. Z.B. für "Summe" einen Interpreter-Handler, der mehrere Zahlen entgegennimmt, diese addiert und die Summe zurückgibt. Oder ein Bytecode-Handler würde z.B. den entsprechenden Code zusammenstellen und diesen an die nächste Stufe weitergeben. Pro Zeile kann man noch einige Optionen einstellen wie z.B. Optimierung und andere.

Ein gültiges Programm wäre nun z.B.:
ausblenden Testprogramm
1:
2:
3:
4:
5:
begin
 sieben := 1+2*3;
 result := 1+(-9+sieben)*(sieben+3);
 test := result < -10;
end


Hier noch vollständigkeitshalber die Definition von EBNF in EBNF (implementiert in RcEBNF):
ausblenden Definition von EBNF in EBNF
1:
2:
3:
4:
5:
Factor = Identifier | String | "(" Expression ")" | "[" Expression "]" | "{" Expression "}".
Term = Factor { Factor }.
Expression = Term { "|" Term }.
Statement = Identifier "=" Expression ".".
Syntax = { Statement }.


Todo:
Lookahead Spezifikationen; Syntax Fehlermeldungen, Optimierung.

[meta]parser,parsing,lexer,typarser,compiler[/meta]

NEUE VERSION: Sources | [url=www.tyberis.com/down...cDemo.exe]Demo[/url]
Einloggen, um Attachments anzusehen!


Zuletzt bearbeitet von delfiphan am So 13.01.08 13:17, insgesamt 8-mal bearbeitet
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: So 09.07.06 17:27 
Kleines Update:
- RegExpr-Tokenizer eingabaut und Code etwas aufgeräumt. Der Code ist jetzt LGPL.
- Benützt keine Interfaces mehr (effizienter)
- Der Optimizer kürzt Stufen weg, wenn man angibt, dass man auf die Handlers verzichten kann, falls der Parsebaum dort keine Verzweigung hat (d.h. wenn der Handler sowieso nur die Eingabe in die Ausgabe kopiert, wenn nur ein Argument vorhanden ist, dann sollte man Optimizeable auf True setzen. Das ist recht häufig der Fall).

- Die Art des Parsings entspricht der von Javacc; mit den gleichen Einschränkungen.
raziel
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 2453

Arch Linux
JS (WebStorm), C#, C++/CLI, C++ (VS2013)
BeitragVerfasst: Mo 17.07.06 21:15 
Hallo,

sehr schöne Units, genau zur richtigen Zeit (schreib am Dienstag Klausur "Formale Sprachen & Compilerbau" ;)). Wollte deshalb grade mal ein bisschen rumspielen (die ganze Definition deiner Beispielgrammatik geschieht ja im FormCreate, nicht?), beim Compilieren bekam ich allerdings ein paar Fehler entgegengeworfen:

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
[Fehler] RegExpr.pas(2883): Undefinierter Bezeichner: 'ExecPrim'
[Fehler] RegExpr.pas(2889): Undefinierter Bezeichner: 'ExecPrim'
[Fehler] RegExpr.pas(3037): Undefinierter Bezeichner: 'Replace'
[Fehler] RegExpr.pas(3041): Undefinierter Bezeichner: 'ReplaceEx'
[Fehler] RegExpr.pas(3041): Undefinierter Bezeichner: 'AInputStr'
[Fehler] RegExpr.pas(3041): Undefinierter Bezeichner: 'AReplaceFunc'


Brauch ich evtl noch zusätzlich die TRegExpr-Unit von Sorokin? Oder woran liegt das? Benutze Delphi 7 zum ausprobieren.

Gruß,
raziel

_________________
JSXGraph
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Fr 21.07.06 23:46 
Ja das ist sehr interessant... vielleicht müsste man einige Compiler-Switches richtig stellen, dammit's klappt... Ich schaue mir das bei Gelegenheit an.

Edit: Kannst einfach alles zwischen "interface" und "uses" in der Datei RegExpr.pas entfernen, dann sollte es gehn.
raziel
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 2453

Arch Linux
JS (WebStorm), C#, C++/CLI, C++ (VS2013)
BeitragVerfasst: Sa 22.07.06 07:57 
Danke, jetzt funktionierts :zustimm:

_________________
JSXGraph
Roaster
Hält's aus hier
Beiträge: 2

WinXP Pro, WinXP Home, Win98 SE
D4 C/S, D7 Enterprise
BeitragVerfasst: Fr 04.08.06 14:02 
Hi,

kann man mit diesem Compiler auch Funktionen einbauen, wie bspw. "test := copy(s1,1,1);"?

Gibt es generell eine Anleitung dafür, wie man das Coding um eine solche Funktion erweitern kann? Kann man auch sowas wie Variablen aus dem eigenen Programm mit übergeben, also bspw. %EMailAdresse%?

Ich habe mal mit diesem hier angefangen:
registerFunc(MyScript.Copy, 'MyScript.Copy');

In der Datei DemoScript dann noch folgendes ergänzt (unvollständig, da nur Test):
function TScript.Copy(Origin: TRcEntity; CResult: array of TRcResult): TRcResult;

aber das war's dann schon. Der Compiler beschwert sich mit einem Syntax Error. Wird eigentlich eine Zeile und eine Position zurückgegeben wo dieser Fehler aufgetreten ist, damit man dem Enduser dies auch mitteilen kann?

Gibt es generell eine kleine Anleitung für den prima Compiler?

_________________
cu,
Roaster
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Sa 12.08.06 02:15 
Das ist natürlich möglich. Du müsstest dann aber nicht Copy selbst definieren sondern den Syntax für Funktionsaufrufe angeben.

Definieren könnte man das z.B. so:

Funktionsaufruf = Bezeichner "(" [ Argument { "," Argument } ] ")".

Das heisst ein Funktionsaufruf besteht aus dem Funktionsnamen gefolgt von der Argumentliste (0 oder mehr Argumente) in Klammern. Danach musst du dann noch einen Handler definieren - der kriegt den Funktionsnamen und die Argumente, sowie die Anzahl Argumente vom Framework geliefert. Der Handler müsste aus diesen Informationen dann die geeignete Funktion raussuchen und aufrufen bzw. eine Exception auslösen. Oder wenn du einen Bytecode Compiler willst müsstest du den entsprechenden Call in den Bytecode hinzufügen.

Vielleicht implementier ich das am Wochenende mal und poste es.
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Mi 13.09.06 17:17 
Hier noch das Update mit den Funktionen. Funktionen werden in DemoScript.pas/TScript.DoFunction aufgerufen. Dort sind auch die zwei Funktionen min und max implementiert. Die Funktionen erwarten 2 oder mehr Zahlen als Argumente.
Einloggen, um Attachments anzusehen!