Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ jobs:
haxelib dev hscript .
- name: Test interp
run: |
haxe bin/build-api.hxml --interp
haxe bin/build-interp.hxml
haxe bin/build-interp.hxml -D hscriptPos
- name: Test neko
run: |
haxe bin/build-neko.hxml && neko bin/Test.n
haxe bin/build-neko.hxml -D hscriptPos && neko bin/Test.n
haxe bin/build-api.hxml -neko bin/api.n
haxe bin/build-neko.hxml && neko bin/Test.n
- name: Test js
run: |
haxe bin/build-js.hxml && node bin/Test.js
haxe bin/build-js.hxml -D hscriptPos && node bin/Test.js
haxe bin/build-api.hxml -js bin/api.js
haxe bin/build-js.hxml && node bin/Test.js
- name: Build hashlink from source
run: |
git clone https://github.com/HaxeFoundation/hashlink.git
Expand All @@ -43,5 +43,5 @@ jobs:
cd ..
- name: Test hl
run: |
haxe bin/build-hl.hxml && ./hashlink/hl bin/Test.hl
haxe bin/build-hl.hxml -D hscriptPos && ./hashlink/hl bin/Test.hl
haxe bin/build-api.hxml -hl bin/api.hl
haxe bin/build-hl.hxml && ./hashlink/hl bin/Test.hl
31 changes: 31 additions & 0 deletions TestHScript.hx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,37 @@ class TestHScript extends TestCase {
assertScript('var newMap = [{a:"a"}=>"foo", objKey=>"bar"]; newMap[objKey];', 'bar', vars);
}

static macro function liveSources() {
var config = @:privateAccess hscript.LiveClass.CONFIG;
var api = sys.FileSystem.exists(config.api) ? sys.io.File.getContent(config.api) : "";
var entries = [macro $v{config.api} => $v{api}];
for( f in sys.FileSystem.readDirectory("tests") ) {
if( !StringTools.endsWith(f,".hx") ) continue;
var file = "tests/"+f;
entries.push(macro $v{file} => $v{sys.io.File.getContent(file)});
}
return macro [$a{entries}];
}

#if !macro
function testLive():Void {
if( !hscript.LiveClass.isEnable() )
return;
#if !(sys || hxnodejs)
var sources : Map<String,String> = liveSources();
hscript.LiveClass.getContent = function(file) return sources.get(file);
#end
var runtimes = @:privateAccess hscript.LiveClass.LiveClassRuntime.runtimes;
assertTrue(runtimes != null && runtimes.length > 0);
for( r in runtimes ) {
var cl = @:privateAccess r.cl;
Type.createInstance(cl,[]);
assertTrue(r.setSource(hscript.LiveClass.getContent(r.file), true));
Type.createInstance(cl,[]);
}
}
#end

static function main() {
#if ((haxe_ver < 4) && php)
// uncaught exception: The each() function is deprecated. This message will be suppressed on further calls (errno: 8192)
Expand Down
2 changes: 2 additions & 0 deletions bin/build-api.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/build-each.hxml
--no-output
6 changes: 5 additions & 1 deletion bin/build-each.hxml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
-main TestHScript
-dce no
-lib hx3compat
-lib hx3compat
-D hscriptPos
--macro include("tests")
-xml bin/live-api.xml
--macro hscript.LiveClass.enable("bin/live-api.xml",["."])
1 change: 1 addition & 0 deletions bin/build-hl.hxml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin/build-each.hxml
-D hl_no_libuv
-hl bin/Test.hl
23 changes: 21 additions & 2 deletions hscript/Checker.hx
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ class Checker {
var completionExpr : Expr;
public var imports : Array<String> = [];
public var checkPrivate : Bool = true;

public var localClass : CClass;
public var allowAsync : Bool;
public var allowReturn : Null<TType>;
public var allowGlobalsDefine : Bool;
Expand Down Expand Up @@ -1095,9 +1097,10 @@ class Checker {
case TInst(c, args):
var map = (t) -> apply(t,c.params,args);
while( c != null ) {
var allowPrivate = isLocalClass(c);
for( fname in c.fields.keys() ) {
var f = c.fields.get(fname);
if( !f.isPublic || !f.complete ) continue;
if( (!f.isPublic && !allowPrivate) || !f.complete ) continue;
var name = f.name, t = map(f.t);
if( allowAsync && StringTools.startsWith(name,"a_") ) {
t = unasync(t);
Expand Down Expand Up @@ -1142,8 +1145,21 @@ class Checker {
return fields;
}

function isLocalClass( ct : CNamedType ) {
var c = localClass;
while( c != null ) {
if( c == ct )
return true;
c = c.superClass == null ? null : switch( follow(c.superClass) ) {
case TInst(c,_): c;
default: null;
}
}
return false;
}

function checkField( cf : CField, ct : CNamedType, args, forWrite, e ) {
if( !cf.isPublic && checkPrivate )
if( !cf.isPublic && checkPrivate && !isLocalClass(ct) )
error("Can't access private field "+cf.name+" on "+ct.name, e);
if( forWrite && !cf.canWrite )
error("Can't write readonly field "+cf.name+" on "+ct.name, e);
Expand Down Expand Up @@ -1811,6 +1827,9 @@ class Checker {
case "!":
unify(et,TBool,e);
return et;
case "~":
unify(et,TInt,e);
return et;
default:
}
case EFor(v, it, e):
Expand Down
2 changes: 1 addition & 1 deletion hscript/Live.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ package hscript;
specify both the XML types output file (generated by haxe compiler output `-xml api.xml` and the class paths
of your local sources.
**/
@:autoBuild(hscript.LiveClass.build())
@:remove @:autoBuild(hscript.LiveClass.build())
extern interface Live {
}
124 changes: 75 additions & 49 deletions hscript/LiveClass.hx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ class LiveClass {

#if !macro
class LiveClassRuntime {

static var runtimes : Array<LiveClassRuntime>;

var cl : Class<Dynamic>;
var type : hscript.Checker.TType;
var className : String;
Expand All @@ -162,13 +165,19 @@ class LiveClassRuntime {
var compiledFields : Map<String,Bool>;
var chk : Checker;
var version = 0;
public var file : String;
public var path : String;
public function new(cl, file, idents) {
this.cl = cl;
className = Type.getClassName(cl);
this.idents = idents;
this.file = file;
if( runtimes == null ) runtimes = [];
runtimes.push(this);
#if (hl && !hl_no_libuv)
this.path = LiveClass.registerFile(file, onChange);
if( this.path != null ) haxe.Timer.delay(onChange,0);
#end
}

function loadType() {
Expand All @@ -183,67 +192,84 @@ class LiveClassRuntime {
}

function onChange() {
try {
reload();
} catch( e : hscript.Expr.Error ) {
log(Std.string(e));
}
}

public function reload( forceReload = false ) {
if( path == null )
throw "Could not find the source file of "+className;
return setSource(LiveClass.getContent(path), forceReload);
}

public function setSource( content : String, forceReload = false ) {
if( type == null )
loadType();
try {
var content = LiveClass.getContent(path);
var parser = new hscript.Parser();
parser.allowTypes = true;
parser.allowMetadata = true;
parser.allowJSON = true;
var defs = parser.parseModule(content,path);
for( d in defs )
switch( d ) {
case DClass(c) if( c.name == className.split(".").pop() ):
var todo : Array<hscript.Expr> = [];
var done = [];
for( cf in c.fields ) {
if( cf.access.indexOf(AStatic) >= 0 )
var parser = new hscript.Parser();
parser.allowTypes = true;
parser.allowMetadata = true;
parser.allowJSON = true;
var defs = parser.parseModule(content,path == null ? className : path);
var changed = false;
for( d in defs )
switch( d ) {
case DClass(c) if( c.name == className.split(".").pop() ):
var todo : Array<hscript.Expr> = [];
var done = [];
for( cf in c.fields ) {
if( cf.access.indexOf(AStatic) >= 0 )
continue;
switch( cf.kind ) {
case KVar(v) if( !compiledFields.exists(cf.name) ):
if( v.get != null || v.set != null )
continue; // New properties not supported
todo.push({ e : EVar(cf.name,v.type,v.expr), pmin : 0, pmax : 0, line : 0, origin : null });
done.push(function(chk:Checker) {
newVars.push({ name : cf.name, expr : v.expr, type : @:privateAccess chk.locals.get(cf.name) });
compiledFields.set(cf.name, true);
});
case KFunction(f):
var v = functions.get(cf.name);
var code = hscript.Printer.toString(f.expr);
if( v == null ) {
v = { prev : code, value : null, index : idents.indexOf(cf.name) };
functions.set(cf.name, v);
if( !forceReload ) continue;
} else if( v.prev == code && !forceReload )
continue;
switch( cf.kind ) {
case KVar(v) if( !compiledFields.exists(cf.name) ):
if( v.get != null || v.set != null )
continue; // New properties not supported
todo.push({ e : EVar(cf.name,v.type,v.expr), pmin : 0, pmax : 0, line : 0, origin : null });
done.push(function(chk:Checker) {
newVars.push({ name : cf.name, expr : v.expr, type : @:privateAccess chk.locals.get(cf.name) });
compiledFields.set(cf.name, true);
});
case KFunction(f):
var v = functions.get(cf.name);
var code = hscript.Printer.toString(f.expr);
if( v == null ) {
v = { prev : code, value : null, index : idents.indexOf(cf.name) };
functions.set(cf.name, v);
} else if( v.prev != code ) {
var e : hscript.Expr = { e : EFunction(f.args,f.expr,cf.name), line : f.expr.line, pmin : f.expr.pmin, pmax : f.expr.pmax, origin : f.expr.origin };
todo.push(e);
done.push(function(chk) {
if( v.value == null && v.index >= 0 )
(cl:Dynamic).__INTERP_BITS |= 1 << v.index;
v.value = e;
v.prev = code;
});
}
default:
}
}
if( todo.length > 0 ) {
checkCode({ e : EBlock(todo), pmin : 0, pmax : 0, line : 0, origin : null }, done);
version++;
var e : hscript.Expr = { e : EFunction(f.args,f.expr,cf.name), line : f.expr.line, pmin : f.expr.pmin, pmax : f.expr.pmax, origin : f.expr.origin };
todo.push(e);
done.push(function(chk) {
if( v.value == null && v.index >= 0 )
(cl:Dynamic).__INTERP_BITS |= 1 << v.index;
v.value = e;
v.prev = code;
});
default:
}
default:
}
} catch( e : hscript.Expr.Error ) {
log(Std.string(e));
}
if( todo.length > 0 ) {
checkCode({ e : EBlock(todo), pmin : 0, pmax : 0, line : 0, origin : null }, done);
version++;
changed = true;
}
default:
}
return changed;
}

function checkCode( e : hscript.Expr, done : Array<Checker->Void> ) {
var chk = new hscript.Checker(LiveClass.getTypes());
chk.allowNew = true;
chk.allowPrivateAccess = true;
chk.allowGlobalTypes = true;
switch( type ) {
case TInst(c,_): chk.localClass = c;
default:
}
chk.setGlobal("this", type);
for( v in newVars )
chk.setGlobal(v.name, v.type);
Expand Down
78 changes: 78 additions & 0 deletions tests/LiveBasic.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tests;

class LiveBasic extends LiveTest {

var counter : Int;
var name : String;

public function new() {
counter = 0;
name = "basic";
checkOps();
checkCalls();
eq(counter, 3);
}

function add( a : Int, b : Int ) {
counter++;
return a + b;
}

function mul( a : Int, b : Int ) : Int {
return a * b;
}

function greet( who : String ) {
return "hello " + (who == null ? "world" : who);
}

function checkOps() {
var x = 10;
x += 5;
x *= 2;
x -= 1;
eq(x, 29);
eq(x % 4, 1);
eq(x >> 1, 14);
eq(x << 1, 58);
eq(x & 7, 5);
eq(x | 2, 31);
eq(x ^ 1, 28);
eq(~x, -30);
eq(-x, -29);
eq(x / 2, 14.5);
eq(x++, 29);
eq(x, 30);
eq(--x, 29);
assert(x > 20);
assert(!(x < 20));
assert(x == 29 && x != 30);
assert(x < 0 || x > 0);
eq(x > 20 ? "big" : "small", "big");
var s = "abc";
eq(s + 55, "abc55");
eq(s + s, "abcabc");
eq(s == "abc", true);
eq(s.length, 3);
eq(s.charAt(1), "b");
var n = null;
eq(n ?? "def", "def");
eq(s ?? "def", "abc");
}

function checkCalls() {
eq(add(1,2), 3);
eq(mul(add(2,3), 4), 20);
eq(counter, 2);
eq(add(mul(2,3), mul(2,2)), 10);
eq(greet(null), "hello world");
eq(greet("live"), "hello live");
eq(name, "basic");
name = "changed";
eq(name, "changed");
name = "basic";
eq(label(55), "[55]");
eq(this.label("x"), "[x]");
}

}
Loading
Loading