Skip to content

Commit

Permalink
Add samples ; Start filling README ; Fix a few platform-specific issues
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyfa committed May 11, 2019
1 parent 98eddfa commit 1606723
Show file tree
Hide file tree
Showing 22 changed files with 315 additions and 251 deletions.
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
# interpret

Turn haxe classes into scriptable/interpretable/hot-reloadable code using hscript

- [x] Class parsing
- [x] Properties with getters and setters
- [x] Static/Instance methods and properties
![interpret livereload demo](images/interpret-livereload-demo.gif)

## Goals

### Execute standard haxe code as script

In contrary of regular hscript, **interpret** takes as input a standard Haxe class. This means you can work on your code with the same IDE, code completino, static analysis tools as you would do for compiled haxe code.

### No need to use haxe compiler to consume scripts

Scripts are loaded as raw text by the app embedding **interpret**, parsed and transformed to hscript on the fly. This means you don't need haxe compiler to load a new script.

As a result, while building a project using haxe compiler can become slower as it gets more and more code in it, updating a script with **interpret** should always be fast, no matter how big is your project, because it doesn't need to build the whole project. It just need to process a single file and execute it, that's it.

### Prefer portability over performance

As **interpret** is built on top of hscript to execute haxe code, it is not expected to be fast, but it is very portable and should work fine with any haxe target.

### Provide live-reload capabilities

It is possible to write haxe classes with live-reloadable methods using **interpret**'s `Interpretable` interface. Simply saving the file will update the code within a second, without restarting the app.

On targets supporting haxe's **sys** API, live reload can work without having to run any custom server. The app itself will just watch for changes on the file system.

It is however possible to provide custom watchers as needed for more complexe scenarios. Some ideas: remotely updating a mobile app (ios/android) through websockets, embedding a code editor in the same app that runs it...

### Try to stay as close as possible to Haxe

While **interpret** cannot reproduce 100% of haxe features as script, it is trying its best to cover most of it. It allows to expose _native_ haxe classes and abstracts (and soon enums) to interpretable code, so that it's possible to call them from script, and write code that works the same way, both as compiled code (with haxe compiler) and interpretable code (with **interpret**).

A typical scenario leveraging this aspect is using **interpret** live-reload while iterating on your app code and testing quickly what it does, then bundling the final binary as compiled, and efficient, haxe code: use scripting in dev, but make no compromise regarding performance in production!

To see what haxe features are supported, you can take a look at the haxe files in [test script directory](/test/script/), which are all tested and compatible with **interpret**.

**interpret** is still at an early stage of development and can be improved a lot. New features, like handling of enums and pattern matching, will be added in the future.

Binary file added images/interpret-livereload-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion interpret/DynamicModule.hx
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ class DynamicModule {
} else {
toAdd.push([
subTypePath + '.' + field.name,
ModuleItemKind.CLASS_VAR,
ModuleItemKind.CLASS_FUNC,
isStatic,
retType,
argTypes,
Expand Down
26 changes: 20 additions & 6 deletions interpret/Env.hx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Env {
var superClasses:Map<String,String> = new Map();

/** Internal map of classes and the interfaces they implement (if any) */
var interfaces:Map<String,Map<String,Bool>> = new Map();
var interfaces:Map<String,EnvInterfaces> = new Map();

/** Resolved dynamic classes */
var resolvedDynamicClasses:Map<String,DynamicClass> = new Map();
Expand Down Expand Up @@ -123,10 +123,12 @@ class Env {
public function getInterfaces(classPath:String):Map<String,Bool> {

var subItems = interfaces.get(classPath);
if (subItems != null) return subItems;
if (subItems != null) return subItems.mapping;
var alias = aliases.get(classPath);
if (alias == null) return null;
return interfaces.get(alias);
var res = interfaces.get(alias);
if (res != null) return res.mapping;
return null;

} //getInterfaces

Expand Down Expand Up @@ -338,14 +340,16 @@ class Env {
var envSubItems = interfaces.get(key);
if (envSubItems == null) {
var aliasKey = aliases.get(key);
envSubItems = interfaces.get(aliasKey);
if (aliasKey != null) {
envSubItems = interfaces.get(aliasKey);
}
if (envSubItems == null) {
envSubItems = new Map();
envSubItems = new EnvInterfaces();
interfaces.set(key, envSubItems);
}
}
for (subKey in subItems.keys()) {
envSubItems.set(subKey, true);
envSubItems.mapping.set(subKey, true);
}
}
}
Expand Down Expand Up @@ -378,3 +382,13 @@ class Env {
} //toString

} //Env

class EnvInterfaces {

public var mapping:Map<String,Bool> = new Map();

public function new() {

} //new

} //EnvInterfaces
9 changes: 0 additions & 9 deletions interpret/ExtensionTest.hx

This file was deleted.

26 changes: 0 additions & 26 deletions interpret/ImportTest.hx

This file was deleted.

3 changes: 2 additions & 1 deletion interpret/InterpretableTools.hx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ class InterpretableTools {
// Create env
var env = new Env();
env.addDefaultModules();
env.addModule('interpret.Interpretable', DynamicModule.fromStatic(interpret.Interpretable));

var extendingClassName = className + '_interpretable';
var extendingClassPath = extendingClassName;
if (classPack.length > 0) extendingClassPath = classPack.join('.') + extendingClassPath;
if (classPack.length > 0) extendingClassPath = classPack.join('.') + '.' + extendingClassPath;

Env.configureInterpretableEnv(env);

Expand Down
24 changes: 21 additions & 3 deletions interpret/Interpreter.hx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ class Interpreter extends hscript.Interp {

override function resolve(id:String):Dynamic {

//trace('RESOLVE $id');

var l = locals.get(id);
if (l != null) {
return l.r;
Expand Down Expand Up @@ -346,7 +344,6 @@ class Interpreter extends hscript.Interp {
}
else if (variables.exists(f)) {
var result = variables.get(f);
//trace('-> $result');
if (Reflect.isFunction(result)) {
// TODO cache?
if (classInterpreter != null) {
Expand Down Expand Up @@ -401,7 +398,26 @@ class Interpreter extends hscript.Interp {
return result;
}
} else {

var clazz = Type.getClass(superInstance);
var type = clazz != null ? Type.getClassName(clazz) : null;
var fieldTypePath = type + '.' + f;
var item = env.resolveItemByTypePath(fieldTypePath);
var result = super.get(superInstance, f);
if (item != null && result != null) {
switch (item) {
case ClassFieldItem(rawItem, moduleId, name, isStatic, type, argTypes):
if (argTypes != null) {
// This is a method, bind instance
var prevResult = result;
result = Reflect.makeVarArgs(function(args) {
return Reflect.callMethod(superInstance, prevResult, args);
});
}
default:
}
}

if (result != null || Reflect.hasField(superInstance, f) || Reflect.hasField(superInstance, 'get_' + f)) {
return result;
}
Expand Down Expand Up @@ -652,6 +668,8 @@ class Interpreter extends hscript.Interp {

override function call(o:Dynamic, f:Dynamic, args:Array<Dynamic>):Dynamic {

//trace('CALL $o $f $args');

if (Std.is(f, RuntimeItem)) {
switch (f) {
case ExtensionItem(ClassFieldItem(rawItem, moduleId, name, isStatic, type, argTypes), _):
Expand Down
17 changes: 0 additions & 17 deletions interpret/ParentClass.hx

This file was deleted.

5 changes: 0 additions & 5 deletions interpret/SomeClassWithParent.hx

This file was deleted.

6 changes: 3 additions & 3 deletions interpret/StandardWatcher.hx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package interpret;

#if sys
#if (sys || hxnodejs || nodejs || node)
import sys.io.File;
import sys.FileSystem;
#end
Expand Down Expand Up @@ -28,7 +28,7 @@ class StandardWatcher implements Watcher {

public function watch(path:String, onUpdate:String->Void):Void->Void {

#if !sys
#if (!sys && !hxnodejs && !nodejs && !node)
trace('[warning] Cannot watch file at path $path with StandardWatcher on this target');
return function() {};
#end
Expand Down Expand Up @@ -72,7 +72,7 @@ class StandardWatcher implements Watcher {
if (timeSinceLastCheck < UPDATE_INTERVAL) return;
timeSinceLastCheck = 0.0;

#if sys
#if (sys || hxnodejs || nodejs || node)
for (path in watched.keys()) {
if (FileSystem.exists(path) && !FileSystem.isDirectory(path)) {
var stat = FileSystem.stat(path);
Expand Down
6 changes: 6 additions & 0 deletions sample-dynclass-js.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-lib hscript:2.3.0
-main sample.DynamicClassSample
-cp .
-debug
-js main.js
-lib hxnodejs
5 changes: 5 additions & 0 deletions sample-dynclass.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-lib hscript:2.3.0
-main sample.DynamicClassSample
-cp .
-debug
--interp
9 changes: 9 additions & 0 deletions sample-livereload-js.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-lib hscript:2.3.0
-main sample.LiveReloadSample
-cp .
-debug
-D interpretable
-D interpret_watch
-D interpret_mute_import_warnings
-js main.js
-lib hxnodejs
8 changes: 8 additions & 0 deletions sample-livereload.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-lib hscript:2.3.0
-main sample.LiveReloadSample
-cp .
-debug
-D interpretable
-D interpret_watch
-D interpret_mute_import_warnings
--interp
50 changes: 50 additions & 0 deletions sample/DynamicClassSample.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package sample;

import sys.io.File;
import interpret.Env;
import interpret.DynamicModule;

/** A sample that loads and instanciate a dynamic class
from file system and call `hello()` method on it. */
class DynamicClassSample {

public static function main() {

#if js
try {
untyped require('source-map-support').install();
} catch (e:Dynamic) {}
#end

// Create env
var env = new Env();
env.addDefaultModules();

// Expose some native modules
// sample.native.NativeClass can be imported and used from interpretable code because we exposed it
env.addModule('sample.native.NativeClass', DynamicModule.fromStatic(sample.native.NativeClass));
// StringTools can be used with `using` in dynamic classes because we exposed it as well
env.addModule('StringTools', DynamicModule.fromStatic(StringTools));

// Read HelloWorld.hx as raw text and add it as dynamic module
env.addModule('sample.script.HelloWorld', DynamicModule.fromString(env, 'HelloWorld', File.getContent('sample/script/HelloWorld.hx')));

// Link every modules
env.link();

// Load `HelloWorld` dynamic class
var helloWorldClass = env.modules.get('sample.script.HelloWorld').dynamicClasses.get('HelloWorld');

// Create instance
var helloWorldInstance = helloWorldClass.createInstance();

// Call hello() on this dynamic instance
// (this should print `hello Jeremy`)
helloWorldInstance.call('hello', ['Jeremy']);

// Call hello without providing a name, should pick a random one
helloWorldInstance.call('hello');

} //main

} //DynamicClassSample
Loading

0 comments on commit 1606723

Please sign in to comment.