Blockly review

This commit is contained in:
Michael Schimmel
2025-06-11 01:33:52 +02:00
parent b8a530254d
commit 3cfca1f167
4 changed files with 248 additions and 242 deletions
+34 -10
View File
@@ -9,8 +9,16 @@ type
// Callback-Prozedur, die von der DLL aufgerufen wird // Callback-Prozedur, die von der DLL aufgerufen wird
TLogIntegerCallback = procedure( AValue: Integer ); stdcall; TLogIntegerCallback = procedure( AValue: Integer ); stdcall;
// Signatur der exportierten DLL-Funktion // NEU: Typ für die Host-Additionsfunktion
TStarteLogikProc = procedure( ALogCallback: TLogIntegerCallback ); stdcall; THostAddIntegersCallback = function( ANum1: Integer; ANum2: Integer ): Integer; stdcall;
// NEUE, VEREINFACHTE Signatur der exportierten DLL-Funktion
// Jetzt nur noch der Log-Callback und der neue Integer-Input-Parameter
TStarteLogikProc = procedure(
ALogIntProc: TLogIntegerCallback;
AHostInputInteger: Integer;
AHostAddIntegersProc: THostAddIntegersCallback // <--- Der neue Host-Unterprogramm-Parameter
); stdcall;
type type
TLLVMRunner = class TLLVMRunner = class
@@ -20,10 +28,12 @@ type
class var class var
FLog: TStrings; FLog: TStrings;
class procedure LogInteger( AValue: Integer ); static; stdcall; class procedure LogInteger( AValue: Integer ); static; stdcall;
// NEU: Host-Unterprogramm-Implementierung
class function HostAddIntegers( ANum1: Integer; ANum2: Integer ): Integer; static; stdcall;
public public
constructor Create(const ADLLPath: string); constructor Create(const ADLLPath: string);
destructor Destroy; override; destructor Destroy; override;
procedure Execute; procedure Execute( AInputInteger: Integer );
class property Log: TStrings read FLog write FLog; class property Log: TStrings read FLog write FLog;
end; end;
@@ -50,19 +60,33 @@ begin
inherited; inherited;
end; end;
procedure TLLVMRunner.Execute; // NEUE Implementierung der Host-Funktion
class function TLLVMRunner.HostAddIntegers( ANum1: Integer; ANum2: Integer ): Integer;
begin begin
if Assigned( FStarteLogik ) then Result := ANum1 + ANum2; // Die eigentliche Logik
// Führe die DLL-Funktion aus und übergebe unsere Callback-Methode if Assigned(FLog) then
FStarteLogik( LogInteger ); FLog.Add(Format('DLL hat Host-Addition angefordert: %d + %d = %d', [ANum1, ANum2, Result]));
end; end;
// Dies ist die Methode, die die DLL aufruft. // Die Execute-Methode muss angepasst werden, um den neuen Funktionszeiger zu übergeben
// Sie muss "static" (class procedure) sein, um eine feste Adresse zu haben. procedure TLLVMRunner.Execute( AInputInteger: Integer );
begin
if Assigned( FStarteLogik ) then
FStarteLogik(
LogInteger,
AInputInteger,
HostAddIntegers // <--- Adresse der Host-Funktion übergeben
);
end;
// Dies ist die Methode, die die DLL für LogInteger aufruft.
class procedure TLLVMRunner.LogInteger( AValue: Integer ); class procedure TLLVMRunner.LogInteger( AValue: Integer );
begin begin
if Assigned( FLOg ) then if Assigned( FLog ) then
FLog.Add( Format( 'DLL logged integer: %d', [AValue] ) ); FLog.Add( Format( 'DLL logged integer: %d', [AValue] ) );
end; end;
// Die Implementierungen von GetCurrentDateTimeUTCHost und GetComponentInZoneHost
// wurden hier entfernt, da sie nicht mehr benötigt werden.
end. end.
+14 -1
View File
@@ -30,7 +30,7 @@ object MainForm: TMainForm
Size.PlatformDefault = False Size.PlatformDefault = False
Text = 'Refresh' Text = 'Refresh'
TextSettings.Trimming = None TextSettings.Trimming = None
OnClick = LauchSiteButtonClick OnClick = RefreshButtonClick
end end
object SaveWorkspaceButton: TSpeedButton object SaveWorkspaceButton: TSpeedButton
Align = Left Align = Left
@@ -52,6 +52,19 @@ object MainForm: TMainForm
TextSettings.Trimming = None TextSettings.Trimming = None
OnClick = GenerateCodeButtonClick OnClick = GenerateCodeButtonClick
end end
object InputEdit: TEdit
Touch.InteractiveGestures = [LongTap, DoubleTap]
Align = Left
TabOrder = 3
Text = '1345'
Position.X = 240.000000000000000000
Position.Y = 10.000000000000000000
Margins.Top = 10.000000000000000000
Margins.Bottom = 10.000000000000000000
Size.Width = 73.000000000000000000
Size.Height = 20.000000000000000000
Size.PlatformDefault = False
end
end end
end end
object Memo: TMemo object Memo: TMemo
+58 -40
View File
@@ -5,30 +5,29 @@ interface
uses uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 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, 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, Winapi.WebView2, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, FMX.Edit, FMX.StdActns,
// FMX.Edit für TEdit, FMX.StdActns oft für Standardaktionen
// Korrekte und vollständige Units // Korrekte und vollständige Units
System.IOUtils, System.Threading, System.Diagnostics, Winapi.Windows, LLVM.Runner; System.IOUtils, System.Threading, System.Diagnostics, Winapi.Windows, LLVM.Runner;
const const
// Pfad zur HTML-Datei // Pfad zur HTML-Datei (BITTE HIER ANPASSEN!)
PATH_BLOCKLY = 'T:\Myc\Blockly\index.html'; PATH_BLOCKLY = 'T:\Myc\Blockly\index.html'; // <--- PRÜFEN UND ANPASSEN!
// Pfade zu den LLVM-Werkzeugen // Pfade zu den LLVM-Werkzeugen (BITTE HIER ANPASSEN!)
CLANG_PATH = 'clang.exe'; CLANG_PATH = 'clang.exe'; // <--- PRÜFEN UND ANPASSEN!
LLD_PATH = 'lld-link.exe'; LLD_PATH = 'lld-link.exe'; // <--- PRÜFEN UND ANPASSEN!
type type
TMainForm = class( TForm ) TMainForm = class( TForm )
WebBrowser1: TWebBrowser; WebBrowser1: TWebBrowser;
Memo: TMemo;
Splitter1: TSplitter;
ToolBar1: TToolBar;
RefreshButton: TSpeedButton;
SaveWorkspaceButton: TSpeedButton; SaveWorkspaceButton: TSpeedButton;
GenerateCodeButton: TSpeedButton; GenerateCodeButton: TSpeedButton;
Memo: TMemo;
InputEdit: TEdit; // <--- Hinzugefügt (Optional für Beschriftung des InputEdit)
procedure FormCreate( Sender: TObject ); procedure FormCreate( Sender: TObject );
procedure FormDestroy( Sender: TObject ); procedure FormDestroy( Sender: TObject );
procedure LauchSiteButtonClick( Sender: TObject ); procedure RefreshButtonClick( Sender: TObject );
procedure SaveWorkspaceButtonClick( Sender: TObject ); procedure SaveWorkspaceButtonClick( Sender: TObject );
procedure GenerateCodeButtonClick( Sender: TObject ); procedure GenerateCodeButtonClick( Sender: TObject );
procedure WebBrowser1DidFinishLoad( ASender: TObject ); procedure WebBrowser1DidFinishLoad( ASender: TObject );
@@ -85,7 +84,8 @@ end;
procedure TMainForm.FormCreate( Sender: TObject ); procedure TMainForm.FormCreate( Sender: TObject );
begin begin
TLLVMRunner.Log := Memo.Lines; TLLVMRunner.Log := Memo.Lines;
LauchSiteButtonClick(Self); // Optional: Standardwert für InputEdit
InputEdit.Text := '123';
end; end;
procedure TMainForm.FormDestroy( Sender: TObject ); procedure TMainForm.FormDestroy( Sender: TObject );
@@ -107,7 +107,7 @@ var
cmdLine: string; cmdLine: string;
buffer: TBytes; buffer: TBytes;
bytesRead: Cardinal; bytesRead: Cardinal;
output: string; output, errors: string;
begin begin
// Pipe für StdOut und StdErr erstellen // Pipe für StdOut und StdErr erstellen
sa.nLength := SizeOf( TSecurityAttributes ); sa.nLength := SizeOf( TSecurityAttributes );
@@ -186,7 +186,7 @@ begin
end; end;
end; end;
procedure TMainForm.LauchSiteButtonClick( Sender: TObject ); procedure TMainForm.RefreshButtonClick( Sender: TObject );
begin begin
WebBrowser1.Navigate( PATH_BLOCKLY ); WebBrowser1.Navigate( PATH_BLOCKLY );
end; end;
@@ -202,40 +202,53 @@ begin
end; end;
procedure TMainForm.HandleWebMessage( const AMessage: string ); procedure TMainForm.HandleWebMessage( const AMessage: string );
var
llFile, objFile, dllFile: string;
compilerOutput: string;
success: Boolean;
InputInteger: Integer; // <--- Hinzugefügt
begin begin
Memo.Lines.Clear; Memo.Lines.Clear;
Memo.Lines.Add( 'LLVM-Code empfangen. Starte Kompilierung...' ); Memo.Lines.Add( 'LLVM-Code empfangen. Starte Kompilierung...' );
Memo.Lines.Add( AMessage ); Memo.Lines.Add( AMessage );
Memo.Lines.Add( '--------------------' ); Memo.Lines.Add( '--------------------' );
// Wert aus dem InputEdit-Feld holen
try
InputInteger := StrToIntDef( InputEdit.Text, 0 ); // Standardwert 0, falls ungültig
except
InputInteger := 0;
Memo.Lines.Add( 'WARNUNG: Ungültige Eingabe im InputEdit-Feld. Verwende 0.' );
end;
TTask.Run( TTask.Run(
procedure procedure
var var
llFile, objFile, dllFile: string; llFileLocal, objFileLocal, dllFileLocal: string; // Lokale Variablen für den Task
compilerOutput: string; compilerOutputLocal: string;
success: Boolean; successLocal: Boolean;
begin begin
llFile := TPath.Combine( TPath.GetTempPath, 'poc.ll' ); llFileLocal := TPath.Combine( TPath.GetTempPath, 'poc.ll' );
objFile := TPath.ChangeExtension( llFile, '.obj' ); objFileLocal := TPath.ChangeExtension( llFileLocal, '.obj' );
dllFile := TPath.ChangeExtension( llFile, '.dll' ); dllFileLocal := TPath.ChangeExtension( llFileLocal, '.dll' );
success := False; successLocal := False;
try try
TFile.WriteAllText( llFile, AMessage ); TFile.WriteAllText( llFileLocal, AMessage );
TThread.Queue( nil, TThread.Queue( nil,
procedure procedure
begin begin
Memo.Lines.Add( 'Kompiliere zu Objektdatei...' ); Memo.Lines.Add( 'Kompiliere zu Objektdatei...' );
end ); end );
compilerOutput := ExecuteProcess( CLANG_PATH, Format( '-c "%s" -o "%s"', [llFile, objFile] ) ); compilerOutputLocal := ExecuteProcess( CLANG_PATH, Format( '-c "%s" -o "%s"', [llFileLocal, objFileLocal] ) );
TThread.Queue( nil, TThread.Queue( nil,
procedure procedure
begin begin
Memo.Lines.Add( compilerOutput ); Memo.Lines.Add( compilerOutputLocal );
end ); end );
if not TFile.Exists( objFile ) then if not TFile.Exists( objFileLocal ) then
begin begin
TThread.Queue( nil, TThread.Queue( nil,
procedure procedure
@@ -250,20 +263,20 @@ begin
begin begin
Memo.Lines.Add( 'Linke zu DLL...' ); Memo.Lines.Add( 'Linke zu DLL...' );
end ); end );
compilerOutput := ExecuteProcess( LLD_PATH, Format( '/dll /noentry "%s" /out:"%s"', [objFile, dllFile] ) ); compilerOutputLocal := ExecuteProcess( LLD_PATH, Format( '/dll /noentry "%s" /out:"%s"', [objFileLocal, dllFileLocal] ) );
TThread.Queue( nil, TThread.Queue( nil,
procedure procedure
begin begin
Memo.Lines.Add( compilerOutput ); Memo.Lines.Add( compilerOutputLocal );
end ); end );
if TFile.Exists( dllFile ) then if TFile.Exists( dllFileLocal ) then
begin begin
success := True; successLocal := True;
TThread.Queue( nil, TThread.Queue( nil,
procedure procedure
begin begin
Memo.Lines.Add( Format( 'ERFOLG: DLL wurde erstellt: %s', [dllFile] ) ); Memo.Lines.Add( Format( 'ERFOLG: DLL wurde erstellt: %s', [dllFileLocal] ) );
end ); end );
end end
else else
@@ -276,30 +289,35 @@ begin
end; end;
finally finally
if TFile.Exists( llFile ) then if TFile.Exists( llFileLocal ) then
TFile.Delete( llFile ); TFile.Delete( llFileLocal );
if TFile.Exists( objFile ) then if TFile.Exists( objFileLocal ) then
TFile.Delete( objFile ); TFile.Delete( objFileLocal );
if not success and TFile.Exists( dllFile ) then if not successLocal and TFile.Exists( dllFileLocal ) then
TFile.Delete( dllFile ); TFile.Delete( dllFileLocal );
end; end;
TThread.Queue( nil,
procedure
begin
Memo.Lines.Add( '---------------------------------------------------------' ); Memo.Lines.Add( '---------------------------------------------------------' );
Memo.Lines.Add( 'DLL wird aufgerufen:' ); Memo.Lines.Add( 'DLL wird aufgerufen:' );
if TFile.Exists( dllFile ) then if TFile.Exists( dllFileLocal ) then
begin begin
var var
Runner := TLLVMRunner.Create( dllFile ); Runner := TLLVMRunner.Create( dllFileLocal );
try try
Runner.Execute; Runner.Execute( InputInteger ); // <--- HIER DEN INPUT-PARAMETER ÜBERGEBEN
finally finally
Runner.Free; Runner.Free;
end; end;
end; end;
end );
Memo.Lines.Add( 'DLL beendet.' ); Memo.Lines.Add( 'DLL beendet.' );
Memo.CaretPosition := TCaretPosition.Create( Memo.Lines.Count-1, 1 );
end );
end );
end; end;
procedure TMainForm.WebBrowser1DidFinishLoad( ASender: TObject ); procedure TMainForm.WebBrowser1DidFinishLoad( ASender: TObject );
+64 -113
View File
@@ -3,6 +3,16 @@
let workspace; let workspace;
const isWebView = window.chrome && window.chrome.webview; const isWebView = window.chrome && window.chrome.webview;
// === Debounce-Funktion ===
let debounceTimeout;
const debounce = (func, delay) => {
return function(...args) {
const context = this;
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => func.apply(context, args), delay);
};
};
// === LLVM-IR-GENERATOR (FINALE VERSION) === // === LLVM-IR-GENERATOR (FINALE VERSION) ===
Blockly.LLVM = new Blockly.Generator('LLVM'); Blockly.LLVM = new Blockly.Generator('LLVM');
Blockly.LLVM.ORDER_ATOMIC = 0; Blockly.LLVM.ORDER_ATOMIC = 0;
@@ -27,50 +37,31 @@
"helpUrl": "" "helpUrl": ""
}, },
{ {
"type": "current_datetime_utc", "type": "host_get_integer_input",
"message0": "Aktueller UTC-Zeitstempel", "message0": "Hole Integer-Eingabe von Host",
"output": "DateTimeUTC", "output": "Number",
"colour": 120, "colour": 290,
"tooltip": "Liefert den aktuellen Zeitstempel in UTC.", "tooltip": "Liefert einen Integer-Wert, der vom Delphi-Host übergeben wurde.",
"helpUrl": "" "helpUrl": ""
}, },
{ {
"type": "get_datetime_component", "type": "host_add_integers",
"message0": "Hole %1 von Zeitstempel %2 in Zeitzone %3", "message0": "Addiere %1 und %2 auf Host",
"args0": [ "args0": [
{ {
"type": "field_dropdown", "type": "input_value",
"name": "COMPONENT", "name": "NUM1",
"options": [ "check": "Number"
["Jahr", "YEAR"],
["Monat", "MONTH"],
["Tag", "DAY"],
["Stunde", "HOUR"],
["Minute", "MINUTE"],
["Sekunde", "SECOND"]
]
}, },
{ {
"type": "input_value", "type": "input_value",
"name": "DATETIME", "name": "NUM2",
"check": "DateTimeUTC" "check": "Number"
},
{
"type": "field_dropdown",
"name": "TIMEZONE",
"options": [
["UTC", "UTC"],
["ET (New York)", "ET"],
["WET (London)", "WET"],
["CET (Frankfurt)", "CET"],
["AEST (Sydney)", "AEST"],
["JST (Tokyo)", "JST"]
]
} }
], ],
"output": "Number", "output": "Number",
"colour": 120, "colour": 290, // Host-Farbe
"tooltip": "Extrahiert eine Komponente (Jahr, Monat, etc.) aus einem UTC-Zeitstempel und konvertiert sie in die angegebene Zeitzone.", "tooltip": "Addiert zwei Integer-Werte unter Verwendung einer Host-Funktion.",
"helpUrl": "" "helpUrl": ""
} }
]); ]);
@@ -102,25 +93,17 @@
let moduleCode = `target triple = "x86_64-pc-windows-msvc"\n\n`; let moduleCode = `target triple = "x86_64-pc-windows-msvc"\n\n`;
moduleCode += `; Definition des DateTimeUTC-Structs\n`; // NEU: Deklaration der Host-Funktion (Signatur, die der Host implementiert)
moduleCode += `%struct.DateTimeUTC = type { i32, i32, i32, i32, i32, i32 }\n\n`; moduleCode += `; Host Provided Function Declarations\n`;
moduleCode += `declare i32 @host_add_integers_func(i32, i32)\n\n`; // Beispiel: Funktion, die zwei i32 nimmt und i32 zurückgibt
if (Blockly.LLVM.globalDeclarations) {
moduleCode += '; Global string constants for timezones\n';
moduleCode += Blockly.LLVM.globalDeclarations + '\n';
}
moduleCode += `; Host Callback Declarations\n`;
moduleCode += `declare void @get_current_datetime_utc(%struct.DateTimeUTC*)\n`;
moduleCode += `declare i32 @get_datetime_component_in_zone(%struct.DateTimeUTC*, i32, i8*)\n`;
if (Blockly.LLVM.functionDeclarations) { if (Blockly.LLVM.functionDeclarations) {
moduleCode += Blockly.LLVM.functionDeclarations + '\n'; moduleCode += Blockly.LLVM.functionDeclarations + '\n';
} }
moduleCode += `@gLogIntProc = common global void (i32)* null\n`; moduleCode += `@gLogIntProc = common global void (i32)* null\n`;
moduleCode += `@gGetCurrentDateTimeUTCProc = common global void (%struct.DateTimeUTC*)* null\n`; // NEU: Globaler Zeiger für die Host-Additionsfunktion
moduleCode += `@gGetComponentInZoneProc = common global i32 (%struct.DateTimeUTC*, i32, i8*)* null\n\n`; moduleCode += `@gHostAddIntegersProc = common global i32 (i32, i32)* null\n\n`;
if (varAllocations) { if (varAllocations) {
@@ -128,11 +111,13 @@
moduleCode += varAllocations + '\n'; moduleCode += varAllocations + '\n';
} }
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc, void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc) {\n'; // StarteLogik-Signatur erweitern, um den neuen Funktionszeiger-Parameter zu akzeptieren
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc, i32 %hostInputInteger, i32 (i32, i32)* %HostAddIntegersProc) {\n';
moduleCode += 'entry:\n'; moduleCode += 'entry:\n';
moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n'; moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n';
moduleCode += ' store void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, void (%struct.DateTimeUTC*)** @gGetCurrentDateTimeUTCProc\n'; // NEU: Den übergebenen Funktionszeiger in den globalen Zeiger speichern
moduleCode += ' store i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n\n'; moduleCode += ' store i32 (i32, i32)* %HostAddIntegersProc, i32 (i32, i32)** @gHostAddIntegersProc\n\n';
moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n'); moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n');
moduleCode += '\n ret void\n'; moduleCode += '\n ret void\n';
@@ -140,6 +125,7 @@
return moduleCode; return moduleCode;
}; };
Blockly.LLVM.scrub_ = function(block, code) { Blockly.LLVM.scrub_ = function(block, code) {
const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
const nextCode = nextBlock ? Blockly.LLVM.blockToCode(nextBlock) : ''; const nextCode = nextBlock ? Blockly.LLVM.blockToCode(nextBlock) : '';
@@ -196,51 +182,23 @@
return code; return code;
}; };
Blockly.LLVM.forBlock['current_datetime_utc'] = function(block) { Blockly.LLVM.forBlock['host_get_integer_input'] = function(block) {
const structPtrReg = Blockly.LLVM.newReg(); const inputParamReg = '%hostInputInteger';
const getUtcProcPtrReg = Blockly.LLVM.newReg(); return [inputParamReg, Blockly.LLVM.ORDER_ATOMIC];
let code = `
${structPtrReg} = alloca %struct.DateTimeUTC, align 8
${getUtcProcPtrReg} = load void (%struct.DateTimeUTC*)*, void (%struct.DateTimeUTC*)** @gGetCurrentDateTimeUTCProc\n
call void ${getUtcProcPtrReg}(%struct.DateTimeUTC* ${structPtrReg})
`;
return [code + structPtrReg, Blockly.LLVM.ORDER_ATOMIC];
}; };
Blockly.LLVM.forBlock['get_datetime_component'] = function(block) { Blockly.LLVM.forBlock['host_add_integers'] = function(block) {
const datetimePtr = Blockly.LLVM.valueToCode(block, 'DATETIME', Blockly.LLVM.ORDER_ATOMIC) || 'null'; const num1 = Blockly.LLVM.valueToCode(block, 'NUM1', Blockly.LLVM.ORDER_ATOMIC) || '0';
const component = block.getFieldValue('COMPONENT'); const num2 = Blockly.LLVM.valueToCode(block, 'NUM2', Blockly.LLVM.ORDER_ATOMIC) || '0';
const timezone = block.getFieldValue('TIMEZONE');
const timezoneGlobalName = `@.str.timezone_${timezone.replace(/[^a-zA-Z0-9_]/g, '')}`;
const timezoneStringLength = new TextEncoder().encode(timezone).length + 1;
if (!Blockly.LLVM.generatedStrings.has(timezoneGlobalName)) {
Blockly.LLVM.globalDeclarations += `${timezoneGlobalName} = private unnamed_addr constant [${timezoneStringLength} x i8] c"${timezone}\\00", align 1\n`;
Blockly.LLVM.generatedStrings.add(timezoneGlobalName);
}
let componentIndex = 0;
switch (component) {
case 'YEAR': componentIndex = 0; break;
case 'MONTH': componentIndex = 1; break;
case 'DAY': componentIndex = 2; break;
case 'HOUR': componentIndex = 3; break;
case 'MINUTE': componentIndex = 4; break;
case 'SECOND': componentIndex = 5; break;
}
const funcPtrReg = Blockly.LLVM.newReg();
const resultReg = Blockly.LLVM.newReg(); const resultReg = Blockly.LLVM.newReg();
const timezoneStringPtrReg = Blockly.LLVM.newReg();
const getComponentProcPtrReg = Blockly.LLVM.newReg();
let code = ` let code = '';
${timezoneStringPtrReg} = getelementptr inbounds [${timezoneStringLength} x i8], [${timezoneStringLength} x i8]* ${timezoneGlobalName}, i64 0, i64 0 // Lade den globalen Funktionszeiger, der vom Host gesetzt wird
${getComponentProcPtrReg} = load i32 (%struct.DateTimeUTC*, i32, i8*)*, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n code += `${funcPtrReg} = load i32 (i32, i32)*, i32 (i32, i32)** @gHostAddIntegersProc\n`;
${resultReg} = call i32 ${getComponentProcPtrReg}(%struct.DateTimeUTC* ${datetimePtr}, i32 ${componentIndex}, i8* ${timezoneStringPtrReg}) // Rufe die Host-Funktion auf
`; code += `${resultReg} = call i32 ${funcPtrReg}(i32 ${num1}, i32 ${num2})\n`;
return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC]; return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC];
}; };
@@ -256,12 +214,10 @@
</category> </category>
<category name="Host" colour="290"> <category name="Host" colour="290">
<block type="host_log_integer"></block> <block type="host_log_integer"></block>
<block type="host_get_integer_input"></block>
<block type="host_add_integers"></block>
</category> </category>
<category name="Datum &amp; Zeit" colour="120"> </xml>`; // <--- DAS FEHLENDE BACKTICK HIER IST DIE LÖSUNG!
<block type="current_datetime_utc"></block>
<block type="get_datetime_component"></block>
</category>
</xml>`;
workspace = Blockly.inject('blocklyDiv', { workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml, toolbox: toolboxXml,
@@ -310,20 +266,23 @@
console.log(code); console.log(code);
} }
} }
window.generateAndPostCode = generateAndPostCode; // Exponiere für Buttons window.generateAndPostCode = generateAndPostCode;
window.saveWorkspace = function() { window.saveWorkspace = function() {
// Diese Funktion kann den Arbeitsbereich manuell speichern und einen Alert anzeigen
const state = Blockly.serialization.workspaces.save(workspace); const state = Blockly.serialization.workspaces.save(workspace);
localStorage.setItem('blocklyWorkspace', JSON.stringify(state)); localStorage.setItem('blocklyWorkspace', JSON.stringify(state));
alert('Arbeitsbereich gespeichert!'); 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); } window.loadWorkspace = function() { const stateString = localStorage.getItem('blocklyWorkspace'); if (!stateString) { return; } const state = JSON.parse(stateString); Blockly.serialization.workspaces.load(state, workspace); }
// --- Dynamische Code-Aktualisierung / Posten (ruft generateAndPostCode auf) --- // --- Debounced-Funktion für die Code-Generierung ---
// Die Generierung wird erst 500ms nach der letzten Änderung im Arbeitsbereich ausgelöst.
const debouncedGenerateAndPostCode = debounce(generateAndPostCode, 500);
// --- Dynamische Code-Aktualisierung / Posten (ruft debouncedGenerateAndPostCode auf) ---
function handleCodeUpdate(event) { function handleCodeUpdate(event) {
if (event.isUiEvent) return; // Nur Änderungen durch Blöcke, nicht UI-Interaktionen if (event.isUiEvent) return;
generateAndPostCode(); // Dies triggert jetzt sowohl Speichern als auch Generieren/Posten debouncedGenerateAndPostCode();
} }
workspace.addChangeListener(handleCodeUpdate); workspace.addChangeListener(handleCodeUpdate);
loadWorkspace(); loadWorkspace();
@@ -333,31 +292,23 @@
const blocklyDiv = document.getElementById('blocklyDiv'); const blocklyDiv = document.getElementById('blocklyDiv');
if (isWebView) { if (isWebView) {
// Wenn im WebView, outputContainer bleibt ausgeblendet (CSS default: display: none;)
// BlocklyDiv die gesamte Breite geben
blocklyDiv.style.width = '100%'; blocklyDiv.style.width = '100%';
// Blockly-Workspace an neue Größe anpassen, mit einer kleinen Verzögerung für WebView
setTimeout(function() { setTimeout(function() {
Blockly.svgResize(workspace); Blockly.svgResize(workspace);
}, 100); // 100ms Verzögerung, kann bei Bedarf angepasst werden. }, 100);
// Entferne die Schaltflächen "Speichern" und "Laden", wenn im WebView
// da die Speicherung automatisch erfolgt und der Host den Code direkt bekommt.
const buttonBar = document.querySelector('.button-bar'); const buttonBar = document.querySelector('.button-bar');
if (buttonBar) { if (buttonBar) {
const saveButton = buttonBar.querySelector('button[onclick="saveWorkspace()"]'); const saveButton = buttonBar.querySelector('button[onclick="saveWorkspace()"]');
const loadButton = buttonBar.querySelector('button[onclick="loadWorkspace()"]'); const loadButton = buttonBar.querySelector('button[onclick="loadWorkspace()"]');
if (saveButton) saveButton.remove(); if (saveButton) saveButton.remove();
if (loadButton) loadButton.remove(); if (loadButton) loadButton.remove();
// Eventuell auch den "Code generieren und posten" Button entfernen, da es automatisch passiert
// oder umbenennen in "Aktualisieren" / "Neu senden"
const postButton = buttonBar.querySelector('button[onclick="generateAndPostCode()"]'); const postButton = buttonBar.querySelector('button[onclick="generateAndPostCode()"]');
if (postButton) postButton.remove(); // Oder postButton.textContent = "Code senden"; if (postButton) postButton.remove();
} }
} else { } else {
// Wenn NICHT im WebView, outputContainer einblenden und Splitter-Logik initialisieren outputContainer.style.display = 'flex';
outputContainer.style.display = 'flex'; // Einblenden als Flex-Container
const outputArea = document.getElementById('outputArea'); const outputArea = document.getElementById('outputArea');
const splitter = document.getElementById('splitter'); const splitter = document.getElementById('splitter');
@@ -410,12 +361,12 @@
document.body.style.cursor = ''; document.body.style.cursor = '';
}); });
// Initiales Resize für Nicht-WebView: Setzt die initialen Breiten const initialOutputAreaWidth = 500;
// Basierend auf der Startbreite der outputArea (500px)
const initialOutputAreaWidth = 500; // Aus CSS initialer Wert
const totalFlexContentWidth = container.offsetWidth - splitter.offsetWidth - parseInt(window.getComputedStyle(outputArea).marginLeft); const totalFlexContentWidth = container.offsetWidth - splitter.offsetWidth - parseInt(window.getComputedStyle(outputArea).marginLeft);
let initialBlocklyWidth = totalFlexContentWidth - initialOutputAreaWidth; let initialBlocklyWidth = totalFlexContentWidth - initialOutputAreaWidth;
const minBlocklyWidth = parseFloat(window.getComputedStyle(blocklyDiv).minWidth);
if (initialBlocklyWidth < minBlocklyWidth) { if (initialBlocklyWidth < minBlocklyWidth) {
initialBlocklyWidth = minBlocklyWidth; initialBlocklyWidth = minBlocklyWidth;
outputArea.style.width = `${totalFlexContentWidth - initialBlocklyWidth}px`; outputArea.style.width = `${totalFlexContentWidth - initialBlocklyWidth}px`;