Blockly test

This commit is contained in:
Michael Schimmel
2025-06-10 22:31:16 +02:00
parent 0e598d595e
commit 7516bd3d9d
9 changed files with 2089 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
program BlocklyTest;
uses
System.StartUpCopy,
FMX.Forms,
MainUnit in 'MainUnit.pas' {MainForm},
LLVM.Runner in 'LLVM.Runner.pas';
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+68
View File
@@ -0,0 +1,68 @@
unit LLVM.Runner;
interface
uses
System.SysUtils, Winapi.Windows, System.Classes;
type
// Callback-Prozedur, die von der DLL aufgerufen wird
TLogIntegerCallback = procedure( AValue: Integer ); stdcall;
// Signatur der exportierten DLL-Funktion
TStarteLogikProc = procedure( ALogCallback: TLogIntegerCallback ); stdcall;
type
TLLVMRunner = class
private
FModule: HMODULE;
FStarteLogik: TStarteLogikProc;
class var
FLog: TStrings;
class procedure LogInteger( AValue: Integer ); static; stdcall;
public
constructor Create(const ADLLPath: string);
destructor Destroy; override;
procedure Execute;
class property Log: TStrings read FLog write FLog;
end;
implementation
{ TLLVMRunner }
constructor TLLVMRunner.Create(const ADLLPath: string);
begin
inherited Create;
FModule := LoadLibrary( PChar( ADLLPath ) );
if FModule = 0 then
raise Exception.Create( 'Failed to load DLL.' );
FStarteLogik := GetProcAddress( FModule, 'StarteLogik' );
if not Assigned( FStarteLogik ) then
raise Exception.Create( 'Procedure "StarteLogik" not found in DLL.' );
end;
destructor TLLVMRunner.Destroy;
begin
if FModule <> 0 then
FreeLibrary( FModule );
inherited;
end;
procedure TLLVMRunner.Execute;
begin
if Assigned( FStarteLogik ) then
// Führe die DLL-Funktion aus und übergebe unsere Callback-Methode
FStarteLogik( LogInteger );
end;
// Dies ist die Methode, die die DLL aufruft.
// Sie muss "static" (class procedure) sein, um eine feste Adresse zu haben.
class procedure TLLVMRunner.LogInteger( AValue: Integer );
begin
if Assigned( FLOg ) then
FLog.Add( Format( 'DLL logged integer: %d', [AValue] ) );
end;
end.
+68
View File
@@ -0,0 +1,68 @@
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 872
ClientWidth = 1010
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
OnDestroy = FormDestroy
DesignerMasterStyle = 0
object WebBrowser1: TWebBrowser
Align = Top
Anchors = [akLeft, akTop, akRight, akBottom]
Size.Width = 1010.000000000000000000
Size.Height = 633.000000000000000000
Size.PlatformDefault = False
WindowsEngine = EdgeOnly
OnDidFinishLoad = WebBrowser1DidFinishLoad
end
object LauchSiteButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 744.000000000000000000
TabOrder = 1
Text = 'Lauch Site'
TextSettings.Trimming = None
OnClick = LauchSiteButtonClick
end
object SaveWorkspaceButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 774.000000000000000000
Size.Width = 97.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
Text = 'Save Workspace'
TextSettings.Trimming = None
OnClick = SaveWorkspaceButtonClick
end
object GenerateCodeButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 804.000000000000000000
Size.Width = 97.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
Text = 'Generate Code'
TextSettings.Trimming = None
OnClick = GenerateCodeButtonClick
end
object Memo: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
Anchors = [akRight, akBottom]
Position.X = 144.000000000000000000
Position.Y = 641.000000000000000000
Size.Width = 841.000000000000000000
Size.Height = 223.000000000000000000
Size.PlatformDefault = False
TabOrder = 4
Viewport.Width = 837.000000000000000000
Viewport.Height = 219.000000000000000000
end
end
+314
View File
@@ -0,0 +1,314 @@
unit MainUnit;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.WebBrowser,
Winapi.WebView2, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo,
// Korrekte und vollständige Units
System.IOUtils, System.Threading, System.Diagnostics, Winapi.Windows, LLVM.Runner;
const
// Pfad zur HTML-Datei
PATH_BLOCKLY = 'T:\Myc\Blockly\blockly_LLVM.html';
// Pfade zu den LLVM-Werkzeugen
CLANG_PATH = 'clang.exe';
LLD_PATH = 'lld-link.exe';
type
TMainForm = class( TForm )
WebBrowser1: TWebBrowser;
LauchSiteButton: TButton;
SaveWorkspaceButton: TButton;
GenerateCodeButton: TButton;
Memo: TMemo;
procedure FormCreate( Sender: TObject );
procedure FormDestroy( Sender: TObject );
procedure LauchSiteButtonClick( Sender: TObject );
procedure SaveWorkspaceButtonClick( Sender: TObject );
procedure GenerateCodeButtonClick( Sender: TObject );
procedure WebBrowser1DidFinishLoad( ASender: TObject );
private
{ Private declarations }
FWebView: ICoreWebView2;
FWebMessageReceiver: ICoreWebView2WebMessageReceivedEventHandler;
FWebMessageReceivedToken: EventRegistrationToken;
procedure HandleWebMessage( const AMessage: string );
function ExecuteProcess( const ACommand: string; const AParameters: string ): string;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.fmx}
type
TWebMessageReceiver = class( TInterfacedObject, ICoreWebView2WebMessageReceivedEventHandler )
private
FOwner: TMainForm;
public
constructor Create( AOwner: TMainForm );
function Invoke( const Sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs ): HResult; stdcall;
end;
constructor TWebMessageReceiver.Create( AOwner: TMainForm );
begin
inherited Create;
FOwner := AOwner;
end;
function TWebMessageReceiver.Invoke( const Sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs ): HResult;
var
message: PWideChar;
begin
Result := args.TryGetWebMessageAsString( message );
if Succeeded( Result ) then
begin
TThread.Queue( nil,
procedure
begin
FOwner.HandleWebMessage( string( message ) );
end );
end;
Result := S_OK;
end;
procedure TMainForm.FormCreate( Sender: TObject );
begin
TLLVMRunner.Log := Memo.Lines;
end;
procedure TMainForm.FormDestroy( Sender: TObject );
begin
TLLVMRunner.Log := nil;
if ( FWebView <> nil ) and ( FWebMessageReceivedToken.value <> 0 ) then
begin
FWebView.remove_WebMessageReceived( FWebMessageReceivedToken );
end;
end;
function TMainForm.ExecuteProcess( const ACommand: string; const AParameters: string ): string;
var
sa: TSecurityAttributes;
si: TStartupInfo;
pi: TProcessInformation;
stdOutRead, stdOutWrite: THandle;
success: Boolean;
cmdLine: string;
buffer: TBytes;
bytesRead: Cardinal;
output, errors: string;
begin
// Pipe für StdOut und StdErr erstellen
sa.nLength := SizeOf( TSecurityAttributes );
sa.lpSecurityDescriptor := nil;
sa.bInheritHandle := True;
// Für stdout und stderr wird dieselbe Pipe verwendet, da clang Fehler auf stdout ausgibt
if not CreatePipe( stdOutRead, stdOutWrite, @sa, 0 ) then
Exit( 'Error creating pipe.' );
try
// StartupInfo vorbereiten
FillChar( si, SizeOf( TStartupInfo ), 0 );
si.cb := SizeOf( TStartupInfo );
si.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
si.wShowWindow := SW_HIDE; // Fenster explizit verstecken
si.hStdInput := GetStdHandle( STD_INPUT_HANDLE );
si.hStdOutput := stdOutWrite;
si.hStdError := stdOutWrite; // Leite stderr auf dieselbe Pipe wie stdout
cmdLine := Format( '"%s" %s', [ACommand, AParameters] );
success := CreateProcess(
nil,
PChar( cmdLine ),
nil,
nil,
True,
CREATE_NO_WINDOW,
nil,
nil,
si,
pi
);
// Schreib-Handle der Pipe sofort schließen
CloseHandle( stdOutWrite );
if not success then
Exit( Format( 'CreateProcess failed. Code: %d', [GetLastError] ) );
try
output := '';
// Warten, bis der Prozess fertig ist und alle Daten in die Pipe geschrieben hat
if WaitForSingleObject( pi.hProcess, 5000 ) = WAIT_TIMEOUT then // 5s Timeout
begin
TerminateProcess( pi.hProcess, 1 );
Exit( 'Process timed out.' );
end;
// Bytes aus der Pipe lesen
repeat
SetLength( buffer, 1024 );
bytesRead := 0;
if ReadFile( stdOutRead, buffer[0], Length( buffer ), bytesRead, nil ) and ( bytesRead > 0 ) then
begin
SetLength( buffer, bytesRead );
// Gelesene Bytes mit dem Default-System-Encoding in einen String umwandeln
output := output + TEncoding.Default.GetString( buffer );
end
else
begin
break; // Pipe ist leer oder Fehler
end;
until False;
finally
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
end;
Result := output;
finally
CloseHandle( stdOutRead );
end;
end;
procedure TMainForm.LauchSiteButtonClick( Sender: TObject );
begin
WebBrowser1.Navigate( PATH_BLOCKLY );
end;
procedure TMainForm.SaveWorkspaceButtonClick( Sender: TObject );
begin
WebBrowser1.EvaluateJavaScript( 'saveWorkspace()' );
end;
procedure TMainForm.GenerateCodeButtonClick( Sender: TObject );
begin
WebBrowser1.EvaluateJavaScript( 'generateAndPostCode()' );
end;
procedure TMainForm.HandleWebMessage( const AMessage: string );
begin
Memo.Lines.Clear;
Memo.Lines.Add( 'LLVM-Code empfangen. Starte Kompilierung...' );
Memo.Lines.Add( AMessage );
Memo.Lines.Add( '--------------------' );
TTask.Run(
procedure
var
llFile, objFile, dllFile: string;
compilerOutput: string;
success: Boolean;
begin
llFile := TPath.Combine( TPath.GetTempPath, 'poc.ll' );
objFile := TPath.ChangeExtension( llFile, '.obj' );
dllFile := TPath.ChangeExtension( llFile, '.dll' );
success := False;
try
TFile.WriteAllText( llFile, AMessage );
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( 'Kompiliere zu Objektdatei...' );
end );
compilerOutput := ExecuteProcess( CLANG_PATH, Format( '-c "%s" -o "%s"', [llFile, objFile] ) );
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( compilerOutput );
end );
if not TFile.Exists( objFile ) then
begin
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( 'FEHLER: Kompilierung fehlgeschlagen. .obj-Datei nicht erstellt.' );
end );
Exit;
end;
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( 'Linke zu DLL...' );
end );
compilerOutput := ExecuteProcess( LLD_PATH, Format( '/dll /noentry "%s" /out:"%s"', [objFile, dllFile] ) );
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( compilerOutput );
end );
if TFile.Exists( dllFile ) then
begin
success := True;
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( Format( 'ERFOLG: DLL wurde erstellt: %s', [dllFile] ) );
end );
end
else
begin
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( 'FEHLER: Linken fehlgeschlagen. .dll-Datei nicht erstellt.' );
end );
end;
finally
if TFile.Exists( llFile ) then
TFile.Delete( llFile );
if TFile.Exists( objFile ) then
TFile.Delete( objFile );
if not success and TFile.Exists( dllFile ) then
TFile.Delete( dllFile );
end;
Memo.Lines.Add( '---------------------------------------------------------' );
Memo.Lines.Add( 'DLL wird aufgerufen:' );
if TFile.Exists( dllFile ) then
begin
var
Runner := TLLVMRunner.Create( dllFile );
try
Runner.Execute;
finally
Runner.Free;
end;
end;
end );
Memo.Lines.Add( 'DLL beendet.' );
end;
procedure TMainForm.WebBrowser1DidFinishLoad( ASender: TObject );
begin
if Assigned( FWebMessageReceiver ) then
Exit;
if Supports( WebBrowser1, ICoreWebView2, FWebView ) then
begin
FWebMessageReceiver := TWebMessageReceiver.Create( Self );
FWebView.add_WebMessageReceived( FWebMessageReceiver, FWebMessageReceivedToken );
end;
end;
end.
+74
View File
@@ -0,0 +1,74 @@
Technische Dokumentation: Integration von Blockly in eine Delphi FireMonkey Anwendung via WebView2
Datum: 10. Juni 2025
Autor: Gemini & ein sehr fähiger Delphi-Entwickler
1. Zielsetzung
Ziel dieser Architektur ist die Erstellung einer Hybrid-Anwendung. Eine in Delphi (FireMonkey) entwickelte, native Desktop-Anwendung hostet eine webbasierte, visuelle Programmierumgebung (Blockly). Dies ermöglicht es Endanwendern, komplexe Logik visuell zu erstellen, während die Hauptanwendung die generierten Ergebnisse (z.B. Quellcode in einer Zielsprache) entgegennehmen und weiterverarbeiten kann.
Diese Architektur ist ideal für die Erstellung von spezialisierten Entwicklungsumgebungen, Konfigurations-Tools oder Lernanwendungen.
2. Kernkomponenten
Native Host-Anwendung: Eine Delphi FireMonkey (FMX) Anwendung, erstellt in RAD Studio (getestet mit Version 12). Dient als Hauptcontainer und steuert die native UI.
Browser-Komponente: Die FMX TWebBrowser-Komponente. Entscheidend ist die Konfiguration der Engine-Eigenschaft, um auf Windows die moderne Microsoft Edge WebView2-Engine zu nutzen.
Browser-Laufzeitumgebung: Die Microsoft Edge WebView2 Runtime. Diese muss auf dem Zielsystem des Endanwenders installiert sein.
Web-Anwendung (Frontend): Eine lokale, in sich geschlossene HTML-Datei (index.html), die die Blockly-Bibliothek lädt und den visuellen Editor initialisiert.
Benutzerdefinierter Generator: Ein in JavaScript geschriebener, benutzerdefinierter Blockly-Generator (in unserem Fall Blockly.Delphi), der die visuellen Blöcke in eine textbasierte Zielsprache übersetzt.
3. Architektur-Übersicht
Die Delphi-Anwendung fungiert als nativer Container. Beim Start lädt die TWebBrowser-Komponente die lokale index.html-Datei. Diese Datei enthält die gesamte Logik für den Blockly-Editor und den Code-Generator. Die Kommunikation zwischen der Delphi-Anwendung (Host) und der JavaScript-Anwendung (Gast) erfolgt über eine spezielle Schnittstelle, die von der WebView2-Engine bereitgestellt wird.
+-------------------------------------------------+
| Delphi FireMonkey Anwendung (MainForm.pas) |
| +---------------------------------------------+ |
| | TWebBrowser (Engine: Edge WebView2) | |
| | +-----------------------------------------+ | |
| | | Lokale index.html geladen | | |
| | | +-----------------+ +---------------+ | | |
| | | | Blockly | | Generator | | | |
| | | | Arbeitsbereich | | (JavaScript) | | | |
| | | +-----------------+ +---------------+ | | |
| | +-----------------------------------------+ | |
| +---------------------------------------------+ |
| ^ | |
| | Delphi -> JS (EvaluateJavaScript) |
| | JS -> Delphi (OnWebMessageReceived) |
| v | |
| +---------------------------------------------+ |
| | Native UI-Elemente (Buttons, TMemo etc.) | |
| +---------------------------------------------+ |
+-------------------------------------------------+
4. Implementierungsschritte
4.1. Die Web-Anwendung (index.html)
Dies ist das Herzstück des Editors. Die Datei enthält die Blockly-Bibliotheken (von einem CDN oder lokal), den benutzerdefinierten Generator und den Initialisierungscode.
Wichtige JavaScript-Funktionen in der index.html:
Blockly.Delphi = new Blockly.Generator(...): Erstellt die Instanz unseres benutzerdefinierten Generators.
Blockly.Delphi.forBlock['block_type'] = function(...): Definiert die Übersetzungsregel für einen spezifischen Block-Typ.
Blockly.Delphi.workspaceToCode(...): Die Hauptfunktion, die den gesamten Arbeitsbereich in die Zielsprache übersetzt. In unserem Fall wurde diese überschrieben, um die Delphi-Unit-Struktur (Header, var-Sektion etc.) zu erzeugen.
window.saveWorkspace() & window.loadWorkspace(): Nutzen Blockly.serialization.workspaces und localStorage, um den Zustand des Editors zu speichern und wiederherzustellen.
window.generateAndPostCode(): Die Schlüsselfunktion für die Kommunikation. Sie generiert den Code und sendet ihn mittels window.chrome.webview.postMessage(code) an die Delphi-Host-Anwendung.
(Der vollständige, funktionierende Code für die index.html ist der aus unserer letzten erfolgreichen Iteration.)
4.2. Die Delphi Host-Anwendung (FireMonkey)
Die FMX-Form enthält die TWebBrowser-Komponente und die nativen Steuerelemente.
Wichtige Konfiguration und Code-Teile in der Delphi-Unit:
Komponenten: Eine TWebBrowser (WebBrowser1), ein TMemo (Memo1) und mehrere TButton.
WebBrowser1.Engine Eigenschaft: Muss im Objektinspektor oder per Code auf EdgeIfAvailable gesetzt werden.
FormCreate: Lädt die lokale index.html-Datei mit WebBrowser1.Navigate('pfad/zur/index.html').
WebBrowser1DidFinishLoad: In diesem Ereignis wird der Nachrichten-Empfänger (WebMessageReceived) registriert. Dies stellt sicher, dass die Webseite vollständig geladen ist, bevor die Kommunikation eingerichtet wird. Die Registrierung erfolgt über die ICoreWebView2-Schnittstelle.
TWebMessageReceiver-Klasse: Eine Hilfsklasse, die das ICoreWebView2WebMessageReceivedEventHandler-Interface implementiert. Ihre Invoke-Methode wird aufgerufen, wenn eine Nachricht von JavaScript eintrifft.
TThread.Queue: Wichtig innerhalb der Invoke-Methode, um die empfangene Nachricht sicher an den Haupt-GUI-Thread zu übergeben und UI-Komponenten (wie das TMemo) zu aktualisieren.
Button.OnClick-Ereignisse: Rufen WebBrowser1.EvaluateJavaScript('funktionsname()') auf, um JavaScript-Funktionen im WebView auszulösen.
(Der vollständige, funktionierende Code für die Delphi-Unit ist der, den Sie zuletzt bereitgestellt haben.)
5. Deployment und Abhängigkeiten
Um die Anwendung an einen Endnutzer weiterzugeben, müssen folgende Komponenten verteilt werden:
Ihre kompilierte .exe-Datei.
Ein Unterordner (z.B. web), der die index.html und alle zugehörigen JavaScript-Dateien enthält (falls sie nicht von einem CDN geladen werden).
Der Installer muss prüfen, ob die Microsoft Edge WebView2 Runtime vorhanden ist, und sie bei Bedarf herunterladen und installieren. Microsoft stellt dafür einen kleinen "Bootstrapper"-Installer bereit.
6. Fazit
Diese Hybrid-Architektur ist extrem leistungsfähig. Sie kombiniert die universelle Einsetzbarkeit und Flexibilität moderner Web-Technologien (HTML/JS/Blockly) für die Benutzeroberfläche des Editors mit der Stärke und Geschwindigkeit einer nativen Delphi-Anwendung für die Programmlogik, Dateiverarbeitung und die Integration in das Betriebssystem. Der TWebBrowser im Edge-Modus ist die entscheidende Brückentechnologie, die diesen Ansatz in FireMonkey modern und zukunftssicher macht.
+163
View File
@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly zu Delphi Unit - Final</title>
<style>
body { font-family: sans-serif; }
#container { display: flex; }
#outputArea { margin-left: 20px; }
#codeOutput { width: 400px; height: 580px; font-family: 'Courier New', Courier, monospace; font-size: 14px; white-space: pre-wrap; border: 1px solid #ccc; }
#blocklyDiv { height: 600px; width: 800px; }
.button-bar { margin-bottom: 10px; }
</style>
</head>
<body>
<h1>Mein Delphi-Unit-Generator</h1>
<p>Bauen Sie links Ihre Blöcke. Rechts erscheint die kompilierbare Delphi-Unit.</p>
<div class="button-bar">
<button onclick="saveWorkspace()">Speichern</button>
<button onclick="loadWorkspace()">Letzten Stand laden</button>
<button onclick="generateAndPostCode()">Code generieren und posten</button>
</div>
<div id="container">
<div id="blocklyDiv"></div>
<div id="outputArea">
<h2>Erzeugter Delphi-Code:</h2>
<textarea id="codeOutput" readonly></textarea>
</div>
</div>
<script src="https://unpkg.com/blockly/blockly_compressed.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<script>
window.onload = function() {
let workspace;
// === DER DELPHI-GENERATOR ===
Blockly.Delphi = new Blockly.Generator('Delphi');
Blockly.Delphi.ORDER_ATOMIC = 0;
Blockly.Delphi.ORDER_NONE = 99;
Blockly.Delphi.workspaceToCode = function(workspace) {
const allVariables = workspace.getAllVariables();
let varDeclarations = '';
if (allVariables.length > 0) {
const varNames = allVariables.map(v => v.name).join(', ');
varDeclarations = ` var\n ${varNames}: Integer;\n`;
}
varDeclarations += ' i: Integer; // Schleifenzähler\n';
const topBlocks = workspace.getTopBlocks(true);
const code = topBlocks.map(block => Blockly.Delphi.blockToCode(block)).join(';\n');
let unitCode = 'unit MeinProgramm;\n\n';
unitCode += 'interface\n\n';
unitCode += 'uses\n System.SysUtils;\n\n';
unitCode += 'procedure StarteLogik;\n\n';
unitCode += 'implementation\n\n';
unitCode += 'procedure StarteLogik;\n';
if (varDeclarations) unitCode += varDeclarations;
unitCode += 'begin\n';
unitCode += ' ' + code.split('\n').join('\n ');
unitCode += '\nend;\n\n';
unitCode += 'end.';
return unitCode;
};
Blockly.Delphi.scrub_ = function(block, code, opt_thisOnly) {
const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
const nextCode = opt_thisOnly ? '' : Blockly.Delphi.blockToCode(nextBlock);
return code + (nextBlock ? ';\n' : '') + nextCode;
};
Blockly.Delphi.forBlock['controls_if'] = function(block) {
let conditionCode = Blockly.Delphi.valueToCode(block, 'IF0', Blockly.Delphi.ORDER_NONE) || 'False';
let branchCode = Blockly.Delphi.statementToCode(block, 'DO0') || '';
let code = `if (${conditionCode}) then\nbegin\n${Blockly.Delphi.prefixLines(branchCode, ' ')}\nend`;
return code;
};
Blockly.Delphi.forBlock['logic_compare'] = function(block) {
const OPERATORS = {'EQ': '=', 'NEQ': '<>', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='};
const operator = OPERATORS[block.getFieldValue('OP')];
const value_a = Blockly.Delphi.valueToCode(block, 'A', Blockly.Delphi.ORDER_ATOMIC) || '0';
const value_b = Blockly.Delphi.valueToCode(block, 'B', Blockly.Delphi.ORDER_ATOMIC) || '0';
return [`${value_a} ${operator} ${value_b}`, Blockly.Delphi.ORDER_ATOMIC];
};
Blockly.Delphi.forBlock['controls_repeat_ext'] = function(block) {
const repeats = Blockly.Delphi.valueToCode(block, 'TIMES', Blockly.Delphi.ORDER_ATOMIC) || '0';
const branch = Blockly.Delphi.statementToCode(block, 'DO') || '';
let code = `for i := 1 to ${repeats} do\nbegin\n${Blockly.Delphi.prefixLines(branch, ' ')}\nend`;
return code;
};
Blockly.Delphi.forBlock['math_number'] = function(block) { return [String(block.getFieldValue('NUM')), Blockly.Delphi.ORDER_ATOMIC]; };
Blockly.Delphi.forBlock['variables_get'] = function(block) { const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name; return [varName, Blockly.Delphi.ORDER_ATOMIC]; };
Blockly.Delphi.forBlock['variables_set'] = function(block) { const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name; const value = Blockly.Delphi.valueToCode(block, 'VALUE', Blockly.Delphi.ORDER_ATOMIC) || '0'; return `${varName} := ${value}`;};
Blockly.Delphi.forBlock['math_change'] = function(block) { const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name; const delta = Blockly.Delphi.valueToCode(block, 'DELTA', Blockly.Delphi.ORDER_ATOMIC) || '0'; if (delta == '1') return `Inc(${varName})`; if (delta == '-1') return `Dec(${varName})`; return `${varName} := ${varName} + ${delta}`; };
Blockly.Delphi.forBlock['text_print'] = function(block) { const value = Blockly.Delphi.valueToCode(block, 'TEXT', Blockly.Delphi.ORDER_ATOMIC) || `''`; return `WriteLn(${value})`; };
Blockly.Delphi.forBlock['text'] = function(block) { const text = block.getFieldValue('TEXT').replace(/'/g, "''"); return [`'${text}'`, Blockly.Delphi.ORDER_ATOMIC]; };
// === INITIALISIERUNG VON BLOCKLY (JETZT KORREKT) ===
// 1. Definition der Werkzeugkiste (Toolbox)
const toolboxXml = `
<xml>
<category name="Logik" colour="%{BKY_LOGIC_HUE}">
<block type="controls_if"></block>
<block type="logic_compare"></block>
</category>
<category name="Schleifen" colour="%{BKY_LOOPS_HUE}">
<block type="controls_repeat_ext">
<value name="TIMES"><shadow type="math_number"><field name="NUM">10</field></shadow></value>
</block>
</category>
<category name="Text" colour="%{BKY_TEXTS_HUE}">
<block type="text_print"></block>
<block type="text"></block>
</category>
<category name="Mathematik" colour="%{BKY_MATH_HUE}">
<block type="math_number"></block>
<block type="math_change">
<value name="DELTA"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
</block>
</category>
<category name="Variablen" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
</xml>`;
// 2. Blockly in den Div-Container "injizieren"
workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml
});
function generateAndPostCode() {
const code = Blockly.Delphi.workspaceToCode(workspace);
// Diese spezielle Funktion sendet eine Nachricht an den Delphi-Host
window.chrome.webview.postMessage(code);
}
window.generateAndPostCode = function() { generateAndPostCode(); }
// === Funktionen für Speichern und Laden (unverändert) ===
window.saveWorkspace = function() { const state = Blockly.serialization.workspaces.save(workspace); localStorage.setItem('blocklyWorkspace', JSON.stringify(state)); alert('Arbeitsbereich gespeichert!'); }
window.loadWorkspace = function() { const stateString = localStorage.getItem('blocklyWorkspace'); if (!stateString) { return; } const state = JSON.parse(stateString); Blockly.serialization.workspaces.load(state, workspace); }
function updateCode(event) { if (event.type == Blockly.Events.UI) return; const code = Blockly.Delphi.workspaceToCode(workspace); document.getElementById('codeOutput').value = code; }
workspace.addChangeListener(updateCode);
loadWorkspace();
};
</script>
</body>
</html>
+192
View File
@@ -0,0 +1,192 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly zu LLVM IR - Final</title>
<style>
body { font-family: sans-serif; }
#container { display: flex; }
#outputArea { margin-left: 20px; }
#codeOutput { width: 500px; height: 580px; font-family: 'Courier New', Courier, monospace; font-size: 14px; white-space: pre-wrap; border: 1px solid #ccc; }
#blocklyDiv { height: 600px; width: 800px; }
.button-bar { margin-bottom: 10px; }
</style>
</head>
<body>
<h1>LLVM IR (.dll) Generator</h1>
<p>Bauen Sie links Ihre Logik. Rechts erscheint der LLVM-Code, der zu einer 64-Bit DLL für Windows kompiliert werden kann.</p>
<div class="button-bar">
<button onclick="saveWorkspace()">Speichern</button>
<button onclick="loadWorkspace()">Letzten Stand laden</button>
<button onclick="generateAndPostCode()">Code generieren und posten</button>
</div>
<div id="container">
<div id="blocklyDiv"></div>
<div id="outputArea">
<h2>Erzeugter LLVM-Code:</h2>
<textarea id="codeOutput" readonly></textarea>
</div>
</div>
<script src="https://unpkg.com/blockly/blockly_compressed.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<script>
window.onload = function() {
let workspace;
// === LLVM-IR-GENERATOR (FINALE VERSION) ===
Blockly.LLVM = new Blockly.Generator('LLVM');
Blockly.LLVM.ORDER_ATOMIC = 0;
Blockly.LLVM.ORDER_NONE = 99;
// --- Block-Definitionen für die Toolbox ---
Blockly.defineBlocksWithJsonArray([
{
"type": "host_log_integer",
"message0": "Logge Integer %1",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": 290,
"tooltip": "Sendet einen Integer-Wert an den Delphi-Host.",
"helpUrl": ""
}
]);
// --- Generator-Implementierungen ---
Blockly.LLVM.regCounter = 0;
Blockly.LLVM.newReg = function() {
return '%' + (Blockly.LLVM.regCounter++);
};
Blockly.LLVM.workspaceToCode = function(workspace) {
Blockly.LLVM.regCounter = 1;
const allVariables = workspace.getAllVariables();
let varAllocations = '';
if (allVariables.length > 0) {
allVariables.forEach(v => {
const safeVarName = v.name.replace(/ /g, '_');
varAllocations += ` @${safeVarName} = common global i32 0\n`;
});
}
const topBlock = workspace.getTopBlocks(true)[0];
const code = topBlock ? Blockly.LLVM.blockToCode(topBlock) : '';
let moduleCode = `target triple = "x86_64-pc-win32"\n\n`;
moduleCode += `@gLogIntProc = common global void (i32)* null\n\n`;
if (varAllocations) {
moduleCode += '; Global Variables\n';
moduleCode += varAllocations + '\n';
}
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc) {\n';
moduleCode += 'entry:\n';
moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n\n';
moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n');
moduleCode += '\n ret void\n';
moduleCode += '}\n';
return moduleCode;
};
Blockly.LLVM.scrub_ = function(block, code) {
const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
const nextCode = nextBlock ? Blockly.LLVM.blockToCode(nextBlock) : '';
return code + nextCode;
};
Blockly.LLVM.forBlock['variables_set'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const value = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '0';
return `store i32 ${value}, i32* @${varName}\n`;
};
Blockly.LLVM.forBlock['math_change'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const delta = Blockly.LLVM.valueToCode(block, 'DELTA', Blockly.LLVM.ORDER_ATOMIC) || '0';
const loadReg = Blockly.LLVM.newReg();
const addReg = Blockly.LLVM.newReg();
let code = '';
code += `${loadReg} = load i32, i32* @${varName}\n`;
code += `${addReg} = add nsw i32 ${loadReg}, ${delta}\n`;
code += `store i32 ${addReg}, i32* @${varName}\n`;
return code;
};
Blockly.LLVM.forBlock['variables_get'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
return ['@' + varName, Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['math_number'] = function(block) {
return [String(block.getFieldValue('NUM')), Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['host_log_integer'] = function(block) {
const varName = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '@unknown';
const valueReg = Blockly.LLVM.newReg();
const logPtrReg = Blockly.LLVM.newReg();
let code = '';
code += `${valueReg} = load i32, i32* ${varName}\n`;
code += `${logPtrReg} = load void (i32)*, void (i32)** @gLogIntProc\n`;
code += `call void ${logPtrReg}(i32 ${valueReg})\n`;
return code;
};
// === INITIALISIERUNG VON BLOCKLY ===
const toolboxXml = `
<xml>
<category name="Variablen" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
<category name="Mathematik" colour="%{BKY_MATH_HUE}">
<block type="math_number"></block>
<block type="math_change">
<value name="DELTA"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
</block>
</category>
<category name="Host" colour="290">
<block type="host_log_integer"></block>
</category>
</xml>`;
workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml
});
function generateAndPostCode() {
const code = Blockly.LLVM.workspaceToCode(workspace);
if (window.chrome && window.chrome.webview) {
window.chrome.webview.postMessage(code);
} else {
console.log("PostMessage an Host nicht verfügbar. Code in Konsole ausgegeben:");
console.log(code);
}
}
window.generateAndPostCode = generateAndPostCode;
window.saveWorkspace = function() { const state = Blockly.serialization.workspaces.save(workspace); localStorage.setItem('blocklyWorkspace', JSON.stringify(state)); alert('Arbeitsbereich gespeichert!'); }
window.loadWorkspace = function() { const stateString = localStorage.getItem('blocklyWorkspace'); if (!stateString) { return; } const state = JSON.parse(stateString); Blockly.serialization.workspaces.load(state, workspace); }
function updateCode(event) { if (event.isUiEvent) return; const code = Blockly.LLVM.workspaceToCode(workspace); document.getElementById('codeOutput').value = code; }
workspace.addChangeListener(updateCode);
loadWorkspace();
};
</script>
</body>
</html>