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
+110 -159
View File
@@ -3,6 +3,16 @@
let workspace;
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) ===
Blockly.LLVM = new Blockly.Generator('LLVM');
Blockly.LLVM.ORDER_ATOMIC = 0;
@@ -27,50 +37,31 @@
"helpUrl": ""
},
{
"type": "current_datetime_utc",
"message0": "Aktueller UTC-Zeitstempel",
"output": "DateTimeUTC",
"colour": 120,
"tooltip": "Liefert den aktuellen Zeitstempel in UTC.",
"type": "host_get_integer_input",
"message0": "Hole Integer-Eingabe von Host",
"output": "Number",
"colour": 290,
"tooltip": "Liefert einen Integer-Wert, der vom Delphi-Host übergeben wurde.",
"helpUrl": ""
},
{
"type": "get_datetime_component",
"message0": "Hole %1 von Zeitstempel %2 in Zeitzone %3",
"type": "host_add_integers",
"message0": "Addiere %1 und %2 auf Host",
"args0": [
{
"type": "field_dropdown",
"name": "COMPONENT",
"options": [
["Jahr", "YEAR"],
["Monat", "MONTH"],
["Tag", "DAY"],
["Stunde", "HOUR"],
["Minute", "MINUTE"],
["Sekunde", "SECOND"]
]
"type": "input_value",
"name": "NUM1",
"check": "Number"
},
{
"type": "input_value",
"name": "DATETIME",
"check": "DateTimeUTC"
},
{
"type": "field_dropdown",
"name": "TIMEZONE",
"options": [
["UTC", "UTC"],
["ET (New York)", "ET"],
["WET (London)", "WET"],
["CET (Frankfurt)", "CET"],
["AEST (Sydney)", "AEST"],
["JST (Tokyo)", "JST"]
]
"name": "NUM2",
"check": "Number"
}
],
"output": "Number",
"colour": 120,
"tooltip": "Extrahiert eine Komponente (Jahr, Monat, etc.) aus einem UTC-Zeitstempel und konvertiert sie in die angegebene Zeitzone.",
"colour": 290, // Host-Farbe
"tooltip": "Addiert zwei Integer-Werte unter Verwendung einer Host-Funktion.",
"helpUrl": ""
}
]);
@@ -83,63 +74,58 @@
};
Blockly.LLVM.workspaceToCode = function(workspace) {
Blockly.LLVM.regCounter = 1;
Blockly.LLVM.generatedStrings = new Set();
Blockly.LLVM.globalDeclarations = '';
Blockly.LLVM.functionDeclarations = '';
Blockly.LLVM.regCounter = 1;
Blockly.LLVM.generatedStrings = new Set();
Blockly.LLVM.globalDeclarations = '';
Blockly.LLVM.functionDeclarations = '';
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 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) : '';
const topBlock = workspace.getTopBlocks(true)[0];
const code = topBlock ? Blockly.LLVM.blockToCode(topBlock) : '';
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`;
moduleCode += `%struct.DateTimeUTC = type { i32, i32, i32, i32, i32, i32 }\n\n`;
// NEU: Deklaration der Host-Funktion (Signatur, die der Host implementiert)
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';
}
if (Blockly.LLVM.functionDeclarations) {
moduleCode += Blockly.LLVM.functionDeclarations + '\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) {
moduleCode += Blockly.LLVM.functionDeclarations + '\n';
}
moduleCode += `@gLogIntProc = common global void (i32)* null\n`;
moduleCode += `@gGetCurrentDateTimeUTCProc = common global void (%struct.DateTimeUTC*)* null\n`;
moduleCode += `@gGetComponentInZoneProc = common global i32 (%struct.DateTimeUTC*, i32, i8*)* null\n\n`;
moduleCode += `@gLogIntProc = common global void (i32)* null\n`;
// NEU: Globaler Zeiger für die Host-Additionsfunktion
moduleCode += `@gHostAddIntegersProc = common global i32 (i32, i32)* null\n\n`;
if (varAllocations) {
moduleCode += '; Global Variables\n';
moduleCode += varAllocations + '\n';
}
if (varAllocations) {
moduleCode += '; Global Variables\n';
moduleCode += varAllocations + '\n';
}
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc, void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc) {\n';
moduleCode += 'entry:\n';
moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n';
moduleCode += ' store void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, void (%struct.DateTimeUTC*)** @gGetCurrentDateTimeUTCProc\n';
moduleCode += ' store i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n\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 += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n';
// NEU: Den übergebenen Funktionszeiger in den globalen Zeiger speichern
moduleCode += ' store i32 (i32, i32)* %HostAddIntegersProc, i32 (i32, i32)** @gHostAddIntegersProc\n\n';
moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n');
moduleCode += '\n ret void\n';
moduleCode += '}\n';
return moduleCode;
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) : '';
@@ -180,7 +166,7 @@
let valueReg;
let code = '';
if (valueCode.startsWith('@')) {
valueReg = Blockly.LLVM.newReg();
code += `${valueReg} = load i32, i32* ${valueCode}\n`;
@@ -189,61 +175,33 @@
} else {
valueReg = valueCode;
}
const logPtrReg = Blockly.LLVM.newReg();
code += `${logPtrReg} = load void (i32)*, void (i32)** @gLogIntProc\n`;
code += `call void ${logPtrReg}(i32 ${valueReg})\n`;
return code;
};
Blockly.LLVM.forBlock['current_datetime_utc'] = function(block) {
const structPtrReg = Blockly.LLVM.newReg();
const getUtcProcPtrReg = Blockly.LLVM.newReg();
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['host_get_integer_input'] = function(block) {
const inputParamReg = '%hostInputInteger';
return [inputParamReg, Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['get_datetime_component'] = function(block) {
const datetimePtr = Blockly.LLVM.valueToCode(block, 'DATETIME', Blockly.LLVM.ORDER_ATOMIC) || 'null';
const component = block.getFieldValue('COMPONENT');
const timezone = block.getFieldValue('TIMEZONE');
Blockly.LLVM.forBlock['host_add_integers'] = function(block) {
const num1 = Blockly.LLVM.valueToCode(block, 'NUM1', Blockly.LLVM.ORDER_ATOMIC) || '0';
const num2 = Blockly.LLVM.valueToCode(block, 'NUM2', Blockly.LLVM.ORDER_ATOMIC) || '0';
const timezoneGlobalName = `@.str.timezone_${timezone.replace(/[^a-zA-Z0-9_]/g, '')}`;
const timezoneStringLength = new TextEncoder().encode(timezone).length + 1;
const funcPtrReg = Blockly.LLVM.newReg();
const resultReg = Blockly.LLVM.newReg();
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 code = '';
// Lade den globalen Funktionszeiger, der vom Host gesetzt wird
code += `${funcPtrReg} = load i32 (i32, i32)*, i32 (i32, i32)** @gHostAddIntegersProc\n`;
// Rufe die Host-Funktion auf
code += `${resultReg} = call i32 ${funcPtrReg}(i32 ${num1}, i32 ${num2})\n`;
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 resultReg = Blockly.LLVM.newReg();
const timezoneStringPtrReg = Blockly.LLVM.newReg();
const getComponentProcPtrReg = Blockly.LLVM.newReg();
let code = `
${timezoneStringPtrReg} = getelementptr inbounds [${timezoneStringLength} x i8], [${timezoneStringLength} x i8]* ${timezoneGlobalName}, i64 0, i64 0
${getComponentProcPtrReg} = load i32 (%struct.DateTimeUTC*, i32, i8*)*, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n
${resultReg} = call i32 ${getComponentProcPtrReg}(%struct.DateTimeUTC* ${datetimePtr}, i32 ${componentIndex}, i8* ${timezoneStringPtrReg})
`;
return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC];
};
return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC];
};
const toolboxXml = `
<xml>
@@ -256,12 +214,10 @@
</category>
<category name="Host" colour="290">
<block type="host_log_integer"></block>
<block type="host_get_integer_input"></block>
<block type="host_add_integers"></block>
</category>
<category name="Datum &amp; Zeit" colour="120">
<block type="current_datetime_utc"></block>
<block type="get_datetime_component"></block>
</category>
</xml>`;
</xml>`; // <--- DAS FEHLENDE BACKTICK HIER IST DIE LÖSUNG!
workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml,
@@ -310,20 +266,23 @@
console.log(code);
}
}
window.generateAndPostCode = generateAndPostCode; // Exponiere für Buttons
window.generateAndPostCode = generateAndPostCode;
window.saveWorkspace = function() {
// Diese Funktion kann den Arbeitsbereich manuell speichern und einen Alert anzeigen
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); }
// --- 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) {
if (event.isUiEvent) return; // Nur Änderungen durch Blöcke, nicht UI-Interaktionen
generateAndPostCode(); // Dies triggert jetzt sowohl Speichern als auch Generieren/Posten
if (event.isUiEvent) return;
debouncedGenerateAndPostCode();
}
workspace.addChangeListener(handleCodeUpdate);
loadWorkspace();
@@ -331,34 +290,26 @@
// --- Bedingte UI-Elemente und Splitter-Logik ---
const outputContainer = document.getElementById('outputContainer');
const blocklyDiv = document.getElementById('blocklyDiv');
if (isWebView) {
// Wenn im WebView, outputContainer bleibt ausgeblendet (CSS default: display: none;)
// BlocklyDiv die gesamte Breite geben
blocklyDiv.style.width = '100%';
// Blockly-Workspace an neue Größe anpassen, mit einer kleinen Verzögerung für WebView
setTimeout(function() {
Blockly.svgResize(workspace);
}, 100); // 100ms Verzögerung, kann bei Bedarf angepasst werden.
// Entferne die Schaltflächen "Speichern" und "Laden", wenn im WebView
// da die Speicherung automatisch erfolgt und der Host den Code direkt bekommt.
if (isWebView) {
blocklyDiv.style.width = '100%';
setTimeout(function() {
Blockly.svgResize(workspace);
}, 100);
const buttonBar = document.querySelector('.button-bar');
if (buttonBar) {
const saveButton = buttonBar.querySelector('button[onclick="saveWorkspace()"]');
const loadButton = buttonBar.querySelector('button[onclick="loadWorkspace()"]');
if (saveButton) saveButton.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()"]');
if (postButton) postButton.remove(); // Oder postButton.textContent = "Code senden";
if (postButton) postButton.remove();
}
} else {
// Wenn NICHT im WebView, outputContainer einblenden und Splitter-Logik initialisieren
outputContainer.style.display = 'flex'; // Einblenden als Flex-Container
outputContainer.style.display = 'flex';
const outputArea = document.getElementById('outputArea');
const splitter = document.getElementById('splitter');
const container = document.getElementById('container');
@@ -377,13 +328,13 @@
const containerRect = container.getBoundingClientRect();
const splitterWidth = splitter.offsetWidth;
const outputAreaMarginLeft = parseInt(window.getComputedStyle(outputArea).marginLeft);
const totalContentWidth = containerRect.width - splitterWidth - outputAreaMarginLeft;
let newBlocklyWidth = e.clientX - containerRect.left;
const minBlocklyWidth = parseFloat(window.getComputedStyle(blocklyDiv).minWidth);
const minOutputAreaWidth = parseFloat(window.getComputedStyle(outputArea).minWidth);
if (newBlocklyWidth < minBlocklyWidth) {
newBlocklyWidth = minBlocklyWidth;
}
@@ -400,7 +351,7 @@
blocklyDiv.style.width = `${newBlocklyWidth}px`;
outputArea.style.width = `${newOutputAreaWidth}px`;
Blockly.svgResize(workspace);
});
@@ -410,12 +361,12 @@
document.body.style.cursor = '';
});
// Initiales Resize für Nicht-WebView: Setzt die initialen Breiten
// Basierend auf der Startbreite der outputArea (500px)
const initialOutputAreaWidth = 500; // Aus CSS initialer Wert
const initialOutputAreaWidth = 500;
const totalFlexContentWidth = container.offsetWidth - splitter.offsetWidth - parseInt(window.getComputedStyle(outputArea).marginLeft);
let initialBlocklyWidth = totalFlexContentWidth - initialOutputAreaWidth;
const minBlocklyWidth = parseFloat(window.getComputedStyle(blocklyDiv).minWidth);
if (initialBlocklyWidth < minBlocklyWidth) {
initialBlocklyWidth = minBlocklyWidth;
outputArea.style.width = `${totalFlexContentWidth - initialBlocklyWidth}px`;
@@ -423,7 +374,7 @@
outputArea.style.width = `${initialOutputAreaWidth}px`;
}
blocklyDiv.style.width = `${initialBlocklyWidth}px`;
Blockly.svgResize(workspace);
}
};