AeThex-Engine-Core/engine/modules/aethex_lang/aethex_script.cpp
2026-02-24 01:55:30 -07:00

800 lines
21 KiB
C++

/**************************************************************************/
/* aethex_script.cpp */
/**************************************************************************/
/* This file is part of: */
/* AETHEX ENGINE */
/* https://aethex.foundation */
/**************************************************************************/
/* Copyright (c) 2026-present AeThex Labs. */
/* Based on Godot Engine, MIT License. */
/**************************************************************************/
#include "aethex_script.h"
#include "aethex_tokenizer.h"
#include "aethex_parser.h"
#include "aethex_compiler.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/math/math_defs.h"
// ============================================================================
// AeThexScriptLanguage
// ============================================================================
AeThexScriptLanguage *AeThexScriptLanguage::singleton = nullptr;
AeThexScriptLanguage::AeThexScriptLanguage() {
ERR_FAIL_COND(singleton);
singleton = this;
}
AeThexScriptLanguage::~AeThexScriptLanguage() {
singleton = nullptr;
}
void AeThexScriptLanguage::init() {
// Register built-in constants
global_constants["PI"] = Math::PI;
global_constants["TAU"] = Math::TAU;
global_constants["INF"] = INFINITY;
global_constants["NAN"] = NAN;
print_line("AeThex Language initialized - Write once, deploy everywhere!");
}
void AeThexScriptLanguage::finish() {
global_constants.clear();
}
Vector<String> AeThexScriptLanguage::get_reserved_words() const {
Vector<String> words;
// AeThex keywords
static const char *keywords[] = {
// Core constructs
"reality", // Module/namespace declaration
"journey", // Cross-platform function
"portal", // Import/export
"beacon", // Event/signal
"artifact", // Class/object
"essence", // Interface/trait
"chronicle", // Enum
// Control flow
"when", // If/condition
"otherwise", // Else
"traverse", // For loop
"while", // While loop
"break",
"continue",
"return",
"yield",
// Data
"let", // Variable
"const", // Constant
"mut", // Mutable
"new", // Instance creation
// Platform-specific
"platform", // Platform declaration
"sync", // Cross-platform sync
"async", // Async operation
"await", // Await async
// Actions
"notify", // Output/print
"reveal", // Return/expose
"summon", // Create/spawn
"banish", // Destroy/remove
// Compliance (built-in)
"Passport", // Auth identity
"Shield", // Data protection
"Consent", // COPPA/GDPR
// Literals
"true",
"false",
"null",
"self",
"super",
nullptr
};
const char **w = keywords;
while (*w) {
words.push_back(*w);
w++;
}
return words;
}
bool AeThexScriptLanguage::is_control_flow_keyword(const String &p_keyword) const {
return p_keyword == "when" || p_keyword == "otherwise" || p_keyword == "traverse" ||
p_keyword == "while" || p_keyword == "break" || p_keyword == "continue" ||
p_keyword == "return" || p_keyword == "yield";
}
Vector<String> AeThexScriptLanguage::get_comment_delimiters() const {
Vector<String> delimiters;
delimiters.push_back("# "); // Single line comment
delimiters.push_back("/* */"); // Block comment
return delimiters;
}
Vector<String> AeThexScriptLanguage::get_doc_comment_delimiters() const {
Vector<String> delimiters;
delimiters.push_back("## "); // Doc comment
return delimiters;
}
Vector<String> AeThexScriptLanguage::get_string_delimiters() const {
Vector<String> delimiters;
delimiters.push_back("\" \"");
delimiters.push_back("' '");
delimiters.push_back("` `"); // Template strings
return delimiters;
}
Ref<Script> AeThexScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
Ref<AeThexScript> script;
script.instantiate();
String template_code = p_template;
if (template_code.is_empty()) {
template_code = R"(# AeThex Script
# Write once, deploy to Roblox, UEFN, Unity, and Web
reality %CLASS% {
platforms: [roblox, uefn, web]
extends: %BASE%
}
journey _ready() {
platform: all
notify "Hello from AeThex!"
}
journey _process(delta) {
platform: all
# Your game logic here
}
)";
}
template_code = template_code.replace("%CLASS%", p_class_name);
template_code = template_code.replace("%BASE%", p_base_class_name);
script->set_source_code(template_code);
return script;
}
Vector<ScriptLanguage::ScriptTemplate> AeThexScriptLanguage::get_built_in_templates(const StringName &p_object) {
Vector<ScriptTemplate> templates;
// Basic template
ScriptTemplate basic;
basic.inherit = "Node";
basic.name = "AeThex Basic";
basic.description = "Basic AeThex script with cross-platform support";
basic.content = R"(# %CLASS_NAME%
reality %CLASS_NAME% {
platforms: [roblox, uefn, web]
extends: %BASE_CLASS_NAME%
}
journey _ready() {
platform: all
notify "%CLASS_NAME% ready!"
}
)";
templates.push_back(basic);
// Player controller
ScriptTemplate player;
player.inherit = "CharacterBody3D";
player.name = "AeThex Player Controller";
player.description = "Cross-platform player movement";
player.content = R"(# %CLASS_NAME%
# Cross-platform player controller
reality %CLASS_NAME% {
platforms: [roblox, uefn, web]
extends: %BASE_CLASS_NAME%
}
let speed = 5.0
let jump_velocity = 4.5
let gravity = 9.8
journey _process(delta) {
platform: all
let velocity = self.velocity
# Apply gravity
when not self.is_on_floor() {
velocity.y = velocity.y - gravity * delta
}
# Handle jump
when Input.is_action_just_pressed("jump") and self.is_on_floor() {
velocity.y = jump_velocity
}
# Get input direction
let input_dir = Input.get_vector("left", "right", "forward", "backward")
let direction = (self.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
when direction {
velocity.x = direction.x * speed
velocity.z = direction.z * speed
} otherwise {
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
}
self.velocity = velocity
self.move_and_slide()
}
)";
templates.push_back(player);
// Multiplayer sync
ScriptTemplate multiplayer;
multiplayer.inherit = "Node";
multiplayer.name = "AeThex Multiplayer";
multiplayer.description = "Cross-platform multiplayer sync";
multiplayer.content = R"(# %CLASS_NAME%
# Cross-platform multiplayer with automatic sync
reality %CLASS_NAME% {
platforms: [roblox, uefn, web]
extends: %BASE_CLASS_NAME%
}
let player_data = {}
journey OnPlayerJoin(player) {
platform: all
let passport = new Passport(player.id)
when passport.verify() {
player_data[player.id] = {
"name": player.name,
"score": 0,
"inventory": []
}
sync player_data[player.id] across [roblox, uefn, web]
notify player.name + " joined the game!"
}
}
journey UpdateScore(player_id, points) {
platform: all
when player_data.has(player_id) {
player_data[player_id].score += points
sync player_data[player_id].score across all
}
}
beacon ScoreChanged(player_id, new_score)
)";
templates.push_back(multiplayer);
return templates;
}
bool AeThexScriptLanguage::validate(const String &p_script, const String &p_path,
List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors,
List<ScriptLanguage::Warning> *r_warnings, HashSet<int> *r_safe_lines) const {
// TODO: Implement full validation with AeThexParser
// For now, basic syntax checking
if (p_script.is_empty()) {
return true;
}
// Check for required reality block
if (!p_script.contains("reality ")) {
if (r_errors) {
ScriptError err;
err.line = 1;
err.column = 1;
err.message = "AeThex scripts must have a 'reality' block declaration";
r_errors->push_back(err);
}
return false;
}
return true;
}
int AeThexScriptLanguage::find_function(const String &p_function, const String &p_code) const {
// Find function in code - search for "journey function_name"
String search = "journey " + p_function;
int pos = p_code.find(search);
if (pos == -1) {
return -1;
}
// Count newlines to get line number
int line = 1;
for (int i = 0; i < pos; i++) {
if (p_code[i] == '\n') {
line++;
}
}
return line;
}
String AeThexScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const {
String args_str;
for (int i = 0; i < p_args.size(); i++) {
if (i > 0) {
args_str += ", ";
}
args_str += p_args[i];
}
return "journey " + p_name + "(" + args_str + ") {\n platform: all\n # TODO: Implement\n}\n";
}
String AeThexScriptLanguage::debug_get_error() const {
return String();
}
int AeThexScriptLanguage::debug_get_stack_level_count() const {
return 0;
}
int AeThexScriptLanguage::debug_get_stack_level_line(int p_level) const {
return 0;
}
String AeThexScriptLanguage::debug_get_stack_level_function(int p_level) const {
return String();
}
String AeThexScriptLanguage::debug_get_stack_level_source(int p_level) const {
return String();
}
void AeThexScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
}
void AeThexScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
}
void AeThexScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
}
String AeThexScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
return String();
}
void AeThexScriptLanguage::frame() {
// Called every frame
}
void AeThexScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
global_constants[p_variable] = p_value;
}
void AeThexScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("aethex");
}
bool AeThexScriptLanguage::handles_global_class_type(const String &p_type) const {
return p_type == "AeThexScript";
}
String AeThexScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
if (!p_path.ends_with(".aethex")) {
return String();
}
// Parse the file to extract class name from reality block
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
return String();
}
String source = f->get_as_text();
// Simple regex-like extraction of reality ClassName
int reality_pos = source.find("reality ");
if (reality_pos != -1) {
int name_start = reality_pos + 8;
int name_end = source.find_char(' ', name_start);
if (name_end == -1) {
name_end = source.find_char('{', name_start);
}
if (name_end != -1) {
return source.substr(name_start, name_end - name_start).strip_edges();
}
}
return String();
}
String AeThexScriptLanguage::export_to_target(const String &p_source, ExportTarget p_target) const {
// Use the AeThex compiler to cross-compile
// This integrates with the standalone AeThex-Lang compiler
// Parse the source first
AeThexTokenizer tokenizer;
tokenizer.tokenize(p_source);
AeThexParser parser;
parser.parse(tokenizer.get_tokens());
if (!parser.get_errors().is_empty()) {
return "// Compilation error: " + parser.get_errors()[0].message;
}
const AeThexParser::ASTNode *root = parser.get_root();
AeThexCompiler compiler;
switch (p_target) {
case EXPORT_ROBLOX:
return compiler.export_to_luau(root);
case EXPORT_UEFN:
return compiler.export_to_verse(root);
case EXPORT_UNITY:
return compiler.export_to_csharp(root);
case EXPORT_WEB:
return compiler.export_to_javascript(root);
case EXPORT_ENGINE:
default:
return p_source; // Native execution
}
}
// ============================================================================
// AeThexScript
// ============================================================================
void AeThexScript::_bind_methods() {
ClassDB::bind_method(D_METHOD("export_to_lua"), &AeThexScript::export_to_lua);
ClassDB::bind_method(D_METHOD("export_to_verse"), &AeThexScript::export_to_verse);
ClassDB::bind_method(D_METHOD("export_to_csharp"), &AeThexScript::export_to_csharp);
ClassDB::bind_method(D_METHOD("export_to_javascript"), &AeThexScript::export_to_javascript);
}
AeThexScript::AeThexScript() {
}
AeThexScript::~AeThexScript() {
}
bool AeThexScript::can_instantiate() const {
return valid;
}
Ref<Script> AeThexScript::get_base_script() const {
return Ref<Script>();
}
StringName AeThexScript::get_global_name() const {
return StringName();
}
bool AeThexScript::inherits_script(const Ref<Script> &p_script) const {
return false;
}
StringName AeThexScript::get_instance_base_type() const {
return StringName("Node");
}
ScriptInstance *AeThexScript::instance_create(Object *p_this) {
AeThexScriptInstance *instance = memnew(AeThexScriptInstance);
instance->set_owner(p_this);
instance->set_script(Ref<AeThexScript>(this));
return instance;
}
PlaceHolderScriptInstance *AeThexScript::placeholder_instance_create(Object *p_this) {
return nullptr;
}
bool AeThexScript::instance_has(const Object *p_this) const {
return false;
}
bool AeThexScript::has_source_code() const {
return !source_code.is_empty();
}
String AeThexScript::get_source_code() const {
return source_code;
}
void AeThexScript::set_source_code(const String &p_code) {
source_code = p_code;
}
Error AeThexScript::reload(bool p_keep_state) {
// Parse and compile the source
valid = false;
functions.clear();
constants.clear();
target_platforms.clear();
if (source_code.is_empty()) {
return OK;
}
// TODO: Full parsing with AeThexParser
// For now, basic validation
// Extract platforms from reality block
int platforms_pos = source_code.find("platforms:");
if (platforms_pos != -1) {
int start = source_code.find("[", platforms_pos);
int end = source_code.find("]", start);
if (start != -1 && end != -1) {
String platforms_str = source_code.substr(start + 1, end - start - 1);
Vector<String> platforms = platforms_str.split(",");
for (const String &p : platforms) {
target_platforms.insert(p.strip_edges());
}
}
}
valid = true;
return OK;
}
#ifdef TOOLS_ENABLED
Vector<DocData::ClassDoc> AeThexScript::get_documentation() const {
return Vector<DocData::ClassDoc>();
}
String AeThexScript::get_class_icon_path() const {
return "res://addons/aethex/icons/aethex_script.svg";
}
PropertyInfo AeThexScript::get_class_category() const {
return PropertyInfo(Variant::STRING, "AeThex");
}
#endif
bool AeThexScript::has_method(const StringName &p_method) const {
for (const CompiledFunction &f : functions) {
if (f.name == p_method) {
return true;
}
}
return false;
}
MethodInfo AeThexScript::get_method_info(const StringName &p_method) const {
for (const CompiledFunction &f : functions) {
if (f.name == p_method) {
MethodInfo mi;
mi.name = f.name;
for (const String &param : f.parameters) {
mi.arguments.push_back(PropertyInfo(Variant::NIL, param));
}
return mi;
}
}
return MethodInfo();
}
ScriptLanguage *AeThexScript::get_language() const {
return AeThexScriptLanguage::get_singleton();
}
const Variant AeThexScript::get_rpc_config() const {
return Dictionary();
}
#ifdef TOOLS_ENABLED
StringName AeThexScript::get_doc_class_name() const {
return StringName();
}
#endif
bool AeThexScript::has_script_signal(const StringName &p_signal) const {
return false;
}
void AeThexScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
}
bool AeThexScript::get_property_default_value(const StringName &p_property, Variant &r_value) const {
return false;
}
void AeThexScript::update_exports() {
}
void AeThexScript::get_script_method_list(List<MethodInfo> *p_list) const {
for (const CompiledFunction &f : functions) {
MethodInfo mi;
mi.name = f.name;
p_list->push_back(mi);
}
}
void AeThexScript::get_script_property_list(List<PropertyInfo> *p_list) const {
}
int AeThexScript::get_member_line(const StringName &p_member) const {
return -1;
}
void AeThexScript::get_constants(HashMap<StringName, Variant> *p_constants) {
for (const KeyValue<StringName, Variant> &E : constants) {
p_constants->insert(E.key, E.value);
}
}
void AeThexScript::get_members(HashSet<StringName> *p_members) {
}
// Cross-platform export methods
String AeThexScript::export_to_lua() const {
return AeThexScriptLanguage::get_singleton()->export_to_target(source_code, AeThexScriptLanguage::EXPORT_ROBLOX);
}
String AeThexScript::export_to_verse() const {
return AeThexScriptLanguage::get_singleton()->export_to_target(source_code, AeThexScriptLanguage::EXPORT_UEFN);
}
String AeThexScript::export_to_csharp() const {
return AeThexScriptLanguage::get_singleton()->export_to_target(source_code, AeThexScriptLanguage::EXPORT_UNITY);
}
String AeThexScript::export_to_javascript() const {
return AeThexScriptLanguage::get_singleton()->export_to_target(source_code, AeThexScriptLanguage::EXPORT_WEB);
}
// ============================================================================
// AeThexScriptInstance
// ============================================================================
AeThexScriptInstance::AeThexScriptInstance() {
}
AeThexScriptInstance::~AeThexScriptInstance() {
}
bool AeThexScriptInstance::set(const StringName &p_name, const Variant &p_value) {
members[p_name] = p_value;
return true;
}
bool AeThexScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (members.has(p_name)) {
r_ret = members[p_name];
return true;
}
return false;
}
void AeThexScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
}
Variant::Type AeThexScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
return Variant::NIL;
}
void AeThexScriptInstance::validate_property(PropertyInfo &p_property) const {
}
bool AeThexScriptInstance::property_can_revert(const StringName &p_name) const {
return false;
}
bool AeThexScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
return false;
}
void AeThexScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
if (script.is_valid()) {
script->get_script_method_list(p_list);
}
}
bool AeThexScriptInstance::has_method(const StringName &p_method) const {
if (script.is_valid()) {
return script->has_method(p_method);
}
return false;
}
Variant AeThexScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
// TODO: Implement AeThex VM execution
r_error.error = Callable::CallError::CALL_OK;
return Variant();
}
void AeThexScriptInstance::notification(int p_notification, bool p_reversed) {
}
Ref<Script> AeThexScriptInstance::get_script() const {
return script;
}
ScriptLanguage *AeThexScriptInstance::get_language() {
return AeThexScriptLanguage::get_singleton();
}
// ============================================================================
// Resource Format Loader/Saver
// ============================================================================
Ref<Resource> ResourceFormatLoaderAeThexScript::load(const String &p_path, const String &p_original_path,
Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
Ref<AeThexScript> script;
script.instantiate();
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
}
return Ref<Resource>();
}
String source = f->get_as_text();
script->set_source_code(source);
script->reload();
if (r_error) {
*r_error = OK;
}
return script;
}
void ResourceFormatLoaderAeThexScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("aethex");
}
bool ResourceFormatLoaderAeThexScript::handles_type(const String &p_type) const {
return p_type == "Script" || p_type == "AeThexScript";
}
String ResourceFormatLoaderAeThexScript::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "aethex") {
return "AeThexScript";
}
return "";
}
Error ResourceFormatSaverAeThexScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
Ref<AeThexScript> script = p_resource;
if (script.is_null()) {
return ERR_INVALID_PARAMETER;
}
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
if (f.is_null()) {
return ERR_CANT_OPEN;
}
f->store_string(script->get_source_code());
return OK;
}
void ResourceFormatSaverAeThexScript::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
if (Object::cast_to<AeThexScript>(*p_resource)) {
p_extensions->push_back("aethex");
}
}
bool ResourceFormatSaverAeThexScript::recognize(const Ref<Resource> &p_resource) const {
return Object::cast_to<AeThexScript>(*p_resource) != nullptr;
}