896 lines
24 KiB
C++
896 lines
24 KiB
C++
/**************************************************************************/
|
|
/* aethex_compiler.cpp */
|
|
/**************************************************************************/
|
|
/* This file is part of: */
|
|
/* AETHEX ENGINE */
|
|
/* https://aethex.foundation */
|
|
/**************************************************************************/
|
|
/* Copyright (c) 2026-present AeThex Labs. */
|
|
/**************************************************************************/
|
|
|
|
#include "aethex_compiler.h"
|
|
#include "core/io/file_access.h"
|
|
|
|
void AeThexCompiler::Chunk::write_int(int32_t value) {
|
|
code.push_back((value >> 0) & 0xFF);
|
|
code.push_back((value >> 8) & 0xFF);
|
|
code.push_back((value >> 16) & 0xFF);
|
|
code.push_back((value >> 24) & 0xFF);
|
|
}
|
|
|
|
void AeThexCompiler::Chunk::write_float(float value) {
|
|
union { float f; int32_t i; } u;
|
|
u.f = value;
|
|
write_int(u.i);
|
|
}
|
|
|
|
int AeThexCompiler::Chunk::add_constant(const Variant &value) {
|
|
constants.push_back(value);
|
|
return constants.size() - 1;
|
|
}
|
|
|
|
int AeThexCompiler::Chunk::add_string(const String &str) {
|
|
strings.push_back(str);
|
|
return strings.size() - 1;
|
|
}
|
|
|
|
AeThexCompiler::AeThexCompiler() {
|
|
}
|
|
|
|
AeThexCompiler::~AeThexCompiler() {
|
|
}
|
|
|
|
void AeThexCompiler::_bind_methods() {
|
|
BIND_ENUM_CONSTANT(TARGET_BYTECODE);
|
|
BIND_ENUM_CONSTANT(TARGET_LUAU);
|
|
BIND_ENUM_CONSTANT(TARGET_VERSE);
|
|
BIND_ENUM_CONSTANT(TARGET_CSHARP);
|
|
BIND_ENUM_CONSTANT(TARGET_JAVASCRIPT);
|
|
}
|
|
|
|
Error AeThexCompiler::compile(const AeThexParser::ASTNode *root, Target p_target) {
|
|
had_error = false;
|
|
current_target = p_target;
|
|
result = CompiledScript();
|
|
result.target = p_target;
|
|
|
|
if (!root) {
|
|
had_error = true;
|
|
return ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (p_target == TARGET_BYTECODE) {
|
|
current_chunk = &result.main_chunk;
|
|
compile_node(root);
|
|
emit_return();
|
|
} else {
|
|
switch (p_target) {
|
|
case TARGET_LUAU:
|
|
result.output_code = generate_luau(root);
|
|
break;
|
|
case TARGET_VERSE:
|
|
result.output_code = generate_verse(root);
|
|
break;
|
|
case TARGET_CSHARP:
|
|
result.output_code = generate_csharp(root);
|
|
break;
|
|
case TARGET_JAVASCRIPT:
|
|
result.output_code = generate_javascript(root);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return had_error ? ERR_COMPILATION_FAILED : OK;
|
|
}
|
|
|
|
void AeThexCompiler::compile_node(const AeThexParser::ASTNode *node) {
|
|
if (!node) return;
|
|
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_REALITY:
|
|
compile_reality(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_JOURNEY:
|
|
compile_journey(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_BEACON:
|
|
compile_beacon(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_IF:
|
|
compile_if(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_SYNC:
|
|
compile_sync(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_BINARY_OP:
|
|
compile_binary(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_UNARY_OP:
|
|
compile_unary(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_CALL:
|
|
compile_call(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_LITERAL:
|
|
compile_literal(node);
|
|
break;
|
|
case AeThexParser::ASTNode::NODE_IDENTIFIER:
|
|
compile_identifier(node);
|
|
break;
|
|
default:
|
|
compile_statement(node);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::compile_reality(const AeThexParser::ASTNode *node) {
|
|
result.reality_name = node->value;
|
|
|
|
// Extract platforms from attributes
|
|
if (node->attributes.has("platforms")) {
|
|
String platforms = node->attributes["platforms"];
|
|
Vector<String> parts = platforms.split(",");
|
|
for (const String &p : parts) {
|
|
result.supported_platforms.push_back(p.strip_edges());
|
|
}
|
|
}
|
|
|
|
// Compile children
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
compile_node(child);
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::compile_journey(const AeThexParser::ASTNode *node) {
|
|
// Create function chunk
|
|
String func_name = node->value;
|
|
result.functions[func_name] = Chunk();
|
|
Chunk *prev_chunk = current_chunk;
|
|
current_chunk = &result.functions[func_name];
|
|
|
|
begin_scope();
|
|
|
|
// Parameters are first children until we hit a non-identifier
|
|
int param_count = 0;
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_IDENTIFIER && param_count < 10) {
|
|
declare_local(child->value, false);
|
|
param_count++;
|
|
} else {
|
|
compile_node(child);
|
|
}
|
|
}
|
|
|
|
emit_return();
|
|
end_scope();
|
|
|
|
current_chunk = prev_chunk;
|
|
}
|
|
|
|
void AeThexCompiler::compile_beacon(const AeThexParser::ASTNode *node) {
|
|
result.beacons.push_back(node->value);
|
|
}
|
|
|
|
void AeThexCompiler::compile_statement(const AeThexParser::ASTNode *node) {
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_VARIABLE: {
|
|
bool is_const = node->attributes.has("const") && node->attributes["const"] == "true";
|
|
declare_local(node->value, is_const);
|
|
|
|
if (!node->children.is_empty()) {
|
|
compile_node(node->children[0]);
|
|
} else {
|
|
emit_byte(OP_PUSH_NULL);
|
|
}
|
|
emit_bytes(OP_STORE_LOCAL, resolve_local(node->value));
|
|
break;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_NOTIFY: {
|
|
if (!node->children.is_empty()) {
|
|
compile_node(node->children[0]);
|
|
}
|
|
emit_byte(OP_NOTIFY);
|
|
break;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_REVEAL: {
|
|
if (!node->children.is_empty()) {
|
|
compile_node(node->children[0]);
|
|
} else {
|
|
emit_byte(OP_PUSH_NULL);
|
|
}
|
|
emit_byte(OP_RETURN);
|
|
break;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_ASSIGNMENT: {
|
|
if (node->children.size() >= 2) {
|
|
compile_node(node->children[1]); // Value
|
|
|
|
if (node->children[0]->type == AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
int local = resolve_local(node->children[0]->value);
|
|
if (local >= 0) {
|
|
emit_bytes(OP_STORE_LOCAL, local);
|
|
} else {
|
|
int idx = current_chunk->add_string(node->children[0]->value);
|
|
emit_bytes(OP_STORE_GLOBAL, idx);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
compile_expression(node);
|
|
emit_byte(OP_POP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::compile_expression(const AeThexParser::ASTNode *node) {
|
|
compile_node(node);
|
|
}
|
|
|
|
void AeThexCompiler::compile_binary(const AeThexParser::ASTNode *node) {
|
|
if (node->children.size() < 2) return;
|
|
|
|
compile_node(node->children[0]);
|
|
compile_node(node->children[1]);
|
|
|
|
String op = node->value;
|
|
if (op == "+") emit_byte(OP_ADD);
|
|
else if (op == "-") emit_byte(OP_SUB);
|
|
else if (op == "*") emit_byte(OP_MUL);
|
|
else if (op == "/") emit_byte(OP_DIV);
|
|
else if (op == "%") emit_byte(OP_MOD);
|
|
else if (op == "==") emit_byte(OP_EQ);
|
|
else if (op == "!=") emit_byte(OP_NE);
|
|
else if (op == "<") emit_byte(OP_LT);
|
|
else if (op == "<=") emit_byte(OP_LE);
|
|
else if (op == ">") emit_byte(OP_GT);
|
|
else if (op == ">=") emit_byte(OP_GE);
|
|
else if (op == "and") emit_byte(OP_AND);
|
|
else if (op == "or") emit_byte(OP_OR);
|
|
}
|
|
|
|
void AeThexCompiler::compile_unary(const AeThexParser::ASTNode *node) {
|
|
if (node->children.is_empty()) return;
|
|
|
|
compile_node(node->children[0]);
|
|
|
|
if (node->value == "-") emit_byte(OP_NEG);
|
|
else if (node->value == "not") emit_byte(OP_NOT);
|
|
}
|
|
|
|
void AeThexCompiler::compile_call(const AeThexParser::ASTNode *node) {
|
|
if (node->children.is_empty()) return;
|
|
|
|
// First child is callee, rest are arguments
|
|
for (int i = 1; i < node->children.size(); i++) {
|
|
compile_node(node->children[i]);
|
|
}
|
|
|
|
compile_node(node->children[0]);
|
|
emit_bytes(OP_CALL, node->children.size() - 1);
|
|
}
|
|
|
|
void AeThexCompiler::compile_literal(const AeThexParser::ASTNode *node) {
|
|
String val = node->value;
|
|
|
|
if (val == "null") {
|
|
emit_byte(OP_PUSH_NULL);
|
|
} else if (val == "true") {
|
|
emit_bytes(OP_PUSH_BOOL, 1);
|
|
} else if (val == "false") {
|
|
emit_bytes(OP_PUSH_BOOL, 0);
|
|
} else if (node->attributes.has("type") && node->attributes["type"] == "number") {
|
|
if (val.contains(".")) {
|
|
emit_byte(OP_PUSH_FLOAT);
|
|
current_chunk->write_float(val.to_float());
|
|
} else {
|
|
emit_byte(OP_PUSH_INT);
|
|
current_chunk->write_int(val.to_int());
|
|
}
|
|
} else {
|
|
int idx = current_chunk->add_string(val);
|
|
emit_bytes(OP_PUSH_STRING, idx);
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::compile_identifier(const AeThexParser::ASTNode *node) {
|
|
int local = resolve_local(node->value);
|
|
if (local >= 0) {
|
|
emit_bytes(OP_LOAD_LOCAL, local);
|
|
} else {
|
|
int idx = current_chunk->add_string(node->value);
|
|
emit_bytes(OP_LOAD_GLOBAL, idx);
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::compile_if(const AeThexParser::ASTNode *node) {
|
|
if (node->children.size() < 2) return;
|
|
|
|
// Condition
|
|
compile_node(node->children[0]);
|
|
|
|
int then_jump = emit_jump(OP_JUMP_IF_NOT);
|
|
emit_byte(OP_POP);
|
|
|
|
// Then block
|
|
if (node->children[1]) {
|
|
for (const AeThexParser::ASTNode *stmt : node->children[1]->children) {
|
|
compile_node(stmt);
|
|
}
|
|
}
|
|
|
|
int else_jump = emit_jump(OP_JUMP);
|
|
patch_jump(then_jump);
|
|
emit_byte(OP_POP);
|
|
|
|
// Else block
|
|
if (node->children.size() > 2 && node->children[2]) {
|
|
for (const AeThexParser::ASTNode *stmt : node->children[2]->children) {
|
|
compile_node(stmt);
|
|
}
|
|
}
|
|
|
|
patch_jump(else_jump);
|
|
}
|
|
|
|
void AeThexCompiler::compile_sync(const AeThexParser::ASTNode *node) {
|
|
if (!node->children.is_empty()) {
|
|
compile_node(node->children[0]);
|
|
}
|
|
emit_byte(OP_SYNC);
|
|
}
|
|
|
|
void AeThexCompiler::emit_byte(uint8_t byte) {
|
|
current_chunk->write(byte);
|
|
}
|
|
|
|
void AeThexCompiler::emit_bytes(uint8_t b1, uint8_t b2) {
|
|
emit_byte(b1);
|
|
emit_byte(b2);
|
|
}
|
|
|
|
void AeThexCompiler::emit_return() {
|
|
emit_byte(OP_PUSH_NULL);
|
|
emit_byte(OP_RETURN);
|
|
}
|
|
|
|
int AeThexCompiler::emit_jump(Opcode op) {
|
|
emit_byte(op);
|
|
emit_byte(0xFF);
|
|
emit_byte(0xFF);
|
|
return current_chunk->code.size() - 2;
|
|
}
|
|
|
|
void AeThexCompiler::patch_jump(int offset) {
|
|
int jump = current_chunk->code.size() - offset - 2;
|
|
current_chunk->code.write[offset] = (jump >> 0) & 0xFF;
|
|
current_chunk->code.write[offset + 1] = (jump >> 8) & 0xFF;
|
|
}
|
|
|
|
void AeThexCompiler::emit_loop(int loop_start) {
|
|
emit_byte(OP_LOOP);
|
|
int offset = current_chunk->code.size() - loop_start + 2;
|
|
emit_byte((offset >> 0) & 0xFF);
|
|
emit_byte((offset >> 8) & 0xFF);
|
|
}
|
|
|
|
void AeThexCompiler::begin_scope() {
|
|
scope_depth++;
|
|
}
|
|
|
|
void AeThexCompiler::end_scope() {
|
|
scope_depth--;
|
|
while (!locals.is_empty() && locals[locals.size() - 1].depth > scope_depth) {
|
|
emit_byte(OP_POP);
|
|
locals.resize(locals.size() - 1);
|
|
}
|
|
}
|
|
|
|
void AeThexCompiler::declare_local(const String &name, bool is_const) {
|
|
Local local;
|
|
local.name = name;
|
|
local.depth = scope_depth;
|
|
local.is_const = is_const;
|
|
locals.push_back(local);
|
|
}
|
|
|
|
int AeThexCompiler::resolve_local(const String &name) {
|
|
for (int i = locals.size() - 1; i >= 0; i--) {
|
|
if (locals[i].name == name) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
String AeThexCompiler::indent_str(int level) const {
|
|
String s;
|
|
for (int i = 0; i < level; i++) {
|
|
s += " ";
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// ==========================================
|
|
// Luau (Roblox) Code Generation
|
|
// ==========================================
|
|
|
|
String AeThexCompiler::export_to_luau(const AeThexParser::ASTNode *root) {
|
|
return generate_luau(root);
|
|
}
|
|
|
|
String AeThexCompiler::generate_luau(const AeThexParser::ASTNode *root) {
|
|
String code = "-- Generated by AeThex Compiler\n";
|
|
code += "-- Target: Roblox Luau\n\n";
|
|
|
|
if (root && root->type == AeThexParser::ASTNode::NODE_REALITY) {
|
|
code += "local " + root->value + " = {}\n\n";
|
|
|
|
for (const AeThexParser::ASTNode *child : root->children) {
|
|
code += luau_node(child, 0);
|
|
}
|
|
|
|
code += "\nreturn " + root->value + "\n";
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
String AeThexCompiler::luau_node(const AeThexParser::ASTNode *node, int indent) {
|
|
if (!node) return "";
|
|
|
|
String ind = indent_str(indent);
|
|
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_JOURNEY: {
|
|
String code = ind + "function " + result.reality_name + "." + node->value + "(";
|
|
|
|
// Parameters
|
|
bool first = true;
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
if (!first) code += ", ";
|
|
code += child->value;
|
|
first = false;
|
|
}
|
|
}
|
|
code += ")\n";
|
|
|
|
// Body
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type != AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
code += luau_node(child, indent + 1);
|
|
}
|
|
}
|
|
|
|
code += ind + "end\n\n";
|
|
return code;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_BEACON: {
|
|
return ind + "-- Beacon (Signal): " + node->value + "\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_VARIABLE: {
|
|
String code = ind + "local " + node->value;
|
|
if (!node->children.is_empty()) {
|
|
code += " = " + luau_node(node->children[0], 0);
|
|
}
|
|
return code + "\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_NOTIFY: {
|
|
String arg = node->children.is_empty() ? "\"\"" : luau_node(node->children[0], 0);
|
|
return ind + "print(" + arg + ")\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_REVEAL: {
|
|
String val = node->children.is_empty() ? "nil" : luau_node(node->children[0], 0);
|
|
return ind + "return " + val + "\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IF: {
|
|
String code = ind + "if " + luau_node(node->children[0], 0) + " then\n";
|
|
if (node->children.size() > 1 && node->children[1]) {
|
|
for (const AeThexParser::ASTNode *stmt : node->children[1]->children) {
|
|
code += luau_node(stmt, indent + 1);
|
|
}
|
|
}
|
|
if (node->children.size() > 2 && node->children[2]) {
|
|
code += ind + "else\n";
|
|
for (const AeThexParser::ASTNode *stmt : node->children[2]->children) {
|
|
code += luau_node(stmt, indent + 1);
|
|
}
|
|
}
|
|
code += ind + "end\n";
|
|
return code;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_SYNC: {
|
|
String arg = node->children.is_empty() ? "nil" : luau_node(node->children[0], 0);
|
|
return ind + "-- sync: " + arg + "\n" + ind + "task.wait()\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_BINARY_OP: {
|
|
String left = node->children.size() > 0 ? luau_node(node->children[0], 0) : "";
|
|
String right = node->children.size() > 1 ? luau_node(node->children[1], 0) : "";
|
|
return "(" + left + " " + node->value + " " + right + ")";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_UNARY_OP: {
|
|
String operand = node->children.is_empty() ? "" : luau_node(node->children[0], 0);
|
|
return node->value + operand;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_CALL: {
|
|
String callee = node->children.is_empty() ? "" : luau_node(node->children[0], 0);
|
|
String args;
|
|
for (int i = 1; i < node->children.size(); i++) {
|
|
if (i > 1) args += ", ";
|
|
args += luau_node(node->children[i], 0);
|
|
}
|
|
return callee + "(" + args + ")";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_LITERAL: {
|
|
if (node->value == "null") return "nil";
|
|
if (node->attributes.has("type") && node->attributes["type"] == "string") {
|
|
return "\"" + node->value + "\"";
|
|
}
|
|
return node->value;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IDENTIFIER:
|
|
return node->value;
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// Verse (UEFN) Code Generation
|
|
// ==========================================
|
|
|
|
String AeThexCompiler::export_to_verse(const AeThexParser::ASTNode *root) {
|
|
return generate_verse(root);
|
|
}
|
|
|
|
String AeThexCompiler::generate_verse(const AeThexParser::ASTNode *root) {
|
|
String code = "# Generated by AeThex Compiler\n";
|
|
code += "# Target: UEFN Verse\n\n";
|
|
code += "using { /Fortnite.com/Devices }\n";
|
|
code += "using { /Verse.org/Simulation }\n\n";
|
|
|
|
if (root && root->type == AeThexParser::ASTNode::NODE_REALITY) {
|
|
code += root->value + " := class(creative_device):\n\n";
|
|
|
|
for (const AeThexParser::ASTNode *child : root->children) {
|
|
code += verse_node(child, 1);
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
String AeThexCompiler::verse_node(const AeThexParser::ASTNode *node, int indent) {
|
|
if (!node) return "";
|
|
|
|
String ind = indent_str(indent);
|
|
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_JOURNEY: {
|
|
String code = ind + node->value + "(";
|
|
|
|
// Parameters
|
|
bool first = true;
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
if (!first) code += ", ";
|
|
code += child->value + " : any";
|
|
first = false;
|
|
}
|
|
}
|
|
code += ")<suspends> : void =\n";
|
|
|
|
// Body
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type != AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
code += verse_node(child, indent + 1);
|
|
}
|
|
}
|
|
|
|
return code + "\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_VARIABLE: {
|
|
String is_var = (node->attributes.has("const") && node->attributes["const"] == "true") ? "" : "var ";
|
|
String code = ind + is_var + node->value + " : any";
|
|
if (!node->children.is_empty()) {
|
|
code += " = " + verse_node(node->children[0], 0);
|
|
}
|
|
return code + "\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_NOTIFY: {
|
|
String arg = node->children.is_empty() ? "\"\"" : verse_node(node->children[0], 0);
|
|
return ind + "Print(" + arg + ")\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_REVEAL: {
|
|
return ind + "# return\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_LITERAL: {
|
|
if (node->value == "null") return "false";
|
|
if (node->value == "true") return "true";
|
|
if (node->value == "false") return "false";
|
|
if (node->attributes.has("type") && node->attributes["type"] == "string") {
|
|
return "\"" + node->value + "\"";
|
|
}
|
|
return node->value;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IDENTIFIER:
|
|
return node->value;
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// C# (Unity) Code Generation
|
|
// ==========================================
|
|
|
|
String AeThexCompiler::export_to_csharp(const AeThexParser::ASTNode *root) {
|
|
return generate_csharp(root);
|
|
}
|
|
|
|
String AeThexCompiler::generate_csharp(const AeThexParser::ASTNode *root) {
|
|
String code = "// Generated by AeThex Compiler\n";
|
|
code += "// Target: Unity C#\n\n";
|
|
code += "using UnityEngine;\n";
|
|
code += "using System;\n\n";
|
|
|
|
if (root && root->type == AeThexParser::ASTNode::NODE_REALITY) {
|
|
code += "public class " + root->value + " : MonoBehaviour\n{\n";
|
|
|
|
for (const AeThexParser::ASTNode *child : root->children) {
|
|
code += csharp_node(child, 1);
|
|
}
|
|
|
|
code += "}\n";
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
String AeThexCompiler::csharp_node(const AeThexParser::ASTNode *node, int indent) {
|
|
if (!node) return "";
|
|
|
|
String ind = indent_str(indent);
|
|
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_JOURNEY: {
|
|
String code = ind + "public void " + node->value + "(";
|
|
|
|
// Parameters
|
|
bool first = true;
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
if (!first) code += ", ";
|
|
code += "object " + child->value;
|
|
first = false;
|
|
}
|
|
}
|
|
code += ")\n" + ind + "{\n";
|
|
|
|
// Body
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type != AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
code += csharp_node(child, indent + 1);
|
|
}
|
|
}
|
|
|
|
code += ind + "}\n\n";
|
|
return code;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_BEACON: {
|
|
return ind + "public event Action " + node->value + ";\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_VARIABLE: {
|
|
String decl = (node->attributes.has("const") && node->attributes["const"] == "true") ? "const " : "";
|
|
String code = ind + decl + "var " + node->value;
|
|
if (!node->children.is_empty()) {
|
|
code += " = " + csharp_node(node->children[0], 0);
|
|
}
|
|
return code + ";\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_NOTIFY: {
|
|
String arg = node->children.is_empty() ? "\"\"" : csharp_node(node->children[0], 0);
|
|
return ind + "Debug.Log(" + arg + ");\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_REVEAL: {
|
|
String val = node->children.is_empty() ? "" : csharp_node(node->children[0], 0);
|
|
return ind + "return" + (val.is_empty() ? "" : " " + val) + ";\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IF: {
|
|
String code = ind + "if (" + csharp_node(node->children[0], 0) + ")\n";
|
|
code += ind + "{\n";
|
|
if (node->children.size() > 1 && node->children[1]) {
|
|
for (const AeThexParser::ASTNode *stmt : node->children[1]->children) {
|
|
code += csharp_node(stmt, indent + 1);
|
|
}
|
|
}
|
|
code += ind + "}\n";
|
|
if (node->children.size() > 2 && node->children[2]) {
|
|
code += ind + "else\n" + ind + "{\n";
|
|
for (const AeThexParser::ASTNode *stmt : node->children[2]->children) {
|
|
code += csharp_node(stmt, indent + 1);
|
|
}
|
|
code += ind + "}\n";
|
|
}
|
|
return code;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_LITERAL: {
|
|
if (node->value == "null") return "null";
|
|
if (node->attributes.has("type") && node->attributes["type"] == "string") {
|
|
return "\"" + node->value + "\"";
|
|
}
|
|
return node->value;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IDENTIFIER:
|
|
return node->value;
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// JavaScript (Web) Code Generation
|
|
// ==========================================
|
|
|
|
String AeThexCompiler::export_to_javascript(const AeThexParser::ASTNode *root) {
|
|
return generate_javascript(root);
|
|
}
|
|
|
|
String AeThexCompiler::generate_javascript(const AeThexParser::ASTNode *root) {
|
|
String code = "// Generated by AeThex Compiler\n";
|
|
code += "// Target: JavaScript (Web)\n\n";
|
|
|
|
if (root && root->type == AeThexParser::ASTNode::NODE_REALITY) {
|
|
code += "const " + root->value + " = {\n";
|
|
|
|
bool first = true;
|
|
for (const AeThexParser::ASTNode *child : root->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_JOURNEY) {
|
|
if (!first) code += ",\n";
|
|
first = false;
|
|
code += js_node(child, 1);
|
|
}
|
|
}
|
|
|
|
code += "\n};\n\nexport default " + root->value + ";\n";
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
String AeThexCompiler::js_node(const AeThexParser::ASTNode *node, int indent) {
|
|
if (!node) return "";
|
|
|
|
String ind = indent_str(indent);
|
|
|
|
switch (node->type) {
|
|
case AeThexParser::ASTNode::NODE_JOURNEY: {
|
|
String code = ind + node->value + "(";
|
|
|
|
// Parameters
|
|
bool first = true;
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type == AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
if (!first) code += ", ";
|
|
code += child->value;
|
|
first = false;
|
|
}
|
|
}
|
|
code += ") {\n";
|
|
|
|
// Body
|
|
for (const AeThexParser::ASTNode *child : node->children) {
|
|
if (child->type != AeThexParser::ASTNode::NODE_IDENTIFIER) {
|
|
code += js_node(child, indent + 1);
|
|
}
|
|
}
|
|
|
|
code += ind + "}";
|
|
return code;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_VARIABLE: {
|
|
String decl = (node->attributes.has("const") && node->attributes["const"] == "true") ? "const" : "let";
|
|
String code = ind + decl + " " + node->value;
|
|
if (!node->children.is_empty()) {
|
|
code += " = " + js_node(node->children[0], 0);
|
|
}
|
|
return code + ";\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_NOTIFY: {
|
|
String arg = node->children.is_empty() ? "\"\"" : js_node(node->children[0], 0);
|
|
return ind + "console.log(" + arg + ");\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_REVEAL: {
|
|
String val = node->children.is_empty() ? "" : js_node(node->children[0], 0);
|
|
return ind + "return" + (val.is_empty() ? "" : " " + val) + ";\n";
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_LITERAL: {
|
|
if (node->value == "null") return "null";
|
|
if (node->attributes.has("type") && node->attributes["type"] == "string") {
|
|
return "\"" + node->value + "\"";
|
|
}
|
|
return node->value;
|
|
}
|
|
|
|
case AeThexParser::ASTNode::NODE_IDENTIFIER:
|
|
return node->value;
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
String AeThexCompiler::target_name(Target t) {
|
|
switch (t) {
|
|
case TARGET_BYTECODE: return "Bytecode";
|
|
case TARGET_LUAU: return "Roblox Luau";
|
|
case TARGET_VERSE: return "UEFN Verse";
|
|
case TARGET_CSHARP: return "Unity C#";
|
|
case TARGET_JAVASCRIPT: return "JavaScript";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
String AeThexCompiler::opcode_name(Opcode op) {
|
|
switch (op) {
|
|
case OP_PUSH_NULL: return "PUSH_NULL";
|
|
case OP_PUSH_BOOL: return "PUSH_BOOL";
|
|
case OP_PUSH_INT: return "PUSH_INT";
|
|
case OP_PUSH_FLOAT: return "PUSH_FLOAT";
|
|
case OP_PUSH_STRING: return "PUSH_STRING";
|
|
case OP_POP: return "POP";
|
|
case OP_DUP: return "DUP";
|
|
case OP_LOAD_LOCAL: return "LOAD_LOCAL";
|
|
case OP_STORE_LOCAL: return "STORE_LOCAL";
|
|
case OP_LOAD_GLOBAL: return "LOAD_GLOBAL";
|
|
case OP_STORE_GLOBAL: return "STORE_GLOBAL";
|
|
case OP_ADD: return "ADD";
|
|
case OP_SUB: return "SUB";
|
|
case OP_MUL: return "MUL";
|
|
case OP_DIV: return "DIV";
|
|
case OP_CALL: return "CALL";
|
|
case OP_RETURN: return "RETURN";
|
|
case OP_SYNC: return "SYNC";
|
|
case OP_BEACON_EMIT: return "BEACON_EMIT";
|
|
case OP_NOTIFY: return "NOTIFY";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|