Java LambdaMOO

14 views
Skip to first unread message

Jo Jaquinta

unread,
Feb 16, 2019, 3:48:42 PM2/16/19
to MOO Talk
Hey Folks,
   Thanks for your input. The point of balance I chose between sticking with the historical LambdaMOO and getting something done quickly was this. I've done a "reimplementation" of LambdaMOO but with a Javascript based scripting language, rather than a MOOScript compatible language. The downside is that I can't use existing databases, but I decided that was mitigated because I'm going to have to largely re-work things anyway, so it's not much harder to re-write the core for what I need. (E.g. being able to choose the "voice" with which your character speaks. Using SSML text instead of plain text, etc.) The up-side is that I can leverage all of the development and proofing of the LambdaMOO design.
    Since it is based on LambdaMOO design, it's not proprietary so I'm posing the code to my public GitHub account. If you are interested, you can find it here. I've got it as far as being able to run Minimal.db. You can scan through the unit tests if you want to get a general understanding of what's there and what isn't.
    This isn't a "port". I didn't just take the c-code and convert it line-by-line. That wouldn't work well and would avoid any of the benefits of going to a more modern object orientated language. Instead I've use the LambdaMOO programming manual as a "spec" and attempted to implement it as faithful as possible to that. The biggest difference is, obviously, the scripting language. But languages are languages, and it still should look pretty familiar. For example, here is a snipped from the add_verb unit test:
var info = [];
info[0] = toobj('#2');
info[1] = 'rw';
info[2] = 'wibble';
var args = [];
args[0] = 'any';
args[1] = 'over';
args[2] = 'any';
add_verb('#0', info, args);
I haven't come up with a good way yet to in-line the #2 object notation. Right now you have to use toobj(). Built-in function, though will accept strings in the form of '#2' as well. You can, however, refer to objects and properties directly. For example toObj('#3').location.test1(). That took some doing. But the alternative was extremely cumbersome.
    Anyhow. If people have the time and interest I'd love for people to have a look. If you spot places I have mis-interpreted the spec (it's been 15+ years since I did a lot of MOO work!) please alert me to them. If you want to actually run it yourself, give me a head's up. There are a few trivial dependencies on my private libraries that I'll have to clear up.
         Cheers,
             Jo

Brendan B

unread,
Feb 17, 2019, 8:16:32 PM2/17/19
to MOO Talk
This is really cool. Thank you for sharing. I'll be checking out the repo!

Shan Hollen

unread,
Feb 18, 2019, 8:41:15 AM2/18/19
to MOO-...@googlegroups.com
Hi, Jo!

This is incredibly cool!

I’m a MOO veteran of decades. I had been using LambaMOO and a custom core for years when Todd Sundsted came out with Stunt. I switched over to Stunt for the multi-inheritance,  map datatype and ability to call functions directly from primitive objects. I’d hacked LambdaMOO to run on OSX, added some other stuff and was wanting to add OOB hooks for clients, a web client, support for all the popular protocols, just a lot of modern features, when Evennia came out. So I hacked around with that trying to make it as MOO-like as I could, and I did pretty well at that but in the end the inability to renumber objects and the classic object model inherent to Python were too much to deal with for what I wanted to do. And now I’m back with MOO again, implementing lots of Python-style syntax, trying to update string formatting and just generally add modern features. But a MOO that uses JS for scripting would come with much of that stuff baked in and make my life so much easier. I’m definitely interested in starting up a JSMOO db and building a core. I haven’t looked at the code yet but I’m very stoked about this!  :-)   Thanks for the effort!

-Shan

--
You received this message because you are subscribed to the Google Groups "MOO Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to MOO-talk+u...@googlegroups.com.
To post to this group, send email to MOO-...@googlegroups.com.
Visit this group at https://groups.google.com/group/MOO-talk.
For more options, visit https://groups.google.com/d/optout.

Jo Jaquinta

unread,
Feb 18, 2019, 9:39:52 AM2/18/19
to MOO Talk
I hope it lives up to your expectations! I did add in an intrinsic Map type. But I also merged the floating point and integral number types. Some of the reasoning is that, ultimately, I want to store the data in DynamoDB, which is what I back-end my Alexa skills with. Merging the numbers makes that easier, and a Map is natively supported. If it causes trouble, I can always split the numbers out eventually.

>I switched over to Stunt for the multi-inheritance
I'm afraid I only have single inheritance. Multi-inheritance is an interesting idea, but I think I'd have to re-work how I've done properties. (Which I may have to do anyway.)

>ability to call functions directly from primitive objects
If by "primitive obejcts" you mean MOO objects, like toobj(0).do_login_command() then that is there. If you mean from primitive objects like integers and strings... well, yes, you get that (more or less) with Javascript.

>inability to renumber objects
That isn't implemented yet. But there shouldn't be a barrier to it.

>I’m definitely interested in starting up a JSMOO db and building a core
My current exercise was to just fire up LambdaCore and see what breaks first. Turned out it was the task management stuff. I had been hoping to avoid that. But it seems intrinsic. I stubbed out the functions last night. First dependency was callers() which isn't too hard to implement. Since Java supports threads natively, I had hoped that I wouldn't have to re-implement all the thread management stuff in MOO. But it looks like I might have to.
I just checked in the LambdaCore.jdb I have, even though it is currently unchanged, against issue #7. You are welcome to start your own core. That's probably a more sensible route. You can see from the LoginTest unit test how to wrap up your own core and run tests against it. Using the extension mechanism I added in a total in-memory dialog system that can be run in place of the TCP/IP sockets specifically for testing. It's very handy if you are a fan of test-driven development.

Next thing on my plate (#6) is supporting the #nnn and $xyzzy syntax. I had a non-terrible workaround for #nnn, but there was no palatable workaround for $xyzzy. So I'm going to put in a pre-processor to scan and change the Javascript. I'd really rather not muck with the Javascript people write, but I couldn't see a way to extend the language to support $xyzzy and that's pretty prevalent in the Core code I've seen.

Oh, and longer term, there is no reason other scripting types couldn't be implemented. I've done other systems with multiple scripting languages and introduced some sort of marking mechanism (e.g. "$javascript{{ ... }}" to key the script processor into what language engine to use for that specific script. If someone really wants, say, Python, as a scripting language, and can find a Java driver for it (is that what Jython does?), it's not too far fetched to wire it up in parallel. Should be able to do change languages on a per-verb basis.
But that's probably getting ahead of things. Just mentioning some of the places this could go if we get it all working.

Jo

David Given

unread,
Feb 18, 2019, 10:55:07 AM2/18/19
to Jo Jaquinta, MOO Talk
What are you doing to enforce security between objects belonging to different players? e.g. simply handing one player a Javascript object belonging to another player won't work because they can be modified at will.

I had thought of a potential mechanism where each player runs in a sandbox similar to a web worker, and all access to the database gets serialised through JSON. Access object properties gets indirected via the kernel. It's expensive, of course, but guarantees no data leakage. But then it also adds all kinds of nasty synchronisation issues as each player gets their own thread to run code in.

Jo Jaquinta

unread,
Feb 18, 2019, 11:32:43 AM2/18/19
to MOO Talk
I'm not 100% sure I understand the question, but I think the answer is this: all argument passing from one verb to another is, effectively, pass by value. Only MOO Objects get passed by reference. And they have a robust permission structure (which I hope I've implemented right).

So, in the core Java code, all transient values are represented by specific classes. With MOOValue being the base class, and then having MOOString, MOONumber, MOOObjRef, MOOList, and MOOMap being derived classes. The API classes (com.tsatsatzu.moo.core.api) are all written in terms of these classes. For example in MOOObjectAPI: public static MOOObjRef create(MOOObjRef parentRef, MOOObjRef owner) throws MOOException.
For the Javascript driver, these functions are wrapped for the Javascript engine, and do translations between the internal representations and the Javascript representations. So the wrapper for the create() function (FuncObjectAPI) looks like this:
    public static Object create(Object arg0, Object arg1) throws MOOException
    {
        MOOObjRef parent = CoerceLogic.toObjRef(arg0);
        MOOObjRef owner = CoerceLogic.toObjRef(arg1);
        MOOObjRef ret = MOOObjectAPI.create(parent, owner);
        return CoerceLogic.toJavascript(ret);
    }

The entities coming in from Javascript are viewed as vanilla Objects in Java. These are then "coerced" into the internal object types we expect for that functional call by common logic. The internal API is called, and, if needed, the reply, which is an internal object type is coerced back to a Javascript object.
So, at least for API calls, a Javascript object would be coerced into a MOOMap and any receiving function would see it as that. They might update it, but unless they return it, those updates will be to a copy.

Thinking on it, though, direct calls to an object (e.g. #0.wibble()) are handled differently. MOOObjRef's are converted to JSObjRef, which have nifty intrinsic methods that make property lookup and assignment work. For verbs, though, they return a JSVerb wrapper around the MOOVerb object which has similarly nifty intrinsic methods that make implicit verb calls work. Looking at the code for that, the objects that come in are just blindly passed down, they are not coerced. So you could have a function call like #0.wibble({"a":"b"}), and with code for wibble that does var wobble = args[0] to get that value.
I have to say I didn't think of that scenario. I don't even know if it works, since you would end up passing an object from one script engine to another script engine. If it does then, yet, that could be a security problem. The called function could modify the object passed to it in arbitrary ways. I may need to put in a layer of coercion in that sort of object to object call. That would force everything down to pass-by-value and limit it to only canonical types.
I should probably write a unit test to replicate that scenario. (Created issue #8.) Good catch.

Jo

David Given

unread,
Feb 18, 2019, 12:25:43 PM2/18/19
to Jo Jaquinta, MOO Talk
Ah, I didn't realise there was a Java layer in there as well --- I thought it was all pure Javascript. (Although doesn't modern LambdaMOO now have some kind of lightweight object semantic for things like maps, which get passed by reference?)

I don't know of any other languages which have pervasive security the way LambdaMOO does, with multiple users interacting within the same VM. I know from past experience that LambdaMOO didn't necessarily enforce this security terribly well (I'd like to apologise again to the PernMOO admins for the plague of self-replicating insects back in the 90s), but that doesn't undermine the idea. I wonder if such a thing would have value today?

Shan Hollen

unread,
Feb 18, 2019, 4:48:52 PM2/18/19
to MOO-...@googlegroups.com
> I'm afraid I only have single inheritance. 

I really meant mixins, which aren’t native to JS ether.

> If by "primitive obejcts" you mean…

I mean primitive types, strings, integers and lists mostly. String formatting, proposition substitution and stuff is a lot more elegant and readable if you don’t have to resort to third-party methods on $utility objects.

> That isn't implemented yet. But there shouldn't be a barrier to it. 

Databases prevent the reuse of object numbers in some codebases. Hate that.

> Next thing on my plate (#6) is supporting the #nnn and $xyzzy 

Access to objects without coercing strings is one of the things I love most about MOO.

> Oh, and longer term, there is no reason other scripting types couldn't be implemented. I've done other systems with multiple scripting languages and introduced some sort of marking mechanism (e.g. "$javascript{{ ... }}" to key the script processor into what language engine to use for that specific script. 

That is very cool. I prefer Python to Javascript. Python isn’t a prototype-based language though so I dunno how that would play. Lua may actually be the most natural fit to game scripting. 

Now I need to go find a Java book.

Jo Jaquinta

unread,
Feb 18, 2019, 4:57:44 PM2/18/19
to MOO Talk
Yes, the code base is in Java, but it supports Javascript as the scripting language. Analogous to how LambdaMOO is implemented in C, but supports MOO as a scripting language.

Yeah, back in the 90s I created a 'Gelatinous Cube' to go around the fantasy realm we had created and "suck things up". It didn't work so well, so I overrode the "accept" verb to see if it would get more. Then everything went dark. It confused me for a while before I realized it had eaten me! So I logged out, logged in and the entire MOO was up in arms about the blob going around everything sucking up EVERYTHING!
Oh, those were the days.
But, yes, a lesson in security. :-)
Jo

Michael Munson

unread,
Feb 18, 2019, 6:26:20 PM2/18/19
to Shan Hollen, MOO-...@googlegroups.com
The reason MOO is so anal about object numbers is because the MOO "Database" is only an array of structures. If you use a modern object container (of which Java has many) like maybe a hash map then there is no reason why you couldn't renumber an object to anything you wanted, even a string perhaps.

--

Jo Jaquinta

unread,
Feb 18, 2019, 6:53:56 PM2/18/19
to MOO-...@googlegroups.com
I had a quick look. Both Python (via Jython) and Lua seem to have Script Engines via the JSR-223 extensions to Java/Javascript. It seems terribly trivial to integrate them. The only problem with Jython is it relies on a jython-engine.jar file from https://scripting.dev.java.net and the site seems to have vanished. If you can find that jar file, I can add Python to the list of supported scripting languages.

FYI: I got the first script in LambdaCore ported.
MOO script:

"...This code should only be run as a server task...";
if (callers())
return E_PERM;
endif
if (typeof(h = $network:incoming_connection(player)) == OBJ)
"connected to an object";
return h;
elseif (h)
return 0;
endif
host = $string_utils:connection_hostname(connection_name(player));
if ($login:redlisted(host))
boot_player(player);
server_log(tostr("REDLISTED: ", player, " from ", host));
return 0;
endif
"HTTP server by Krate";
try
newargs = $http:handle_connection(@args);
if (!newargs)
return 0;
endif
args = newargs;
except v (ANY)
endtry
"...checks to see if the login is spamming the server with too many commands...";
if (!$login:maybe_limit_commands())
args = $login:parse_command(@args);
return $login:(args[1])(@listdelete(args, 1));
endif


Javascript script:
function do_login_command() {
	"...This code should only be run as a server task...";
	if (callers()) {
		return E_PERM;
	}
	if (typeof (h = $network.incoming_connection(player)) == OBJ) {
		"connected to an object";
		return h;
	} else if (h) {
		return 0;
	}
	host = $string_utils.connection_hostname(connection_name(player));
	if ($login.redlisted(host)) {
		boot_player(player);
		server_log(tostr("REDLISTED: ", player, " from ", host));
		return 0;
	}
	"HTTP server by Krate";
	try {
		newargs = $http.handle_connection.apply($http.handle_connection, args);
		if (!newargs) {
			return 0;
		}
		args = newargs;
	} catch (ANY) {}
	"...checks to see if the login is spamming the server with too many commands...";
	if (!$login.maybe_limit_commands()) {
		args = $login.parse_command.apply($login.parse_command, args);
		return $login[args[1]].apply($login[args[1]], listdelete(args, 1));
	}
}
do_login_command();
You can't do a "return" from top level Javascript. So, for minimal intervention, I wrapped the whole thing in a function().
Also, the cleverest way I could work out how to port @list from MOO was the rather awkward blah.apply(blah, list) construct.
Only seventeen thousand and forty seven more functions to go. :-)

Jo

Jo Jaquinta

unread,
Feb 20, 2019, 9:16:09 AM2/20/19
to MOO Talk
I was able to, eventually, find the right jars and integrated Python into the code base. Unfortunately I wasn't able to do anything non-trivial with it. It's method of calling native functions is different than the Javascript engine, and so another interface layer would have to be done. It's tedious and boring, but simple enough. I trialed two native functions and was able to get compiling Python script, but could not get it to execute right. Lots of complains about __call__ not being defined. I futzed with different indentation levels but I don't really know Python well enough to look at it more intelligently. If anyone wants to mess with it the test case I was using was VerbAPITest.testVerbSetInfoPython. My "closest to working" script looked like this:

                from com.tsatsatzu.moo.core.logic.script.funcs import PythonAPI
                info = [ '#2', 'rw', 'wibble' ]
                PythonAPI.set_verb_info(#0, 'do_login_command', info)
which generates this error:
AttributeError: 'javainstance' object has no attribute '__call__'

Jo

Just Johnny

unread,
Feb 24, 2019, 8:44:41 PM2/24/19
to Jo Jaquinta, MOO Talk
Don't call it MOO then, because it's something completely different. JOO? I'd run a JOO database. :-D

--
Reply all
Reply to author
Forward
0 new messages