•   almost 9 years ago

Io parser

Also pretty late to the game... but I've been working on a parser for Io. It's far from complete: I've only been able to implement the absolute core of the language, plus a few constructs like infix operators.

Demo: http://dariusf.github.io/iota/
Project page (and details): https://github.com/dariusf/iota

I know Io is a bit obscure, but I thought it would be an interesting language to try. :)

  • 14 comments

  • Manager   •   almost 9 years ago

    Cool! Just had a look at the parser, and at the Io language. I love how simple the grammar is. You know, I almost wonder if this might be pretty good as a total beginner language (where they wouldn't even know they were using Io). A question I couldn't quite figure out: if you just call a method with no target, does it get the target of Object, or of something else, or nothing? (Just wondering if we could transpile "moveRight" to "this.moveRight()".)

    Do you think that this approach will be able to preserve original source ranges in the end? I noticed that you have a preprocessor step that does some regexp replacement from 'Object' to 'IoRootObject', which will change the source code locations.

  • Manager   •   almost 9 years ago

    I added an Io language stub to Aether here:

    https://github.com/codecombat/aether/blob/clojure/src/languages/io.coffee

  •   •   almost 9 years ago

    If you call a method with no target, the message gets sent to an object representing the current context. In the case of the global scope, that's a global object called 'Lobby' (similar to 'window' in JS). In a method body, the current context will implicitly be resolved to the current instance 'self' via prototypes. So, yes, it would be translated as you suggested. :)

    Edit: made a gist instead.
    https://gist.github.com/dariusf/60c00e82211531e458ad

    The regex preprocessing is a leftover from an earlier version and isn't needed any more. That said, compilation is a bit of a hack right now, and I'm still trying to figure how best to incorporate the source information as I refactor.

  •   •   almost 9 years ago

    Thanks for adding the stub!

    I know I have to implement Language#parse, but there are a couple of other steps I'm not sure how to achieve:

    1. A small library (below) is needed to run the generated code. Thus far I've mostly just prepended the library to whatever comes out of the compiler to run it - is that the way to go here as well? If the library were stored in a file somewhere, how would it be accessed from the language stub file?

    https://github.com/dariusf/iota/blob/master/src/lib.js

    2. I'm guessing I have to hook Io methods up to the actual CodeCombat API (like in my previous post) and add them to the library. How would they be accessed from within Aether?

  • Manager   •   almost 9 years ago

    I recently added the `runtimeGlobals` property stub for this. The Clojure parser is a decent example of how to use it: https://github.com/codecombat/aether/blob/clojure/src/languages/clojure.coffee

    Basically, you should make the library available in `runtimeGlobals` by `exporting` it from Iota as a node module and then `require()`ing it inside iota.coffee in Aether. Aether will then take care of making it available to the generated code. (Prepending works, too, but then the whole library needs to be recreated every time the player code is called.)

    CodeCombat and Aether will take care of making the CodeCombat methods available, so in your gist, none of the wrapper stuff is needed. The player will just type `moveRight`, and your code will parse that inside of the `player chooseAction := method` wrapper and return the method.

    Actually, since in JavaScript we can make an anonymous function and then later call it with the player as `this`, that's the general approach Aether takes. I assume it's possible in Io to do some sort of wrapper like this?

    chooseAction := method( moveRight )

    and then later:

    player chooseAction := chooseAction

  •   •   almost 9 years ago

    Ah, I see. It is indeed possible in Io, however the way it's compiled right now involves a bit more indirection (to support stuff like a prototype tree instead of chain, etc.).

    What I've done to support this is use proxy objects to intercept messages and forward them to the real destinations. Here's an example of the output:

    https://gist.github.com/dariusf/11492467685ec6cd933f

    While most of that can be brought in by a require(), the lower portion enclosing the user's code will probably have to be concatenated... which isn't pretty, but I'm not sure how else it could be done. An alternative to all this indirection would be wrappers, but I imagine that approach has its downsides too.

  • Manager   •   almost 9 years ago

    Hmm, I *think* that could work, but it does look complicated and it's early, so I'm not sure. Similar levels of complexity are being used in various bits for other languages, and eventually we got them working.

  •   •   almost 9 years ago

    Hi Nick,

    I know submissions have closed, but can I continue working on this? I was busy for a few days last week but am back now. Was just looking into integration with Aether today.

  • Manager   •   almost 9 years ago

    Sure, until I actually judge. I've been busy with http://blog.codecombat.com/multiplayer-programming-tournament so I haven't gotten started yet.

  •   •   almost 9 years ago

    Alright, thanks. Just say the word when you start judging.

    I've updated the Io stub, but I can't get it to work. :( The generated code works when I copy it out and run it externally, but not when I run it from within Aether:

    var aether = new Aether({language: 'io'});
    aether.transpile("writeln(1 + 1)");
    aether.run();

    I've been debugging it for a few hours and my best guess is that the runtime library isn't visible... Not sure why that would be though. Would you have any idea what the problem is?

    https://github.com/dariusf/aether/blob/io/src/languages/io.coffee

  •   •   almost 9 years ago

    Hi darius_foo, runtimeGlobals needs to be an object mapping your runtime's name to the actual runtime. So if your parser translates "writeln(1+1)" to "iota.lib.writeln(1+1)" (in JavaScript), your runtimeGlobals would be:

    runtimeGlobals: { iota: { lib: iota.lib } }

    Or simply:

    runtimeGlobals: { iota: iota }

    (ChallengePost screws up my indentation; you can omit the braces and indent the object literal appropriately)

    The way you're doing it now should also work, as long as the call is translated directly to "writeln(1+1)" in JavaScript. But I think it's not preferrable to do it this way because it introduces *all* of iota's runtime functions directly into Aether's global scope. You would be better off namespacing it under iota.lib. See how it's done for Clojure, for instance: https://github.com/codecombat/aether/blob/master/src/languages/clojure.coffee#L28-L30 and https://github.com/vickychijwani/closer.js/blob/master/src/closer-core.coffee#L691-L694. Hope that helps.

  •   •   almost 9 years ago

    @vickychijwani Thanks for the tip! I've changed it to use a namespace like you suggested.

    I've finally resolved the bugs. Unit tests are now passing! Will send a PR soon.

  • Manager   •   almost 9 years ago

    @darius_foo I'm in the judging process now; hope to finish by tonight.

  • Manager   •   almost 9 years ago

    @darius_foo can you send me an email? nick@codecombat.com

Comments are closed.