my http more compatible with ssl, script+jsvar can do subclasses of D objects

Posted 2020-04-27

More fun stuff in the arsd repo these last couple days. And btw I got my first eye treatment too and it seems to have been a success I feel improvement already! But still gotta avoid staring at computer screens all the time.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

What Adam is working on

I fixed a lot of little bugs over the weekend and played with a new scriptable subclass idea.

http bug fixes

arsd.http2 now dynamically loads openssl in an attempt to be more compatible with different openssl binary versions without recompiling the application. It also now correctly sets the hostname for tls ssi, meaning it will just work better on more shared encrypted servers.

subclasses in script

Most my weekend time though was spent on arsd.script and arsd.jsvar. Friday night, an IRC user asked if there was a script language that could inherit D classes. I said my script.d could with just a little work and that work could be automated with a template... then I spent some time and did it fairly automatically. And fixed more random bugs in script.d while I was in there, then realized I never switch and added it in there too!

I wrote up the examples as unittest documentation so you can see the new stuff here: arsd.jsvar.subclassable.

As you can see in the examples in the link, you can declare D classes normally, assign them to the script object via the subclassable template, then start instantiating and subclassing them in the script!

Subclasses created in the script can be passed back to D via references to the base class.

So the way it works is the subclassable template creates a final class that loops through all virtual methods and implements them with a stub that forwards to the var from jsvar. It also adds a private allocate method to help the script's new operator and instantiates a sub object to use as the var prototype.

The generated class is private, but since it implements the base class interface, the rest of D never needs to know about it. This same fact is what allows the script to override it: you set prototype things (which script.d can do in a D-like class syntax for you, or you can do members one by one)

Combining with minigui

Part of the fun here is combining these new facilities with arsd.minigui. Check this out:

1 import arsd.minigui;
2 import arsd.script;
3 
4 void main() {
5         var globals = var.emptyObject;
6 
7         globals.Widget = subclassable!Widget;
8         globals.Button = subclassable!Button;
9         auto window = new Window();
10         globals.window = window;
11 
12         import std.file;
13         interpret(readText("guis.js"), globals);
14 
15         window.loop();
16 }

There's the D side. Now, the script side:

var w = new Widget(window);
w.paddingLeft = 60; // overriding the D virtual method in script!
var b = new Button("very cool", w);

Run the program with that (you must use the master branch of my arsd libs if you want to try at home) and you'll see a space on the left of the window. Since paddingLeft is actually a virtual method in minigui (something I kinda regret now because changing it is a bit of a pain; I'm probably going to change that at some point, so if you are reading this in the future it might not work like this anymore), it will override it. The script lang treats opCall on a numeric member to be mulitiplication with a default arg of 1, so 5() yields 5*1 (aka 5) or 5(x+2) is 5*x+10, like in algebra. But here it is just the opCall returning self that gives the illusion of this working.

I could have also just done paddingLeft = function() { return 30; }, but since the script supports other syntax, I might as well use it!

In D, I often do an anonymous class return new class Widget { ... } , but that's a little wordy. In the script though, the syntax is extremely small.

Though, of course, you CAN write it out in class style too:

1 var w = new Widget(window);
2 w.maxWidth = 150; // override on this specific instance
3 
4 // and create a new class for reusing
5 class MyButton : Button {
6 	// The constructor is REQUIRED, even if it only does basic forwarding
7 	// since ctors are not inherited, even in the script.
8 	//
9 	// I might change my mind on that later, but for now be sure you
10 	// do this right to get the D object initialized properly.
11         this(lbl, parent) {
12                 super("overridden " ~ lbl, parent);
13         }
14 
15 	// and here's the virtual method override
16         function maxHeight() { return 30; }
17 }
18 
19 var b = new MyButton("omg", w);
20 
21 // btw events are already done JS-style in minigui
22 // and has several methods marked @scriptable, so like
23 // this just works too.
24 b.addEventListener("click", function() {
25         var nb = new Button("large", w);
26 });

Previously in script.d, you could attach individual objects and/or factory functions to return new ones, but you could never create new D instances like this and not have subclass overrides be visible to other native code.

The rest of minigui has no clue about this new script override feature, but it just worked with the replacement classes in the rest of it.

script.d is still buggy - I found 5 == 3+2 was failing before due to a precedence bug, and it just now got switch and is sure to be quirky in its own way I'll find as I use it more - and it makes zero effort for speed. But it is good at its original motivation: showing how easy D interop to a script language can be.

And this new little feature continues to push those limits.

To see more script.d examples, I wrote a few more here: arsd.script#examples. The classes demo at the bottom is generated from a unittest to show more interop pieces.

script in fiber

One other challenge I had with the script relates to the game I talked about last week: the implementations may take several looks to get user input or display animations, but the script shouldn't worry too much about that.

At first, I wanted to put in some kind of continuation feature in the interpreter, but then I realized there's an easier way: just wrap it in a Fiber and yield at those times.

It was actually really easy, and now my game script can say things like:

if(choose(["Yes", "No"]) == 0)
	showTextBox("You said yes!");

and while the reality in D is somewhat complicate to show all that stuff, the script is written just like that. I can't believe I didn't consider the fiber sooner.