381 lines
14 KiB
JavaScript
381 lines
14 KiB
JavaScript
window.onload = function() {
|
|
|
|
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;
|
|
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": "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": "host_add_integers",
|
|
"message0": "Addiere %1 und %2 auf Host",
|
|
"args0": [
|
|
{
|
|
"type": "input_value",
|
|
"name": "NUM1",
|
|
"check": "Number"
|
|
},
|
|
{
|
|
"type": "input_value",
|
|
"name": "NUM2",
|
|
"check": "Number"
|
|
}
|
|
],
|
|
"output": "Number",
|
|
"colour": 290, // Host-Farbe
|
|
"tooltip": "Addiert zwei Integer-Werte unter Verwendung einer Host-Funktion.",
|
|
"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`;
|
|
|
|
// 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.functionDeclarations) {
|
|
moduleCode += Blockly.LLVM.functionDeclarations + '\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';
|
|
}
|
|
|
|
// 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;
|
|
};
|
|
|
|
|
|
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['host_get_integer_input'] = function(block) {
|
|
const inputParamReg = '%hostInputInteger';
|
|
return [inputParamReg, Blockly.LLVM.ORDER_ATOMIC];
|
|
};
|
|
|
|
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 funcPtrReg = Blockly.LLVM.newReg();
|
|
const resultReg = Blockly.LLVM.newReg();
|
|
|
|
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`;
|
|
|
|
return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC];
|
|
};
|
|
|
|
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>
|
|
<block type="host_get_integer_input"></block>
|
|
<block type="host_add_integers"></block>
|
|
</category>
|
|
</xml>`; // <--- DAS FEHLENDE BACKTICK HIER IST DIE LÖSUNG!
|
|
|
|
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;
|
|
|
|
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); }
|
|
|
|
// --- 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;
|
|
debouncedGenerateAndPostCode();
|
|
}
|
|
workspace.addChangeListener(handleCodeUpdate);
|
|
loadWorkspace();
|
|
|
|
// --- Bedingte UI-Elemente und Splitter-Logik ---
|
|
const outputContainer = document.getElementById('outputContainer');
|
|
const blocklyDiv = document.getElementById('blocklyDiv');
|
|
|
|
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();
|
|
const postButton = buttonBar.querySelector('button[onclick="generateAndPostCode()"]');
|
|
if (postButton) postButton.remove();
|
|
}
|
|
|
|
} else {
|
|
outputContainer.style.display = 'flex';
|
|
|
|
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 = '';
|
|
});
|
|
|
|
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`;
|
|
} else {
|
|
outputArea.style.width = `${initialOutputAreaWidth}px`;
|
|
}
|
|
blocklyDiv.style.width = `${initialBlocklyWidth}px`;
|
|
|
|
Blockly.svgResize(workspace);
|
|
}
|
|
};
|