Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Added YueScript support #22

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/language_gdnative.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,51 @@ static godot_pluginscript_language_desc lps_language_desc = {
},
};

static godot_pluginscript_language_desc lps_yue_language_desc = {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a specific lps_yue_language_desc for custom names, extension and reserved keywords.
Turns out you can just register multiple language in one GDNative binding! Super cool :)

Since all of the functions are the same, it means changes / improvements on the Lua side will automatically be available for YueScript.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out you can just register multiple language in one GDNative binding!

Yeah! Not only that, if we had any NativeScript classes or anything else we could add in the same library as well =]

.name = "YueScript",
.type = "YueScript",
.extension = "yue",
.recognized_extensions = (const char *[]){ "yue", NULL },
.init = &lps_language_init,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Godot will call this init function twice, once for each language, so that 2 lua_States will be created and fully initialized.
In the end, only the second one will be used, since there is the lps_L global variable that is used by almost all callbacks.
There will probably be no leaks, since the first one will still get closed in lps_language_finish, but it's better not having this duplicated state.

.finish = &lps_language_finish,
.reserved_words = (const char *[]){
// Lua keywords
"and", "break", "do", "else", "elseif",
"false", "for", "if", "in",
"local", "nil", "not", "or", "return",
"then", "true", "until", "while",
// Other remarkable identifiers
"self", "_G", "_VERSION",
// YueScript specific keywords
"@", "@@", "continue", "class", "extends",
gilzoide marked this conversation as resolved.
Show resolved Hide resolved
"export", "switch", "super", "unless",
"when", "with",
#if LUA_VERSION_NUM >= 502
"_ENV",
#endif
Comment on lines +373 to +375
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is _ENV a thing in Yuescript? If not, this could be removed.

"bool", "int", "float",
NULL
},
.comment_delimiters = (const char *[]){ "--", NULL },
.string_delimiters = (const char *[]){ "' '", "\" \"", "[[ ]]", "[=[ ]=]", NULL },
.has_named_classes = false,
.supports_builtin_mode = false,
.add_global_constant = &lps_language_add_global_constant,

.script_desc = {
.init = &lps_script_init,
.finish = &lps_script_finish,
.instance_desc = {
.init = &lps_instance_init,
.finish = &lps_instance_finish,
.set_prop = &lps_instance_set_prop,
.get_prop = &lps_instance_get_prop,
.call_method = &lps_instance_call_method,
.notification = &lps_instance_notification,
},
},
};

#define PREFIX_SYMBOL(s) lps_ ## s

// GDNative functions
Expand All @@ -365,6 +410,7 @@ GDN_EXPORT void PREFIX_SYMBOL(gdnative_init)(godot_gdnative_init_options *option
in_editor = options->in_editor;
if (in_editor) {
lps_register_in_editor_callbacks(&lps_language_desc);
lps_register_in_editor_callbacks(&lps_yue_language_desc);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The editor callbacks implemented ("validate", "get_template_source_code" and "make_function") should probably have a specific implementation for Yuescript, since the language is quite different.

}

#ifdef LUAJIT_DYNAMICALLY_LINKED
Expand All @@ -386,6 +432,7 @@ GDN_EXPORT void PREFIX_SYMBOL(gdnative_init)(godot_gdnative_init_options *option
#endif

hgdn_pluginscript_api->godot_pluginscript_register_language(&lps_language_desc);
hgdn_pluginscript_api->godot_pluginscript_register_language(&lps_yue_language_desc);
gilzoide marked this conversation as resolved.
Show resolved Hide resolved
}

GDN_EXPORT void PREFIX_SYMBOL(gdnative_terminate)(godot_gdnative_terminate_options *options) {
Expand Down
25 changes: 24 additions & 1 deletion src/pluginscript_callbacks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-- IN THE SOFTWARE.

local lps_scripts = {}
local lps_instances = setmetatable({}, weak_k)

Expand Down Expand Up @@ -97,6 +98,14 @@ pluginscript_callbacks.script_init = wrap_callback(function(manifest, path, sour
manifest = ffi_cast('godot_pluginscript_script_manifest *', manifest)
path = tostring(ffi_cast('godot_string *', path))
source = tostring(ffi_cast('godot_string *', source))
local is_yue = path:sub(-4) == '.yue'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to fix tabbing on this as well (or most of the code rather).
I basically just check if the file that's being loaded ends with .yue, i'll use this property later

Copy link
Owner

@gilzoide gilzoide Apr 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding specific extra code for Yuescript in script_init, maybe you could create a new callback yuescript_init that does the translation, that then calls script_init for going through the actual import process. This would need a new C function hooked in the Yuescript PluginScript, but this shouldn't be a problem. Also the source and possibly path parameters could need to support both Lua strings or godot_strings. Maybe always receive Lua strings and create a lua_script_init for forwarding the godot_string already transformed, idk.


if is_yue then
local yue = require('yue')
local codes, err, globals = yue.to_lua(source)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the file is a .yue file it loads the YueScript shared library (which we can compile for our user or ask our user to obtain themselves).
It then compiles the source to be Lua code. The rest of the code should be compatible from this point

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd better compile and/or distribute the Yuescript shared library to end users, since it will be needed in runtime for the game/app. If the code would be compiled ahead-of-time as part of the build process, for example, than it would be more ok (although still somewhat annoying) to ask devs for obtaining it by themselves.

source = codes
end

err = ffi_cast('godot_error *', err)

lps_callstack:push('script_load', '@', string_quote(path))
Expand All @@ -114,14 +123,28 @@ pluginscript_callbacks.script_init = wrap_callback(function(manifest, path, sour
return
end
lps_coroutine_pool:release(co)

if type(script) ~= 'table' then
api.godot_print_error('Script must return a table', path, path, -1)
return
end

if is_yue then
if script.__base ~= nil then
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we detect a .yue file, we check if it exports a YueScript class instead of a table.
When it does we change the script to script.__base so it can detect the methods and variables of the class.

Copy link
Owner

@gilzoide gilzoide Apr 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite like these if is_yue branches, I think it's better to separate the code that parses Lua code and Yuescript code and get the script definition table. The import code below could still be the same for both languages, extracted to a new function that receives the input table and script manifest to fill.
Yuescript would then pass script.__base to it and Lua would just forward its script.

script = script.__base
end
end

local known_properties = {}
for k, v in pairs(script) do
if k == 'class_name' then
if is_yue and (
k == '__class' or
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a simple ignore for the values of __class, __index and __base. Let me know if there is a better way to go about this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe ignoring anything that starts with __, even for Lua, makes sense? They are usually metatable stuff and is possibly not important for Godot. We'd need to warn users about this behavior, of course, but seems ok to me.

k == '__index' or
k == '__base'
)
then

elseif k == 'class_name' then
manifest.name = ffi_gc(StringName(v), nil)
elseif k == 'is_tool' then
manifest.is_tool = bool(v)
Expand Down
3 changes: 3 additions & 0 deletions src/pluginscript_class_metadata.lua
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ function export(metadata)
return prop
end

-- Another alias for `export` since it's a reserved keyword in
-- YueScript
exp = export
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a small alias for export since it's a reserved keyword for YueScript. There might be a better name for this alias, but couldn't find one for now 😅

I could also add an macro for this in YueScript so I can just use the property method instead.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exp sounds more like an exponential function of math libraries. Maybe exported or exported_property is a better name. Idk, exp just doesn't sound like a good name to me.


local Signal = {}

Expand Down