Einhugur LuaScript Tables

I’m exploring a plugin system and experimenting with Einhugur’s LuaScript plugin. I’m trying to make a function to make HTTP requests in Lua. The function will get passed to Xojo, which will use URLConnection to perform the actual work.

So far, my very simple Lua script looks like:

response = httpRequest('GET', 'https://api.url.com/', {Authentication='password'})
print(response)

The challenge I’m running into is iterating over the table in the third parameter. My context object’s IsTable(3) returns true. But the only examples I can find for iterating a table are for a table stored in a global named variable. How do I do this with a passed parameter?

My Xojo code looks like this so far:

Var Context As LuaScriptContext = New LuaScriptContext(ContextPointer)
If Context.ParameterCount < 2 Then
  Context.SetError("httpRequest needs at least requestMethod and url parameters.")
  Return 0
End If

Var RequestMethod As String = Context.GetString(1, False)
Var Url As String = Context.GetString(2, False)

Var Conn As New URLConnection

If Context.ParameterCount > 2 And Context.IsTable(3) Then
  // Headers included
  // What now?
End If

Try
  Var Response As String = Conn.SendSync(RequestMethod, Url)
  Context.Push(Response)
Catch Err As RuntimeException
  Context.SetError(Err.Message)
End Try

Return 1

On a side note, JavaScript was my first choice, especially since I already own the MBS plugins. But the included DukTape javascript engine doesn’t support any of the modern features my users will expect, and JSContextMBS has no browser context, so that’s also missing important things like the fetch function. Python is not self-contained and package management would be an issue. PHPMBS is deprecated, and if I recall correctly, wasn’t self-contained either. Oh and XojoScript is effectively useless due to the small userbase, and no decent IDE. I’m open to other ideas, but I’m struggling to come up with others. So Lua it is if I can make this work.

I think you need Einhugur Developers Network Library
LuaScriptContext.GetTableValue(name as String, relativeIndex as Integer)

Sample code from the docs there:

if ls.GetGlobalTable("background") then
    ls.GetTableValue("red",0) // 0 because table is at top of the stack
    red = ls.GetInteger(-1,true)
   
    ls.GetTableValue("green",0) // 0 because table is at top of the stack
    red = ls.GetInteger(-1,true)
   
    ls.GetTableValue("blue",0) // 0 because table is at top of the stack
    blue = ls.GetInteger(-1,true)
   
    ls.Pop() // We pop the table off the stack once we are done with it
   
    MsgBox "Red: " + Str(red) + ", Green: " + Str(green) + ", Blue: " + Str(blue)
end if

So maybe:

Var Context As LuaScriptContext = New LuaScriptContext(ContextPointer)
If Context.ParameterCount < 2 Then
  Context.SetError("httpRequest needs at least requestMethod and url parameters.")
  Return 0
End If

Var RequestMethod As String = Context.GetString(1, False)
Var Url As String = Context.GetString(2, False)

Var Conn As New URLConnection

If Context.ParameterCount > 2 And Context.IsTable(3) Then
  // 
  context.GetTableValue("header1", 0)
  // check if ti is a string:
  if context.isString(0) then
  var headerValue As string = context.GetString(0)
  end if
  // Headers included 
  // What now?
End If

Try
  Var Response As String = Conn.SendSync(RequestMethod, Url)
  Context.Push(Response)
Catch Err As RuntimeException
  Context.SetError(Err.Message)
End Try

Return 1

It’s my understanding that GetTableValue pulls a value from the table by key. In my case, I don’t know the keys ahead of time.

I checked what is under that function. (since this plugin not really been touched for long time then its definitely not fresh in memory)

The GetTableValue has lua_gettable under it. (using first lua_pushstring to push the name onto the stack)

Which you can see there:

https://www.lua.org/manual/5.3/manual.html

The document it as
Pushes onto the stack the value t[k], where t is the value at the given index and k is the value at the top of the stack.

Some googling tells me maybe the solution would be to do:

lua_pushnumber(L, 1);
lua_gettable(L, -2);

Where 1 is the index.

If this is true then I need to add another function.

No that does not work gives hard crash.

Are Lua tables even sequential in that sense ?

Like if you for example use the Traversing a table from Lua example project then it wont give you the data in same order as it was passed in.

So it feels like it is behaving more like hash map ?

Ok so after more googling and testing.

Then, I found that not tables are born the same.

Table like this is not sequential:

BLUE = {red=0, green=0, blue=255}

And for such table you cannot get the length of it nor access by index without looping through it.

Table like this is sequential:

BLUE = {0, 0, 255}

And for the sequential type you can access it by index and get number of elements in the table without looping through it.

So I am adding GetTableLength function and also override for GetTableValue which takes index.

Both of them will only work for sequential table.

(Though, if I could somehow detect if its sequential, then I could internally do the looping and make it then work for all kinds of tables but I do not know how to detect that yet or if its possible)

This live tutorial here shows a bit the problem, its showing mixed mode table that is both sequential and not sequential where the element count returns only number of sequential elements:

The answer in lua - How to iterate items in a table using the C API - Stack Overflow looks like what I need, but I haven’t been able to follow along with it enough.

There is example project with the plugin that shows how to iterate through it.

Yeah I understand that, but it’s iterating over a table stored in a global variable. How do I get a table from a parameter?

Trying Wren now instead since I can’t figure out Lua, and I’m finding what may be a bug. Oh and for the record, I’d prefer to open a ticket for these, but I can’t find a way to do that on the website.

My logic is very similar in Wren. The script looks like this:

class Beacon {
  foreign static httpRequest(requestMethod, url)
  foreign static httpRequest(requestMethod, url, headers)
  foreign static httpRequest(requestMethod, url, headers, body)
}

System.print("Hello world")
Beacon.httpRequest("GET", "https://api.url.com/", {"Authorization": "password"})
System.print("Finished")

And my handler currently just dumps some information:

Var Machine As EinhugurWren.VM = EinhugurWren.VM.FromVMPtr(MachinePointer)
Var RequestMethod As String = Machine.SlotString(1)
Var Url As String = Machine.SlotString(2)

For Slot As Integer = 1 To Machine.SlotCount
  Var Type As EinhugurWren.SlotTypes = Machine.SlotType(Slot)
  Var TypeString As String
  Select Case Type
  Case EinhugurWren.SlotTypes.BOOL
    TypeString = "Boolean"
  Case EinhugurWren.SlotTypes.FOREIGN
    TypeString = "Foreign"
  Case EinhugurWren.SlotTypes.LIST
    TypeString = "List"
  Case EinhugurWren.SlotTypes.MAP
    TypeString = "Map"
  Case EinhugurWren.SlotTypes.NULL
    TypeString = "Null"
  Case EinhugurWren.SlotTypes.NUM
    TypeString = "Num"
  Case EinhugurWren.SlotTypes.STRING
    TypeString = "String"
  Case EinhugurWren.SlotTypes.UNKNOWN
    TypeString = "Unknown"
  End Select
  System.DebugLog("Slot " + Slot.ToString(Locale.Current, "0") + " is a " + TypeString)
  
  Select Case Type
  Case EinhugurWren.SlotTypes.STRING
    System.DebugLog("String in slot " + Slot.ToString(Locale.Current, "0") + " is '" + Machine.SlotString(Slot) + "'")
  End Select
Next

Which gives me the following output:

Slot 1 is a String
String in slot 1 is 'GET'
Slot 2 is a String
String in slot 2 is 'https://api.url.com/'
Slot 3 is a Map
Slot 4 is a String
String in slot 4 is 'Authorization'

Ok… good. I should be able to use EnsureSlots to create a slot 5, then GetMapValue to load the value of the key in slot 4 into slot 5.

But if I use the 4th parameter of my signature, the values in the slots go a bit… haywire:

Slot 1 is a String
String in slot 1 is 'GET'
Slot 2 is a String
String in slot 2 is 'https://api.url.com/'
Slot 3 is a Map
Slot 4 is a String
String in slot 4 is 'body'
Slot 5 is a String
String in slot 5 is 'password'

Now I no longer have the key from the map, I only have the value. This feels like a bug with the plugin. I’d expect either slot 4 to remain Authorization and slot 5 have body, or slot 4 gets body and slot 5 gets Authorization.

As a workaround, I could rearrange my parameters so the map is last, but that’s kind of avoiding the problem. What if I want a function with multiple maps?

Well shoot, it may be an issue with the Wren C API itself. There doesn’t appear to be a way to loop over a map. I opened this ticket in case I’m wrong. How do you iterate over a map with the C API? · Issue #1190 · wren-lang/wren · GitHub

Back to the drawing board I guess…

So why don’t you use javascript but from the browser (htmlviewer) it doesn’t need to be visible i guess. But it can communicate with xojo itself today. And you can load scripts on demand have a gui (html cas) etc still keeping it local to the machine.

Because this will be used in a console app.

1 Like

Wren embedding is much easier than lua as you dont have to manage the stack in such crazy way as Lua lets you do.

Wren is young though and they have not put out official update for long time which is a bit of a worry.

As for the ticket link then its on left side on our page under support.

Not sure how performance critical this is but you could have a look at my ObjoScript module as a scripting language. I think that can do what you’re asking:

https://garrypettet.com/projects/objoscript.html

Your module looks very pleasant to program in, and had I been aware of it sooner, I may have considered it. What I’ve decided on is using JavaScriptCore and implementing the missing functionality on my own. It’s just something I’ll have to document. I’m adding a whole library of functions anyway, so having developers use my injected httpRequest function instead of fetch isn’t a huge deal. That’s just an example, there’s a bunch I’ve had to replace. But it’s working really well and I’ve been able to move on to other parts of the feature.

1 Like