window.onload = function() { let workspace; const isWebView = window.chrome && window.chrome.webview; // === 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": "" }, { "type": "current_datetime_utc", "message0": "Aktueller UTC-Zeitstempel", "output": "DateTimeUTC", "colour": 120, "tooltip": "Liefert den aktuellen Zeitstempel in UTC.", "helpUrl": "" }, { "type": "get_datetime_component", "message0": "Hole %1 von Zeitstempel %2 in Zeitzone %3", "args0": [ { "type": "field_dropdown", "name": "COMPONENT", "options": [ ["Jahr", "YEAR"], ["Monat", "MONTH"], ["Tag", "DAY"], ["Stunde", "HOUR"], ["Minute", "MINUTE"], ["Sekunde", "SECOND"] ] }, { "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"] ] } ], "output": "Number", "colour": 120, "tooltip": "Extrahiert eine Komponente (Jahr, Monat, etc.) aus einem UTC-Zeitstempel und konvertiert sie in die angegebene Zeitzone.", "helpUrl": "" } ]); // --- Generator-Implementierungen --- Blockly.LLVM.regCounter = 0; Blockly.LLVM.newReg = function() { return '%' + (Blockly.LLVM.regCounter++); }; Blockly.LLVM.workspaceToCode = function(workspace) { 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 topBlock = workspace.getTopBlocks(true)[0]; const code = topBlock ? Blockly.LLVM.blockToCode(topBlock) : ''; 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`; 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) { 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`; 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'; 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 valueCode = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '0'; let valueReg; let code = ''; if (valueCode.startsWith('@')) { valueReg = Blockly.LLVM.newReg(); code += `${valueReg} = load i32, i32* ${valueCode}\n`; } else if (valueCode.startsWith('%')) { valueReg = valueCode; } 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['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'); 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 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]; }; const toolboxXml = ` 1 `; workspace = Blockly.inject('blocklyDiv', { toolbox: toolboxXml, zoom: { controls: true, wheel: true, startScale: 1.0, maxScale: 3, minScale: 0.3, scaleSpeed: 1.2 }, trashcan: true, maxBlocks: 500, scrollbars: true, grid: { spacing: 25, length: 3, colour: '#eee', snap: true }, comments: true, disable: true, collapse: true, readOnly: false }); // --- Code-Generierungs- und Post-Funktion (angepasst, um vorher zu speichern) --- function generateAndPostCode() { // 1. Arbeitsbereich speichern const state = Blockly.serialization.workspaces.save(workspace); localStorage.setItem('blocklyWorkspace', JSON.stringify(state)); // Optional: Konsolenmeldung zur automatischen Speicherung // console.log("Arbeitsbereich automatisch gespeichert vor Code-Generierung."); // 2. Code generieren const code = Blockly.LLVM.workspaceToCode(workspace); // 3. Code posten oder anzeigen if (isWebView) { if (window.chrome && window.chrome.webview) { window.chrome.webview.postMessage(code); } } else { document.getElementById('codeOutput').value = code; console.log("Code in Konsole ausgegeben (nicht im WebView):"); console.log(code); } } window.generateAndPostCode = generateAndPostCode; // Exponiere für Buttons 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) --- 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 } workspace.addChangeListener(handleCodeUpdate); loadWorkspace(); // --- 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. 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"; } } else { // Wenn NICHT im WebView, outputContainer einblenden und Splitter-Logik initialisieren outputContainer.style.display = 'flex'; // Einblenden als Flex-Container const outputArea = document.getElementById('outputArea'); const splitter = document.getElementById('splitter'); const container = document.getElementById('container'); let isDragging = false; splitter.addEventListener('mousedown', function(e) { isDragging = true; document.body.style.userSelect = 'none'; document.body.style.cursor = 'ew-resize'; }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; 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; } let newOutputAreaWidth = totalContentWidth - newBlocklyWidth; if (newOutputAreaWidth < minOutputAreaWidth) { newOutputAreaWidth = minOutputAreaWidth; newBlocklyWidth = totalContentWidth - newOutputAreaWidth; if (newBlocklyWidth < minBlocklyWidth) { newBlocklyWidth = minBlocklyWidth; } } blocklyDiv.style.width = `${newBlocklyWidth}px`; outputArea.style.width = `${newOutputAreaWidth}px`; Blockly.svgResize(workspace); }); document.addEventListener('mouseup', function() { isDragging = false; document.body.style.userSelect = ''; 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 totalFlexContentWidth = container.offsetWidth - splitter.offsetWidth - parseInt(window.getComputedStyle(outputArea).marginLeft); let initialBlocklyWidth = totalFlexContentWidth - initialOutputAreaWidth; if (initialBlocklyWidth < minBlocklyWidth) { initialBlocklyWidth = minBlocklyWidth; outputArea.style.width = `${totalFlexContentWidth - initialBlocklyWidth}px`; } else { outputArea.style.width = `${initialOutputAreaWidth}px`; } blocklyDiv.style.width = `${initialBlocklyWidth}px`; Blockly.svgResize(workspace); } };