musictheory / NilScript

Objective-C-style language superset of JavaScript with a tiny, simple runtime
Other
50 stars 5 forks source link

Utilize ojc in NodeJS #46

Closed IngwiePhoenix closed 8 years ago

IngwiePhoenix commented 9 years ago

So there actually is a way to extend NodeJS' require:

Ingwie@Ingwies-Macbook-Pro.local ~/W/BIRD3 $ node
> require
{ [Function: require]
  resolve: [Function],
  main: undefined,
  extensions: 
   { '.js': [Function],
     '.json': [Function],
     '.node': [Function: dlopen] },
  registerExtension: [Function],
  cache: {} }

And I even found this: http://stackoverflow.com/questions/17294275/is-there-a-way-to-register-new-coffeescript-extensions-for-require (The ticked answer is where I found the resources)

Honestly, it would be pretty awesome to be able to use .oj files seamlessly together. For example:

require("other.oj");
@implementation MyClass : OtherClass {}
// ...
@end

I haven't looked into the compiler/runtime again in a while to see if classes are registered into the global $oj_oj object or not ^^; But it certainly would be very nice to do use this!

iccir commented 9 years ago

require occurs at runtime. That's too late for several of the existing compiler optimizations (inline const, inline enum, squeezing, etc). You could add support to parse require into the compiler, but that means making the compiler aware of Node's implementation of require, and the compiler would need to know when a module was suppose to be handled at runtime vs. at ojc time.

In general, there are optimizations that the compiler can do when it knows about all classes and methods in a compilation unit. The compiler may not do those optimizations yet, but I'd prefer to not exclude them in the future (hence the reason why the current supported path has always been "compile your .oj files as one unit" with multiple compilation units being experimental/undocumented).

Note: actual compilation speed, even if you compile all your sources each time, is really fast. The bottleneck is using JSHint/linters/babel/typechecking.

IngwiePhoenix commented 9 years ago

Well what I was thinking about was to redo my current app to use OJ instead of plain JS. So my idea was this:

app.js

require.extensions[".oj"] = function(module, filename) {
    // Get file from filename, compile it and run 
    // module._compile to finalize the require call
}
// Run actual app
require("./main/main.oj");

main.oj:

#include "myfile.oj"
#include "lib.oj"
@implementation MyApp {}
// ...
-(http) {
    var app = require("express")();
    app.get("/", function(req, res, next) {
        [self handleRequest:req to:res withCallback:next];
    });
}
@end

I dont know if you remember my connect-oj project. It has a tiny pre-processor that it can use to include files into this .oj file. So the compiler would get the entire source.

Another mock up I have had was

request_handler.oj

@implementation RequestHandler {
    var app;
}
+(id)init:(Express)_app {
    app = _app;
    // Assign routes etc
}
@end
module.exports = function(app) {
    return [[RequestHandler alloc] init:app];
}

And the very last mock up

db_models.oj

@implementation Model {
    var con;
}
+(id)create:(string)name {
    // ...
}
@end

module.exports = Model;

my_model.oj:

require("db_model.oj");
// or: var Model = require(...);
@implementation ChatMessages : Model {}
// ...
@end

Of course, these are only mock-ups. So I will probably have to do what other apps do and create an npm script that re-compiles my app, or re-build the app whenever it launches.

Anyway, I'd recommend you to think about it - anything that would work, would be very interesting! :) I just love using OJ in my project and want to extend its use beyond the browser.

IngwiePhoenix commented 9 years ago

I just played and toyed around with the runtime functions, and came across something that got me baffled:

var oj = require("ojc/src/runtime");
@implementation Foo
+(id)greet { console.log("Hi"); }
@end

var vname = [Foo class].displayName;
var obj = {f: oj._cls[vname] };
[obj.f greet]; // <-- And that works!

Maybe I figure it out somehow, but It made me think of an expression. If you ever intended to implement CommonJS patterns or NodeJS support in general, consider a code transformation like this:

@export Foo;
// => module.exports.Foo = $oj_oj._cls[ [Foo class].displayName ];

In another file:

var Foo = require("other.oj").Foo;
@implementation Baz : Foo
// ...
@end

I dont have any idea how to properly create a @import statement. It would probably require an enhanced @export statement.

But I just thought I'd drop it here, because I found this really cool! :)

IngwiePhoenix commented 9 years ago

Let's just say I got a bit bored while waiting for my food to be done... https://github.com/IngwiePhoenix/oj-node :) Should try it, it's kinda nice.