Error when executing function

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Mimiko
Posts: 11
Joined: 09 Dec 2020, 10:37
Contact:

Error when executing function

21 Oct 2022, 09:31

I recently encountered an error in the following code when adding functionality to my compiler

This code will look strange because it is generated by the compiler

Code: Select all

; Generated by Coffee-AHK/0.0.49
global __rf_shell__ := Func("shell_6")
global __shell_module_2__ := (Func("shell_5")).Call()
global __shell_module_1__ := (Func("shell_3")).Call()
global __isFunction__ := __shell_module_1__
(Func("shell_1")).Call()
shell_1() {
  result := __rf_shell__.Call(__isFunction__).Call(__isFunction__)
  if !(result) {
    throw 1
  }
}
shell_2(__getType__, ipt) {
  __type__ := __rf_shell__.Call(__getType__).Call(ipt)
  if !(__type__ == "function") {
    return false
  }
  return true
}
shell_3() {
  __getType__ := __shell_module_2__
  return Func("shell_2").Bind(__getType__)
}
shell_4(ipt) {
  if ipt is Number
    return "number"
  if (IsFunc(ipt)) {
    return "function"
  }
  if (IsObject(ipt)) {
    if (ipt.Count() == ipt.Length()) {
      if !(ipt.Length() >= 0) {
        return "function"
      }
      return "array"
    }
    return "object"
  }
  return "string"
}
shell_5() {
  return Func("shell_4")
}
shell_6(__fn__) {
  if (IsFunc(__fn__)) {
    return __fn__
  }
  throw Exception("invalid function")
}
Calling `__rf_shell__.Call(__isFunction__)` within the `shell_1` function causes an error to be reported. But I don't know why this happens

Also, is there a more elegant way to provide the compiler with the ability to "determine if all functions exist when they are executed"?

I don't feel like my current injection method is a good choice
lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: Error when executing function

21 Oct 2022, 18:23

It would likely be much easier to make sense of your code if your compiler used more meaningful names for the generated functions, and you posted the code from which this was compiled.

Your code throws "invalid function" because IsFunc returns false. A BoundFunc is not a function that exists in the script; it is neither a "function name" nor a "function reference". In shell_4, we can see that you expect there to be some values for which IsFunc returns false but should still be considered "function"; but shell_6 doesn't take this into account.

I think that making the generated code more readable, using more meaningful names and removing some of the apparently unnecessary indirection would make it much easier to debug. It would also be more efficient.

All functions exist when they execute. A function that doesn't exist can't execute. :P

If what you want is for the call operator - which is presumably something like identifier(params) - to throw an exception if the value isn't a function, the simplest and most direct approach would be to compile the call operator to something like __call__(identifier, params), and give it whatever behaviour you want. There's no need for __call__ itself to be assigned to a variable or checked in this manner.

The same goes for any other operators that need altered behaviour or that don't exist in AutoHotkey.

A better approach would be to rely on polymorphism; i.e. anything that can be called has a Call method, and everything else has a Call method which throws. Don't directly expose the built-in objects to the code, but wrap them in a class that implements the behaviour you want. For primitive values, the Call method can be defined for the default base object. There is no need to check whether a function is a function before calling it, if calling any other value would produce an "invalid function" error.

AutoHotkey v2 would be a more suitable target for this, for various reasons. It has a more consistent type system, including a function to query the proper type (class) name. All values with a Call method can be called, and attempting to call any other value already throws an exception. Rather than checking types, you can (and should) check for properties or methods; e.g. HasMethod(value,, 2) is true if value can be called with 2 parameters (the method name "Call" is implied). Built-in objects work the same as user-defined objects; their properties can be queried and extended just the same.
Mimiko
Posts: 11
Joined: 09 Dec 2020, 10:37
Contact:

Re: Error when executing function

22 Oct 2022, 00:46

Thank you for your reply, it helped me solve this problem

I don't know much about ahk itself and didn't recognize the problem that `IsFunc` can't determine the BoundFunc after being passed multiple times

In terms of code readability, which looks like it should be harder to improve at this point, the source for this code is as follows

Code: Select all

# @ts-check

import $isFunction from '../../source/module/isFunction'

do ->

  result = $isFunction $isFunction
  unless result then throw 1
In order to implement functions such as anonymous functions or modules in ahkv1, a lot of intermediate functions have to be generated. I don't have a better idea how to name these functions at the moment

The migration to ahkv2 is simply a matter of sunk costs. I will try to migrate it to a newer version in the future when I have enough effort and time
lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: Error when executing function

22 Oct 2022, 21:13

Mimiko wrote:
22 Oct 2022, 00:46
I don't know much about ahk itself and didn't recognize the problem that `IsFunc` can't determine the BoundFunc after being passed multiple times
I don't see what "being passed multiple times" means or how it relates. Any BoundFunc just doesn't meet the conditions that IsFunc is looking for.

If you are implementing a compiler, you can implement your own type system. Functions defined within the source language do not need to correspond to functions in AutoHotkey. A function can be its own kind of object, with methods and properties different to what AutoHotkey provides. A module or function could even compile to an AutoHotkey class.

AutoHotkey has the capability for any object to be callable. Assuming your source language isn't more restrictive, you should not be checking whether a value is a function, but whether it can be called. That's a lot easier in v2, but if you implement your own type system, you can implement all "built-in" objects in a way that allows you to determine what methods they have at runtime (such as Call). For example, Object.ahk implemented something like the current v2 type system in a previous version of v2. v1 has more limitations, but they are much less relevant if you are implementing your own compiler or preparser, and even less relevant if you aren't aiming to be compatible with AutoHotkey code written by others.
In order to implement functions such as anonymous functions or modules in ahkv1, a lot of intermediate functions have to be generated.
Most compilers implement inlining and other optimizations. For instance, shell_5() is more efficient than (Func("shell_5")).Call(); the only potential benefit of the latter is that shell_5 can be defined or not, without preventing the script from executing. But these are compiler-generated functions, and I assume a call to shell_5 would never be generated without a definition of shell_5. Any function that just returns a single expression and has no parameters or local variables would be very trivial to inline; for instance, any instance of Func("shell_5").Call() can be replaced with Func("shell_4"). (I don't see the reason for generating a function in the first place.)
I don't have a better idea how to name these functions at the moment
shell_4 and shell_6, at least, appear to correspond to actual named functions, so naming them would be a start. The function names aren't just seen when reading the generated code, but also at runtime if you use various debugging methods (ListVars, ListLines, interactive debugging with DBGp, A_ThisFunc, Exception.What).
Mimiko
Posts: 11
Joined: 09 Dec 2020, 10:37
Contact:

Re: Error when executing function

25 Oct 2022, 22:31

This is due to my lack of understanding of ahk. I took it for granted that BoundFunc is also a Func, so it should be able to be identified by IsFunc, which led to this problem.

As for the type system, I actually still use mainly the ahk system, because my source language, being a dynamic language, is not too type-sensitive either. It's only because my own projects are getting bigger and bigger, and it's easy to get typo errors without static type support, that's why I introduced typescript type checking in a recent update. It works very well in most cases, even beyond my own expectations.

As for "shell_5" and "shell_4", that's because I added modules. shell_5 is a module, which is then implemented as a function within ahk. In this module, a function is defined and exported, and the function itself is pulled out to the outer layer, so that only that export is left inside the module. Of course, from an optimization point of view, it really should be optimized as a single function, but my compiler doesn't have the power to do that at the moment.

As for the function naming, it has to do with the source language I'm using. In this language, there is actually no concept of named functions, all functions are created as anonymous functions and then assigned to different variables. So it is much easier to use a similar approach when translating from its ast to ahk. BoundFunc was used extensively because I needed to pass functions as arguments, and ahk's function arguments cannot accept Func (doubtful here, as this piece of logic was done a dozen months ago, and I've forgotten the details now).

My compiler project was initially a practice project I created to learn typescript, and I was unfamiliar with both ahk and typescript when I completed most of its logic, so there were inevitably all sorts of problems with it. Thanks again for the additional clarification on my question, and have a great day.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: AlFlo, Google [Bot] and 67 guests