diff --git a/build.hxml b/build.hxml index c206dabd..ada9ea6b 100644 --- a/build.hxml +++ b/build.hxml @@ -15,4 +15,4 @@ # -cmd cp java_out/Main-debug.jar converter.jar -cmd cp java_out/Main.jar converter.jar --cmd java -jar converter.jar config-local.json +# -cmd java -jar converter.jar config-local.json diff --git a/compat/ASAny.hx b/compat/ASAny.hx index e0853431..13046dca 100644 --- a/compat/ASAny.hx +++ b/compat/ASAny.hx @@ -49,7 +49,7 @@ abstract ASAny(Dynamic) if (this == null) { return false; } - if (Std.is(this, Float)) { + if (Std.isOfType(this, Float)) { var v:Float = cast this; return v != 0 && !Math.isNaN(v); } @@ -64,18 +64,18 @@ abstract ASAny(Dynamic) if (this == null) { return 0; } - if (Std.is(this, Int)) { + if (Std.isOfType(this, Int)) { return cast this; } - if (Std.is(this, Float)) { + if (Std.isOfType(this, Float)) { var v:Float = cast this; return if (Math.isNaN(v)) 0 else Std.int(v); } - if (Std.is(this, String)) { + if (Std.isOfType(this, String)) { var i = Std.parseInt(cast this); return if (i == null) 0 else i; } - if (Std.is(this, Bool)) { + if (Std.isOfType(this, Bool)) { return if (cast this) 1 else 0; } return 0; diff --git a/compat/ASCompat.hx b/compat/ASCompat.hx index ecc9be73..84077d31 100644 --- a/compat/ASCompat.hx +++ b/compat/ASCompat.hx @@ -31,6 +31,14 @@ class ASCompat { #end } + public static inline function unescape(s:String):String { + #if flash + return untyped __global__["unescape"](s); + #else + return js.Lib.global.unescape(s); + #end + } + #if flash public static inline function describeType(value:Any):compat.XML { return flash.Lib.describeType(value); @@ -167,10 +175,9 @@ class ASCompat { public static macro function asVector(value:Expr, typecheck:Expr):ExprOf>>; public static macro function isVector(value:Expr, typecheck:Expr):ExprOf; - #if !flash - @:noCompletion public static inline function _asVector(value:Any):Null> return if (openfl.Vector.isVector(value)) value else null; - @:noCompletion public static inline function _isVector(value:Any):Bool return openfl.Vector.isVector(value); - #end + @:noCompletion public static inline function _asVector(value:Any):Null> return if (_isVector(value)) value else null; + @:noCompletion public static inline function _isVector(value:Any):Bool + return Reflect.hasField(value, '__array') && Reflect.hasField(value, 'fixed'); public static inline function asFunction(v:Any):Null { return if (Reflect.isFunction(v)) v else null; @@ -221,6 +228,51 @@ class ASCompat { return !!v; #end } + + /** + * https://github.com/HaxeFoundation/as3hx/blob/829f661777d0458c7902c4235a4c944de4c8cc6d/src/as3hx/Compat.hx#L114 + */ + public static function parseInt(s:String, ?base:Int):Null { + #if js + if (base == null) base = s.indexOf("0x") == 0 ? 16 : 10; + var v:Int = js.Syntax.code("parseInt({0}, {1})", s, base); + return Math.isNaN(v) ? null : v; + #elseif flash + if (base == null) base = 0; + var v:Int = untyped __global__["parseInt"](s, base); + return Math.isNaN(v) ? null : v; + #else + var BASE = "0123456789abcdefghijklmnopqrstuvwxyz"; + if (base != null && (base < 2 || base > BASE.length)) + return throw 'invalid base ${base}, it must be between 2 and ${BASE.length}'; + s = s.trim().toLowerCase(); + var sign = if (s.startsWith("+")) { + s = s.substring(1); + 1; + } else if (s.startsWith("-")) { + s = s.substring(1); + -1; + } else { + 1; + }; + if (s.length == 0) return null; + if (s.startsWith('0x')) { + if (base != null && base != 16) return null; // attempting at converting a hex using a different base + base = 16; + s = s.substring(2); + } else if (base == null) { + base = 10; + } + var acc = 0; + try s.split('').map(function(c) { + var i = BASE.indexOf(c); + if(i < 0 || i >= base) throw 'invalid'; + acc = (acc * base) + i; + }) catch(e:Dynamic) {}; + return acc * sign; + #end + } + } class ASArray { diff --git a/compat/ASDictionary.hx b/compat/ASDictionary.hx index eb000def..98bfabef 100644 --- a/compat/ASDictionary.hx +++ b/compat/ASDictionary.hx @@ -69,7 +69,7 @@ abstract ASDictionary(Dictionary) from Dictionary to DictionaryDynamic to (Dynamic,Dynamic,Dynamic,Dynamic,Dynamic)->Dynamic to (Dynamic,Dynamic,Dynamic,Dynamic,Dynamic,Dynamic)->Dynamic -{} +{ + + #if js + public var length(get, never): Int; + + private function get_length(): Int { + final f = this; + return js.Syntax.code('f.length'); + } + #end + +} #end \ No newline at end of file diff --git a/compat/ASObject.hx b/compat/ASObject.hx index f9e3908f..0872fdee 100644 --- a/compat/ASObject.hx +++ b/compat/ASObject.hx @@ -59,7 +59,7 @@ abstract ASObject(flash.utils.Object) if (this == null) { return false; } - if (Std.is(this, Float)) { + if (Std.isOfType(this, Float)) { var v:Float = cast this; return v != 0 && !Math.isNaN(v); } @@ -74,18 +74,18 @@ abstract ASObject(flash.utils.Object) if (this == null) { return 0; } - if (Std.is(this, Int)) { + if (Std.isOfType(this, Int)) { return cast this; } - if (Std.is(this, Float)) { + if (Std.isOfType(this, Float)) { var v:Float = cast this; return if (Math.isNaN(v)) 0 else Std.int(v); } - if (Std.is(this, String)) { + if (Std.isOfType(this, String)) { var i = Std.parseInt(cast this); return if (i == null) 0 else i; } - if (Std.is(this, Bool)) { + if (Std.isOfType(this, Bool)) { return if (cast this) 1 else 0; } return 0; diff --git a/compat/compat/Attribute.hx b/compat/compat/Attribute.hx new file mode 100644 index 00000000..8226c370 --- /dev/null +++ b/compat/compat/Attribute.hx @@ -0,0 +1,9 @@ +package compat; + +@:transitive abstract Attribute(String) from String to String { + + @:op(a == b) private inline function equal(b: Any): Bool return this == b || (this == '' && b == null); + @:op(a != b) private inline function notEqual(b: Any): Bool return !equal(b); + @:op(a + b) private inline function add(b: String): String return this + b; + +} \ No newline at end of file diff --git a/compat/compat/RegExp.hx b/compat/compat/RegExp.hx index b91429af..824ca31b 100644 --- a/compat/compat/RegExp.hx +++ b/compat/compat/RegExp.hx @@ -1,5 +1,7 @@ package compat; - +#if js +import js.lib.RegExp.RegExpMatch; +#end import haxe.extern.EitherType; import haxe.Constraints.Function; @@ -11,7 +13,7 @@ abstract RegExp(RegExpImpl) { this = new RegExpImpl(pattern, options); } - public inline function exec(s:String) /*infer the type from `return`*/ { + public inline function exec(s:String): RegExpResult { return this.exec(s); } @@ -41,3 +43,13 @@ abstract RegExp(RegExpImpl) { return (cast s).split(this); } } + +#if js +abstract RegExpResult(RegExpMatch) from RegExpMatch to RegExpMatch { + + @:to public function toString(): Null return this != null ? this[0] : null; + +} +#else +typedef RegExpResult = Dynamic; +#end \ No newline at end of file diff --git a/compat/compat/XML.hx b/compat/compat/XML.hx index 91dac969..bc369a54 100644 --- a/compat/compat/XML.hx +++ b/compat/compat/XML.hx @@ -21,7 +21,7 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { #end } - public inline function attribute(name:String):String { + public inline function attribute(name:String):Attribute { #if flash return this.attribute(name).toString(); #else @@ -30,6 +30,14 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { #end } + public inline function attributes() { + #if flash + return this.attributes(); + #else + return [for (a in this.attributes()) {name: () -> a, localName: () -> a, toString: () -> a}]; + #end + } + public inline function setAttribute(name:String, value:String):String { #if flash this.attribute(name)[0] = new flash.xml.XML(value); @@ -47,6 +55,14 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { #end } + public function elements():XMLList { + #if flash + return this.elements(); + #else + return [for (x in this.elements()) x]; + #end + } + public function children():XMLList { #if flash return this.children(); @@ -63,6 +79,14 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { #end } + public function name():String { + #if flash + return this.localName(); + #else + return this.nodeName; + #end + } + public function descendants(name:String):XMLList { #if flash return this.descendants(name); @@ -85,7 +109,8 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { #end #if flash inline #end - public function toString():String { + @:to public function toString():String { + if (this == null) return null; #if flash return this.toString(); #else @@ -109,4 +134,7 @@ abstract XML(XMLImpl) from XMLImpl to XMLImpl { return this.toString(); #end } + + public inline function namespace():Any return null; // todo + } diff --git a/compat/compat/XMLList.hx b/compat/compat/XMLList.hx index 6cfb27b1..d54169e1 100644 --- a/compat/compat/XMLList.hx +++ b/compat/compat/XMLList.hx @@ -1,5 +1,6 @@ package compat; +import haxe.Exception; import Xml as StdXml; private typedef XMLListImpl = #if flash flash.xml.XMLList #else Array #end; @@ -14,7 +15,7 @@ abstract XMLList(XMLListImpl) from XMLListImpl to XMLListImpl { } #if flash inline #end - public function attribute(name:String):String { + public function attribute(name:String):Attribute { #if flash return this.attribute(name).toString(); #else @@ -22,6 +23,26 @@ abstract XMLList(XMLListImpl) from XMLListImpl to XMLListImpl { #end } + #if flash inline #end + public function appendChild(v:XML):Void { + #if flash + this.appendChild(v); + #else + for (x in this) x.appendChild(v); + #end + } + + #if flash inline #end + public function children():XMLList { + #if flash + return this.children(); + #else + final r: Array = []; + for (x in this) for (e in x.children()) r.push(e); + return r; + #end + } + #if flash inline #end public function toXMLString():String { #if flash diff --git a/converter.jar b/converter.jar index 7e4b7099..9d93b92d 100644 Binary files a/converter.jar and b/converter.jar differ diff --git a/run.n b/run.n index f9851ecd..59de12b7 100644 Binary files a/run.n and b/run.n differ diff --git a/src/ax3/Context.hx b/src/ax3/Context.hx index 84499f8b..2e6ae291 100644 --- a/src/ax3/Context.hx +++ b/src/ax3/Context.hx @@ -4,6 +4,8 @@ import haxe.extern.EitherType; import haxe.DynamicAccess; import ax3.Utils.printerr; +using StringTools; + enum abstract ToplevelImportKind(String) to String { var Import = "import"; var Using = "using"; @@ -16,6 +18,8 @@ class Context { public function new(config:Config) { this.config = config; + if (config.dataout != null && !config.dataout.endsWith('/')) config.dataout += '/'; + if (config.unpackout != null && !config.unpackout.endsWith('/')) config.unpackout += '/'; } public function reportError(path:String, pos:Int, message:String) { @@ -30,14 +34,24 @@ class Context { } typedef Config = { - var src:EitherType>; - var swc:Array; + var ?src:EitherType>; + var ?swc:Array; var ?skipFiles:Array; var ?hxout:String; var ?injection:InjectionConfig; var ?haxeTypes:DynamicAccess; var ?rootImports:String; var ?settings:Settings; + var ?keepTypes:Bool; + var ?dataout:String; + var ?dataext:Array; + var ?datafiles:Array; + var ?unpackout:String; + var ?unpackswc:Array; + var ?hxoutClean:Bool; + var ?dataoutClean:Bool; + var ?formatter:Bool; + var ?copy:Array<{unit: String, to: String}>; } typedef InjectionConfig = { diff --git a/src/ax3/ExprTyper.hx b/src/ax3/ExprTyper.hx index 4c238bf1..acf40a89 100644 --- a/src/ax3/ExprTyper.hx +++ b/src/ax3/ExprTyper.hx @@ -116,6 +116,10 @@ class ExprTyper { if (expectedType != TTVoid) throw "assert"; mk(TEReturn(keyword, if (eReturned != null) typeExpr(eReturned, currentReturnType) else null), TTVoid, TTVoid); + case ETypeof(keyword, ex): + if (ex == null) throw "assert"; + mk(TETypeof(keyword, typeExpr(ex, TTAny)), TTVoid, TTVoid); + case EThrow(keyword, e): if (expectedType != TTVoid) throw "assert"; mk(TEThrow(keyword, typeExpr(e, TTAny)), TTVoid, TTVoid); @@ -293,7 +297,7 @@ class ExprTyper { case "Object": mk(TEBuiltin(i, "Object"), TTBuiltin, expectedType); case "RegExp": mk(TEBuiltin(i, "RegExp"), TTBuiltin, expectedType); // TODO: actually these must be resolved after everything because they are global idents!!! - case "parseInt": mk(TEBuiltin(i, "parseInt"), TTFun([TTString], TTInt), expectedType); + case "parseInt": mk(TEBuiltin(i, "parseInt"), TTFun([TTString, TTInt], TTInt), expectedType); case "parseFloat": mk(TEBuiltin(i, "parseFloat"), TTFun([TTString], TTNumber), expectedType); case "NaN": mk(TEBuiltin(i, "NaN"), TTNumber, expectedType); case "isNaN": mk(TEBuiltin(i, "isNaN"), TTFun([TTNumber], TTBoolean), expectedType); @@ -451,7 +455,7 @@ class ExprTyper { var type = switch [fieldName, skipParens(obj)] { case [_, {type: TTInt | TTUint | TTNumber}]: getNumericInstanceFieldType(fieldToken, obj.type); - case ["toString", _]: TTFun([], TTString); + case ["toString", _]: TTFun([TTUint], TTString); case ["hasOwnProperty", {type: TTDictionary(keyType, _)}]: TTFun([keyType], TTBoolean); case ["hasOwnProperty", _]: TTFun([TTString], TTBoolean); case ["prototype", _]: tUntypedObject; @@ -534,6 +538,8 @@ class ExprTyper { var field = loop(cls); if (field != null) { return getFieldType(field); + } else if (Lambda.exists(cls.modifiers, function(m) return m.equals(DMDynamic(null)))) { + return TTAny; } return throwErr('Unknown instance field $fieldName on class ${cls.name}', pos); @@ -548,6 +554,7 @@ class ExprTyper { case "child": TTFun([tUntypedObject], TTXMLList); case "childIndex": TTFun([], TTInt); case "children": TTFun([], TTXMLList); + case "namespace": TTFun([], TTAny); case "comments": TTFun([], TTXMLList); case "contains": TTFun([TTXML], TTBoolean); case "name": TTFun([], TTString); @@ -585,6 +592,7 @@ class ExprTyper { function getFunctionInstanceFieldType(field:Token):TType { return switch field.text { case "call" | "apply": TTFunction; + case "length": TTUint; case other: err('Unknown Function instance field: $other', field.pos); TTAny; } } @@ -627,6 +635,7 @@ class ExprTyper { case "indexOf" | "lastIndexOf": TTFun([t, TTInt], TTInt); case "slice": TTFun([TTInt, TTInt], TTArray(t)); case "splice": TTFun([TTInt, TTUint, TTAny], TTArray(t)); + case "removeAt": TTFun([TTInt], t); case "sort": TTFun([TTAny], TTArray(t)); case "sortOn": TTFun([TTString, tUntypedObject], TTArray(t)); case "filter": TTFun([TTFun([t], TTBoolean)], TTArray(t)); // in as3 the `fitler` signature is actually 3-argument, but not in Haxe @@ -648,6 +657,9 @@ class ExprTyper { case "concat": TTFun([TTVector(t)], TTVector(t)); case "reverse": TTFun([], TTVector(t)); case "forEach": TTFun([TTFunction, tUntypedObject], TTVoid); + case "fixed": TTBoolean; + case "removeAt": TTFun([TTInt], t); + case "filter": TTFun([TTFun([t, TTUint, TTVector(t)], TTBoolean), TTObject(TTAny)], TTVector(t)); case other: err('Unknown Vector instance field $other', field.pos); TTAny; } } @@ -683,6 +695,8 @@ class ExprTyper { return switch field.text { case "MIN_VALUE": type; case "MAX_VALUE": type; + case "POSITIVE_INFINITY": type; + case "NEGATIVE_INFINITY": type; case other: err('Unknown field $other on type ${type.getName()}', field.pos); TTAny; } } @@ -694,7 +708,8 @@ class ExprTyper { case {kind: TELiteral(TLSuper(_)), type: TTInst(cls)}: getConstructorType(cls); case _: eobj.type; } - var targs = typeCallArgs(args, callableType); + + var targs = typeCallArgs(args, callableType, e); inline function mkCast(path, t) return { var e = switch targs.args { @@ -758,7 +773,22 @@ class ExprTyper { case {kind: TEDeclRef(path, _), type: TTStatic(cls)}: // ClassName(expr) cast return mkCast(path, TTInst(cls)); - case _: + case {kind: TEXmlChild(child)} if (child.name == "hasSimpleContent"): + type = TTBoolean; + + case {kind: TEXmlChild(child)} if (child.name == "children"): + type = TTXMLList; + + case {kind: TEXmlChild(child)} if (child.name == "child"): + type = TTXMLList; + + case {kind: TEXmlChild(child)} if (child.name == "appendChild"): + type = TTVoid; + + case {kind: TEXmlChild(child)} if (child.name == "namespace"): + type = TTAny; + + case v: err("unknown callable type: " + eobj.type, exprPos(e)); type = TTAny; } @@ -870,8 +900,23 @@ class ExprTyper { return mk(TENew(keyword, obj, args), type, expectedType); } - function typeCallArgs(args:CallArgs, callableType:TType):TCallArgs { + function typeCallArgs(args:CallArgs, callableType:TType, ?e: Expr):TCallArgs { var getExpectedType = switch (callableType) { + case TTXMLList if (e != null): + switch e { + case EField(_, _, ident) if (ident.kind.equals(TkIdent) && ident.text == "hasSimpleContent"): + (i,earg) -> TTBoolean; + case EField(_, _, ident) if (ident.kind.equals(TkIdent) && ident.text == "children"): + (i,earg) -> TTFun([], TTXMLList); + case EField(_, _, ident) if (ident.kind.equals(TkIdent) && ident.text == "child"): + (i,earg) -> TTFun([TTString], TTXMLList); + case EField(_, _, ident) if (ident.kind.equals(TkIdent) && ident.text == "appendChild"): + (i,earg) -> TTFun([TTXML], TTVoid); + case EField(_, _, ident) if (ident.kind.equals(TkIdent) && ident.text == "namespace"): + (i,earg) -> TTFun([], TTAny); + case _: + throwErr("Trying to call an expression of type " + callableType.getName(), args.openParen.pos); + } case TTVoid | TTBoolean | TTNumber | TTInt | TTUint | TTString | TTArray(_) | TTObject(_) | TTXML | TTXMLList | TTRegExp | TTVector(_) | TTInst(_) | TTDictionary(_): throwErr("Trying to call an expression of type " + callableType.getName(), args.openParen.pos); case TTClass: @@ -1179,12 +1224,23 @@ class ExprTyper { }), TTVoid, TTVoid); } - function typeIf(keyword:Token, openParen:Token, econd:Expr, closeParen:Token, ethen:Expr, eelse:Null<{keyword:Token, expr:Expr}>, expectedType:TType):TExpr { + function typeIf( + keyword:Token, + openParen:Token, + econd:Expr, + closeParen:Token, + ethen:Expr, + eelse:Null<{keyword:Token, expr:Expr, semiliconBefore: Bool}>, + expectedType:TType + ):TExpr { if (expectedType != TTVoid) throw "assert"; - var econd = typeExpr(econd, TTBoolean); var ethen = typeExpr(ethen, TTVoid); - var eelse = if (eelse != null) {keyword: eelse.keyword, expr: typeExpr(eelse.expr, TTVoid)} else null; + var eelse = if (eelse != null) { + keyword: eelse.keyword, + expr: typeExpr(eelse.expr, TTVoid), + semiliconBefore: eelse.semiliconBefore + } else null; return mk(TEIf({ syntax: {keyword: keyword, openParen: openParen, closeParen: closeParen}, econd: econd, diff --git a/src/ax3/Filters.hx b/src/ax3/Filters.hx index a5d112fb..1d6a156f 100644 --- a/src/ax3/Filters.hx +++ b/src/ax3/Filters.hx @@ -31,6 +31,7 @@ class Filters { new AddSuperCtorCall(context), new RewriteBlockBinops(context), new RewriteNewArray(context), + new RewriteTypesWithComment(context), new RewriteDelete(context), new RewriteArrayAccess(context), new RewriteAs(context), diff --git a/src/ax3/GenHaxe.hx b/src/ax3/GenHaxe.hx index 21236887..b40acfeb 100644 --- a/src/ax3/GenHaxe.hx +++ b/src/ax3/GenHaxe.hx @@ -1,5 +1,6 @@ package ax3; +import ax3.Token.TokenKind; import ax3.filters.RewriteForIn; import ax3.ParseTree; import ax3.TypedTree; @@ -11,6 +12,16 @@ using StringTools; @:nullSafety class GenHaxe extends PrinterBase { + + static var REPLACE_CONTROL_CHAR: Map = [ + "\\b" => 0x08, + "\\f" => 0x0C, + "\\u00A0" => 160, // non breaking space + "\\u2028" => 8232, // line seperator + "\\u2029" => 8233, // paragraph seperator + "\\u3000" => 12288 // ideographic space + ]; + @:nullSafety(Off) var currentModule:TModule; final context:Context; @@ -18,6 +29,7 @@ class GenHaxe extends PrinterBase { public function new(context) { super(); this.context = context; + importError(); } inline function throwError(pos:Int, msg:String):Dynamic { @@ -305,7 +317,7 @@ class GenHaxe extends PrinterBase { true; }; - if (addMeta) buf.add("@:flash.property "); + if (addMeta) buf.add("@:flash.property @:isVar "); if (p.isPublic) buf.add("public "); if (p.isStatic) buf.add("static "); buf.add("var "); @@ -358,14 +370,20 @@ class GenHaxe extends PrinterBase { printTrivia(trailTrivia); case TFGetter(f): - var kwd = if (f.isInline) "inline function" else "function"; + var kwd = ""; + if (f.haxeProperty != null && f.haxeProperty.isPublic) kwd += "public "; + if (f.isInline) kwd += "inline "; + kwd += "function"; printTextWithTrivia(kwd, f.syntax.functionKeyword); printTokenTrivia(f.syntax.accessorKeyword); printTextWithTrivia("get_" + f.name, f.syntax.name); printSignature(f.fun.sig, NoVoid); printExpr(f.fun.expr); case TFSetter(f): - var kwd = if (f.isInline) "inline function" else "function"; + var kwd = ""; + if (f.haxeProperty != null && f.haxeProperty.isPublic) kwd += "public "; + if (f.isInline) kwd += "inline "; + kwd += "function"; printTextWithTrivia(kwd, f.syntax.functionKeyword); printTokenTrivia(f.syntax.accessorKeyword); printTextWithTrivia("set_" + f.name, f.syntax.name); @@ -379,7 +397,7 @@ class GenHaxe extends PrinterBase { printVarKind(v.kind, v.init == null /* `final` must be immediately initialized */); printTextWithTrivia(v.name, v.syntax.name); - var skipTypeHint = v.isInline && v.init != null && canSkipTypeHint(v.type, v.init.expr); + var skipTypeHint = context.config.keepTypes != true && v.isInline && v.init != null && canSkipTypeHint(v.type, v.init.expr); if (!skipTypeHint) { // TODO: don't lose the typehint's trivia printTypeHint({type: v.type, syntax: v.syntax.type}); @@ -423,8 +441,13 @@ class GenHaxe extends PrinterBase { printTypeHint({type: arg.type, syntax: hint}); if (init != null) printVarInit(init, false, arg.type); - case TArgRest(dots, _): + case TArgRest(dots, _, hint): + #if (haxe_ver >= 4.20) + printTextWithTrivia('...' + arg.name, arg.syntax.name); + printTypeHint({type: arg.type, syntax: hint}); + #else throwError(dots.pos, "Unprocessed rest arguments"); + #end } if (arg.comma != null) printComma(arg.comma); } @@ -491,7 +514,7 @@ class GenHaxe extends PrinterBase { } static function makeFQN(cls:TClassOrInterfaceDecl) { - var packName = cls.parentModule.parentPack.name; + var packName = cls.parentModule == null ? "" : cls.parentModule.parentPack.name; return if (packName == "") cls.name else packName + "." + cls.name; } @@ -603,6 +626,7 @@ class GenHaxe extends PrinterBase { case TEArrayDecl(d): printArrayDecl(d); case TEVectorDecl(v): throw "assert"; case TEReturn(keyword, e): printTextWithTrivia("return", keyword); if (e != null) printExpr(e); + case TETypeof(keyword, e): printTextWithTrivia("typeof", keyword); printExpr(e); case TEThrow(keyword, e): printTextWithTrivia("throw", keyword); printExpr(e); case TEDelete(keyword, e): throw "assert"; case TEBreak(keyword): printTextWithTrivia("break", keyword); @@ -663,7 +687,7 @@ class GenHaxe extends PrinterBase { // TODO: this is hacky (builtins in general are hacky...) name = switch name { case - "Std.is" | "Std.downcast" | "Std.int" | "Std.string" | "String" + "Std.isOfType" | "cast" | "Std.int" | "Std.string" | "String" | "flash.Lib.getTimer" | "flash.Lib.getURL" | "Reflect.deleteField" | "Type.createInstance"| "Type.resolveClass" | "Type.getClassName" | "Type.getClass" | "haxe.Json" | "Reflect.compare" | "Reflect.isFunction" | "Math.POSITIVE_INFINITY" | "Math.NEGATIVE_INFINITY" @@ -683,11 +707,12 @@ class GenHaxe extends PrinterBase { "Vector"; case "Array": "Array"; case "RegExp": "compat.RegExp"; - case "parseInt": "Std.parseInt"; + case "parseInt": "ASCompat.parseInt"; case "parseFloat": "Std.parseFloat"; case "NaN": "Math.NaN"; case "isNaN": "Math.isNaN"; - case "escape": "escape"; + case "escape": "ASCompat.escape"; + case "unescape": "ASCompat.unescape"; case "arguments": "/*TODO*/arguments"; case "trace": "trace"; case "untyped __global__": "untyped __global__"; @@ -800,7 +825,7 @@ class GenHaxe extends PrinterBase { | TELocal(_) // no way these can even appear here so we could as well throw an assertion failure here - | TEReturn(_) | TEThrow(_) | TEDelete(_) | TEBreak(_) | TEContinue(_) + | TEReturn(_) | TETypeof(_) | TEThrow(_) | TEDelete(_) | TEBreak(_) | TEContinue(_) | TEWhile(_) | TEDoWhile(_) | TEFor(_) | TEForIn(_) | TEForEach(_) | TEHaxeFor(_) | TELocalFunction(_) | TEVars(_) | TEBlock(_) | TESwitch(_) | TECondCompValue(_) | TECondCompBlock(_) | TETry(_) | TEUseNamespace(_) @@ -923,6 +948,7 @@ class GenHaxe extends PrinterBase { printCloseParen(i.syntax.closeParen); printExpr(i.ethen); if (i.eelse != null) { + if (i.eelse.semiliconBefore) buf.add(";\n"); printTextWithTrivia("else", i.eelse.keyword); printExpr(i.eelse.expr); } @@ -1023,7 +1049,7 @@ class GenHaxe extends PrinterBase { printTextWithTrivia(v.v.name, v.syntax.name); // TODO: don't lose the typehint's trivia - var skipTypeHint = v.init != null && canSkipTypeHint(v.v.type, v.init.expr); + var skipTypeHint = context.config.keepTypes != true && v.init != null && canSkipTypeHint(v.v.type, v.init.expr); if (!skipTypeHint) { printTypeHint({type: v.v.type, syntax: v.syntax.type}); } @@ -1044,6 +1070,8 @@ class GenHaxe extends PrinterBase { return false; case TELiteral(TLInt(_)) if (expectedType.match(TTNumber | TTUint)): return false; + case TELiteral(TLNumber(_)): + return false; case TECall({kind: TEBuiltin(_, "Vector.convert")}, _): // this one depends on the expected type return false; @@ -1110,7 +1138,7 @@ class GenHaxe extends PrinterBase { case TLUndefined(syntax): printTextWithTrivia("/*undefined*/null", syntax); case TLInt(syntax): printTextWithTrivia(syntax.text, syntax); case TLNumber(syntax): printTextWithTrivia(syntax.text, syntax); - case TLString(syntax): printTextWithTrivia(syntax.text, syntax); + case TLString(syntax): printTextWithTrivia(replaceControlChars(syntax.text, syntax.kind), syntax); case TLRegExp(syntax): throw "assert"; } } @@ -1160,6 +1188,27 @@ class GenHaxe extends PrinterBase { inline function importVector() { context.addToplevelImport("flash.Vector", Import); } + + inline function importError() { + context.addToplevelImport("flash.errors.Error", Import); + } + + static function replaceControlChars(s: String, kind: TokenKind): String { + var r: Null = REPLACE_CONTROL_CHAR[s.substr(1, s.length - 2)]; + return if (r != null) 'String.fromCharCode($r)' + else switch kind { + case TkStringDouble: + replaces(s, [for (k in REPLACE_CONTROL_CHAR.keys()) k => '" + String.fromCharCode(' + REPLACE_CONTROL_CHAR[k] + ') + "']); + case TkStringSingle: + replaces(s, [for (k in REPLACE_CONTROL_CHAR.keys()) k => "${String.fromCharCode(" + REPLACE_CONTROL_CHAR[k] + ")}"]); + case _: s; + } + } + + static function replaces(s: String, m: Map): String { + for (k in m.keys()) s = @:nullSafety(Off) s.replace(k, m[k]); + return s; + } } private enum ReturnTypeRule { diff --git a/src/ax3/Main.hx b/src/ax3/Main.hx index 21d8b378..f52dc0ff 100644 --- a/src/ax3/Main.hx +++ b/src/ax3/Main.hx @@ -1,23 +1,36 @@ package ax3; +import sys.io.File; import sys.FileSystem; +import haxe.zip.Reader; +import haxe.io.Path; + import ax3.Utils.*; import ax3.Context; +using StringTools; + class Main { static var ctx:Context; static var skipFiles = new Map(); static function main() { + var total = stamp(); + var args = Sys.args(); - if (args.length != 1) { - throw "invalid args"; - } - var config:Config = haxe.Json.parse(sys.io.File.getContent(args[0])); - ctx = new Context(config); + if (args.length != 1) error('invalid args'); - var total = stamp(); + var config:Config = haxe.Json.parse(File.getContent(args[0])); + checkSet(config.src, 'src'); + checkSet(config.hxout, 'hxout'); + checkSet(config.swc, 'swc'); + + ctx = new Context(config); + clean(); + copy(); + unpackswc(); + copydatafiles(); var tree = new TypedTree(); @@ -25,8 +38,10 @@ class Main { SWCLoader.load(tree, config.haxeTypes, config.swc); Timers.swcs = stamp() - t; + if (ctx.config.dataout != null) FileSystem.createDirectory(ctx.config.dataout); + var files = []; - var srcs = if (Std.is(config.src, String)) [config.src] else config.src; + var srcs = if (Std.isOfType(config.src, String)) [config.src] else config.src; for (src in srcs) { walk(src, files); } @@ -35,7 +50,7 @@ class Main { Typer.process(ctx, tree, files); Timers.typing = stamp() - t; - // sys.io.File.saveContent("structure.txt", tree.dump()); + // File.saveContent("structure.txt", tree.dump()); t = stamp(); Filters.run(ctx, tree); @@ -45,7 +60,7 @@ class Main { t = stamp(); for (packName => pack in tree.packages) { - var dir = haxe.io.Path.join({ + var dir = Path.join({ var parts = packName.split("."); parts.unshift(haxeDir); parts; @@ -58,7 +73,7 @@ class Main { gen.writeModule(mod); var out = gen.toString(); var path = dir + "/" + mod.name + ".hx"; - sys.io.File.saveContent(path, out); + File.saveContent(path, out); } } @@ -67,46 +82,75 @@ class Main { imports.push('$kind $path;'); } if (config.rootImports != null) { - imports.push(sys.io.File.getContent(config.rootImports)); + imports.push(File.getContent(config.rootImports)); } if (imports.length > 0) { imports.unshift("#if !macro"); imports.push("#end"); - sys.io.File.saveContent(haxe.io.Path.join([haxeDir, "import.hx"]), imports.join("\n")); + File.saveContent(Path.join([haxeDir, "import.hx"]), imports.join("\n")); } Timers.output = stamp() - t; + formatter(); + total = (stamp() - total); + if (Timers.copy > 0) + print("copy " + Timers.copy); + if (Timers.unpack > 0) + print("unpack " + Timers.unpack); print("parsing " + Timers.parsing); print("swcs " + Timers.swcs); print("typing " + Timers.typing); print("filters " + Timers.filters); print("output " + Timers.output); + if (Timers.formatter > 0) + print("formatter " + Timers.formatter); print("-- TOTAL " + total); } + static function checkSet(value: Any, name: String): Void { + if (value == null) error('$name not set'); + } + + static function error(message: String): Void { + printerr(message); + Sys.exit(1); + } + static function shouldSkip(path:String):Bool { var skipFiles = ctx.config.skipFiles; return skipFiles != null && skipFiles.contains(path); } static function walk(dir:String, files:Array) { - function loop(dir) { - for (name in FileSystem.readDirectory(dir)) { - var absPath = dir + "/" + name; - if (FileSystem.isDirectory(absPath)) { - walk(absPath, files); - } else if (StringTools.endsWith(name, ".as") && !shouldSkip(absPath)) { + for (name in FileSystem.readDirectory(dir)) { + var absPath = dir + "/" + name; + if (FileSystem.isDirectory(absPath)) { + walk(absPath, files); + } else if (!shouldSkip(absPath)) { + final extIndex = name.lastIndexOf('.') + 1; + if (extIndex <= 1) continue; + final ext = name.substr(extIndex); + if (ext == "as") { var file = parseFile(absPath); if (file != null) { files.push(file); } + } else if ( + ctx.config.dataout != null && ctx.config.dataext != null && + ctx.config.dataext.indexOf(ext) != -1 + ) { + print('Walk copy file ' + absPath); + final t = stamp(); + final dst = ctx.config.dataout + name; + if (FileSystem.exists(dst)) print('File exists, overwrite'); + File.copy(absPath, dst); + Timers.copy += stamp() - t; } } } - loop(dir); } static function parseFile(path:String):ParseTree.File { @@ -140,4 +184,82 @@ class Main { throw '$path not the same'; } } + + static function unpackswc() { + if (ctx.config.unpackswc == null && ctx.config.unpackout == null) return; + final t = stamp(); + if (!FileSystem.exists(ctx.config.unpackout)) FileSystem.createDirectory(ctx.config.unpackout); + + for (swc in ctx.config.unpackswc) + for (entry in Reader.readZip(File.read(swc))) + if (entry.fileName == 'library.swf') { + print('Unpack ' + swc); + File.saveBytes(ctx.config.unpackout + fileName(swc) + 'swf', Reader.unzip(entry)); + break; + } + Timers.unpack = stamp() - t; + } + + static function fileName(path: String): String { + return path.substring(path.lastIndexOf('/'), path.lastIndexOf('.') + 1); + } + + static function copydatafiles(): Void { + if (ctx.config.datafiles != null && ctx.config.dataout != null && ctx.config.datafiles.length > 0) { + final t = stamp(); + for (path in ctx.config.datafiles) { + final fileName = path.substr(path.lastIndexOf('/') + 1); + print('Copy file $fileName'); + File.copy(path, ctx.config.dataout + fileName); + } + Timers.copy += stamp() - t; + } + } + + static function clean(): Void { + if (ctx.config.dataoutClean && ctx.config.dataout != null) deleteDirRecursively(ctx.config.dataout); + if (ctx.config.hxoutClean) deleteDirRecursively(ctx.config.hxout); + } + + static function deleteDirRecursively(path: String): Void { + if (FileSystem.exists(path) && FileSystem.isDirectory(path)) { + for (entry in FileSystem.readDirectory(path)) { + if (FileSystem.isDirectory(path + '/' + entry)) { + deleteDirRecursively(path + '/' + entry); + FileSystem.deleteDirectory(path + '/' + entry); + } else { + FileSystem.deleteFile(path + '/' + entry); + } + } + } + } + + static function formatter(): Void { + if (!ctx.config.formatter) return; + final t = stamp(); + final args = ['run', 'formatter', '-s', ctx.config.hxout]; + print('haxelib ' + args.join(' ')); + Sys.command('haxelib', args); + Timers.formatter = stamp() - t; + } + + static function copy(): Void { + if (ctx.config.copy != null && ctx.config.copy.length > 0) { + final t = stamp(); + for (copy in ctx.config.copy) copyUnit(copy.unit, copy.to); + Timers.copy += stamp() - t; + } + } + + static function copyUnit(unit: String, to: String): Void { + if (FileSystem.isDirectory(unit)) { + if (!unit.endsWith('/')) unit += '/'; + if (!to.endsWith('/')) to += '/'; + if (!FileSystem.exists(to)) FileSystem.createDirectory(to); + for (u in FileSystem.readDirectory(unit)) copyUnit(unit + u, to + u); + } else { + print('Copy file $unit to $to'); + File.copy(unit, to); + } + } } diff --git a/src/ax3/ParseTree.hx b/src/ax3/ParseTree.hx index 38414c41..7e6fbf0a 100644 --- a/src/ax3/ParseTree.hx +++ b/src/ax3/ParseTree.hx @@ -48,7 +48,7 @@ class ParseTree { return switch (e) { case EIdent(t) | ELiteral(LString(t) | LDecInt(t) | LHexInt(t) | LFloat(t) | LRegExp(t)): t.pos; case ECall(e, _) | EArrayAccess(e, _, _, _) | EField(e, _, _) | EXmlAttr(e, _, _, _) | EXmlAttrExpr(e, _, _, _, _, _) | EXmlDescend(e, _, _): exprPos(e); - case ETry(keyword, _) | EFunction(keyword, _) | EIf(keyword, _, _, _, _, _) | ESwitch(keyword, _) | EReturn(keyword, _) | EThrow(keyword, _) | EDelete(keyword, _) | EBreak(keyword) | EContinue(keyword) | ENew(keyword, _, _) | EVectorDecl(keyword, _, _): keyword.pos; + case ETry(keyword, _) | EFunction(keyword, _) | EIf(keyword, _, _, _, _, _) | ESwitch(keyword, _) | EReturn(keyword, _) | EThrow(keyword, _) | EDelete(keyword, _) | EBreak(keyword) | EContinue(keyword) | ENew(keyword, _, _) | ETypeof(keyword, _) | EVectorDecl(keyword, _, _): keyword.pos; case EParens(openParen, _, _): openParen.pos; case EArrayDecl(d): d.openBracket.pos; case EBlock(b): b.openBrace.pos; @@ -408,6 +408,7 @@ enum Expr { EArrayAccess(e:Expr, openBracket:Token, eindex:Expr, closeBracket:Token); EArrayDecl(d:ArrayDecl); EReturn(keyword:Token, e:Null); + ETypeof(keyword:Token, e:Expr); EThrow(keyword:Token, e:Expr); EDelete(keyword:Token, e:Expr); EBreak(keyword:Token); @@ -420,7 +421,7 @@ enum Expr { EXmlDescend(e:Expr, dotDot:Token, childName:Token); EBlock(b:BracedExprBlock); EObjectDecl(openBrace:Token, fields:Separated, closeBrace:Token); - EIf(keyword:Token, openParen:Token, econd:Expr, closeParen:Token, ethen:Expr, eelse:Null<{keyword:Token, expr:Expr}>); + EIf(keyword:Token, openParen:Token, econd:Expr, closeParen:Token, ethen:Expr, eelse:Null<{keyword:Token, expr:Expr, semiliconBefore: Bool}>); ETernary(econd:Expr, question:Token, ethen:Expr, colon:Token, eelse:Expr); EWhile(w:While); EDoWhile(w:DoWhile); diff --git a/src/ax3/Parser.hx b/src/ax3/Parser.hx index 56b92266..766bbfe7 100644 --- a/src/ax3/Parser.hx +++ b/src/ax3/Parser.hx @@ -230,6 +230,7 @@ class Parser { var modifiers = []; var namespace = null; var metadata = parseSequence(parseOptionalMetadata); + var namespaceSetted: Bool = false; while (true) { var token = scanner.advance(); @@ -245,13 +246,21 @@ class Parser { switch token.text { case "public": + if (namespaceSetted) throw "Namespace setted"; modifiers.push(FMPublic(scanner.consume())); + namespaceSetted = true; case "private": + if (namespaceSetted) throw "Namespace setted"; modifiers.push(FMPrivate(scanner.consume())); + namespaceSetted = true; case "protected": + if (namespaceSetted) throw "Namespace setted"; modifiers.push(FMProtected(scanner.consume())); + namespaceSetted = true; case "internal": + if (namespaceSetted) throw "Namespace setted"; modifiers.push(FMInternal(scanner.consume())); + namespaceSetted = true; case "override": modifiers.push(FMOverride(scanner.consume())); case "static": @@ -265,8 +274,15 @@ class Parser { case "function": return MField(parseClassFunNext(metadata, namespace, modifiers, scanner.consume())); case text: - if (modifiers.length > 0) - throw "Modifiers without declaration"; + if (modifiers.length > 0) { + if (!namespaceSetted) { + modifiers.push(FMPublic(scanner.consume())); + namespaceSetted = true; + continue; + } else { + throw "Modifiers without declaration"; + } + } if (metadata.length > 0) throw "Metadata without declaration"; if (namespace != null) @@ -475,6 +491,11 @@ class Parser { scanner.consume(); case TkBraceClose: null; // if the next token is `}` then okay, allow no semicolon + case TkColonColon: + Utils.printerr('Skip expression ' + scanner.consume().toString()); + scanner.advance(); + Utils.printerr('Skip expression ' + scanner.consume().toString()); + return {expr: EIdent(new Token(-1, TkIdent, "null", [], [])), semicolon: null}; // todo case _ if (scanner.lastConsumedToken.kind != TkBraceClose): throw "Semicolon expected after block expression"; case _: @@ -560,6 +581,8 @@ class Parser { switch consumedToken.text { case "new": return parseNewNext(consumedToken, allowComma); + case "typeof": + return ETypeof(consumedToken, parseExpr(allowComma)); case "return": return EReturn(consumedToken, parseOptionalExpr(allowComma)); case "throw": @@ -730,7 +753,18 @@ class Parser { var ethen = parseExpr(true); var eelse = switch scanner.advance() { case {kind: TkIdent, text: "else"}: - {keyword: scanner.consume(), expr: parseExpr(true)}; + {keyword: scanner.consume(), expr: parseExpr(true), semiliconBefore: false}; + case {kind: TkSemicolon, text: ";"}: + scanner.savePos(); + scanner.consume(); + switch scanner.advance() { + case {kind: TkIdent, text: "else"}: + {keyword: scanner.consume(), expr: parseExpr(true), semiliconBefore: true}; + case _: + scanner.cancelConsume(); + scanner.restorePos(); + null; + } case _: null; } diff --git a/src/ax3/Printer.hx b/src/ax3/Printer.hx index ce5ef6b6..b7a9c87f 100644 --- a/src/ax3/Printer.hx +++ b/src/ax3/Printer.hx @@ -329,6 +329,9 @@ class Printer extends PrinterBase { case EReturn(keyword, e): printTextWithTrivia("return", keyword); if (e != null) printExpr(e); + case ETypeof(keyword, e): + printTextWithTrivia("typeof", keyword); + printExpr(e); case EThrow(keyword, e): printTextWithTrivia("throw", keyword); printExpr(e); diff --git a/src/ax3/SWCLoader.hx b/src/ax3/SWCLoader.hx index 8d5544c4..567000a1 100644 --- a/src/ax3/SWCLoader.hx +++ b/src/ax3/SWCLoader.hx @@ -248,11 +248,13 @@ class SWCLoader { } } + var dynList = [{p: 'flash.net', c: 'URLVariables'}, {p: 'flash.display', c: 'ShaderData'}]; + var tDecl:TClassOrInterfaceDecl = { kind: clsKind, syntax: null, metadata: [], - modifiers: [], + modifiers: Lambda.exists(dynList, function(d) return d.p == packName && d.c == className) ? [DMDynamic(null)] : [], parentModule: null, name: n.name, members: members @@ -421,6 +423,7 @@ class SWCLoader { case ["", "XML"]: TTXML; case ["", "XMLList"]: TTXMLList; case ["", "RegExp"]: TTRegExp; + case ["", "Namespace"]: TTAny; case ["flash.utils", "Dictionary"]: tUntypedDictionary; case ["__AS3__.vec", "Vector"]: TTVector(TTAny); // hope that's correct case ["mx.core" | "mx.managers", _]: TTAny; // TODO: hacky hack diff --git a/src/ax3/Scanner.hx b/src/ax3/Scanner.hx index b3ede21c..3724cec9 100644 --- a/src/ax3/Scanner.hx +++ b/src/ax3/Scanner.hx @@ -14,12 +14,16 @@ class Scanner { var text:String; var end:Int; var pos:Int; + var prevPos:Int = -1; var tokenStartPos:Int; var leadTrivia:Array; var lastPos:Int; + var prevLastPos:Int = -1; var lastToken:Token; + var prevConsumedToken:Token; var lastMode:ScanMode; + var prevLastMode:ScanMode; public var lastConsumedToken(default,null):Token; @@ -47,12 +51,35 @@ class Scanner { } public function consume():Token { + prevLastMode = lastMode; + prevConsumedToken = lastConsumedToken; lastConsumedToken = lastToken; lastToken = null; lastMode = null; return lastConsumedToken; } + public function cancelConsume():Token { + if (prevConsumedToken == null) return lastConsumedToken; + lastMode = prevLastMode; + lastToken = lastConsumedToken; + lastConsumedToken = prevConsumedToken; + prevConsumedToken = null; + prevLastMode = null; + return lastToken; + } + + public function savePos(): Void { + prevPos = pos; + prevLastPos = lastPos; + } + + public function restorePos(): Void { + if (prevPos == -1) return; + pos = prevPos; + prevLastPos = lastPos; + } + function scanTrivia(breakOnNewLine:Bool):Array { var trivia = []; while (pos < end) { @@ -582,6 +609,8 @@ class Scanner { case "t".code: case "n".code: case "r".code: + case "b".code: + case "f".code: case "\\".code: case "/".code: case "\"".code: diff --git a/src/ax3/Timers.hx b/src/ax3/Timers.hx index c5b2228d..16445d9e 100644 --- a/src/ax3/Timers.hx +++ b/src/ax3/Timers.hx @@ -1,9 +1,12 @@ package ax3; class Timers { + public static var copy:Float = 0; + public static var unpack:Float = 0; public static var parsing:Float = 0; public static var swcs:Float = 0; public static var typing:Float = 0; public static var filters:Float = 0; public static var output:Float = 0; + public static var formatter:Float = 0; } diff --git a/src/ax3/TokenTools.hx b/src/ax3/TokenTools.hx index 5a88248f..dd4790ff 100644 --- a/src/ax3/TokenTools.hx +++ b/src/ax3/TokenTools.hx @@ -33,6 +33,7 @@ class TokenTools { public static inline function mkComma() return new Token(0, TkComma, ",", [], []); public static inline function mkDot() return new Token(0, TkDot, ".", [], []); public static inline function mkSemicolon() return new Token(0, TkSemicolon, ";", [], []); + public static inline function mkString(s) return new Token(0, TkStringDouble, '"$s"', [], []); public static inline function addTrailingWhitespace(t:Token):Token { t.trailTrivia.push(whitespace); diff --git a/src/ax3/TypedTree.hx b/src/ax3/TypedTree.hx index 86b0e9c9..cc9f95f7 100644 --- a/src/ax3/TypedTree.hx +++ b/src/ax3/TypedTree.hx @@ -29,6 +29,37 @@ class TypedTree { return mod.pack.decl; } + public function getType(s: String): TType { + s = StringTools.trim(s); + final i = s.indexOf('<'); + return i == -1 ? switch s { + case 'void': TTVoid; + case 'Boolean': TTBoolean; + case 'Number': TTNumber; + case 'int': TTInt; + case 'uint': TTUint; + case 'String': TTString; + case 'Class': TTClass; + case 'Function': TTFunction; + case 'XML': TTXML; + case 'XMLList': TTXMLList; + case 'RegExp': TTRegExp; + case _: TTInst(getByFullName(s)); + } : switch s.substr(0, i) { + case 'Array': TTArray(getType(s.substring(i + 1, s.lastIndexOf('>')))); + case 'Object': TTObject(getType(s.substring(i + 1, s.lastIndexOf('>')))); + case 'Dictionary': + final comma = s.indexOf(','); + TTDictionary(getType(s.substring(i + 1, comma)), getType(s.substring(comma + 1, s.lastIndexOf('>')))); + case _: throw 'Not supported type: $s'; + } + } + + public function getByFullName(name: String): TClassOrInterfaceDecl { + final i = name.lastIndexOf('.'); + return getClassOrInterface(name.substring(0, i), name.substring(i + 1)); + } + public function getClassOrInterface(packName:String, name:String):TClassOrInterfaceDecl { return switch getDecl(packName, name).kind { case TDClassOrInterface(c): c; @@ -536,6 +567,7 @@ enum TExprKind { TEArrayDecl(a:TArrayDecl); TEVectorDecl(v:TVectorDecl); TEReturn(keyword:Token, e:Null); + TETypeof(keyword:Token, e:TExpr); TEThrow(keyword:Token, e:TExpr); TEDelete(keyword:Token, e:TExpr); TEBreak(keyword:Token); @@ -746,7 +778,7 @@ typedef TIf = { }; var econd:TExpr; var ethen:TExpr; - var eelse:Null<{keyword:Token, expr:TExpr}>; + var eelse:Null<{keyword:Token, expr:TExpr, semiliconBefore: Bool}>; } typedef TCallArgs = { diff --git a/src/ax3/TypedTreeTools.hx b/src/ax3/TypedTreeTools.hx index 9a81c024..adb1f955 100644 --- a/src/ax3/TypedTreeTools.hx +++ b/src/ax3/TypedTreeTools.hx @@ -219,7 +219,7 @@ class TypedTreeTools { case TECast(c): c.syntax.path.first.pos; case TEArrayDecl(a): a.syntax.openBracket.pos; case TEVectorDecl(v): v.syntax.newKeyword.pos; - case TEReturn(keyword, _) | TEThrow(keyword, _) | TEDelete(keyword, _) | TEBreak(keyword) | TEContinue(keyword): keyword.pos; + case TEReturn(keyword, _) | TETypeof(keyword, _) | TEThrow(keyword, _) | TEDelete(keyword, _) | TEBreak(keyword) | TEContinue(keyword): keyword.pos; case TEVars(VVar(t) | VConst(t), _): t.pos; case TEObjectDecl(o): o.syntax.openBrace.pos; case TEArrayAccess(a): a.syntax.openBracket.pos; @@ -365,7 +365,7 @@ class TypedTreeTools { case TECast(c): fromDotPath(c.syntax.path); case TEArrayDecl(a): r(a.syntax.openBracket); case TEVectorDecl(v): r(v.syntax.newKeyword); - case TEReturn(keyword, _) | TEThrow(keyword, _) | TEDelete(keyword, _) | TEBreak(keyword) | TEContinue(keyword): r(keyword); + case TEReturn(keyword, _) | TETypeof(keyword, _) | TEThrow(keyword, _) | TEDelete(keyword, _) | TEBreak(keyword) | TEContinue(keyword): r(keyword); case TEVars(VVar(t) | VConst(t), _): r(t); case TEObjectDecl(o): r(o.syntax.openBrace); case TEArrayAccess(a): processLeadingToken(r, a.eobj); @@ -432,7 +432,7 @@ class TypedTreeTools { case TEArrayDecl(a): r(a.syntax.closeBracket); case TEVectorDecl(v): r(v.elements.syntax.closeBracket); case TEBreak(keyword) | TEContinue(keyword) | TEReturn(keyword, null): r(keyword); - case TEReturn(_, e) | TEThrow(_, e) | TEDelete(_, e): processTrailingToken(r, e); + case TEReturn(_, e) | TETypeof(_, e) | TEThrow(_, e) | TEDelete(_, e): processTrailingToken(r, e); case TEObjectDecl(o): r(o.syntax.closeBrace); case TEArrayAccess(a): r(a.syntax.closeBracket); case TEBlock(block): r(block.syntax.closeBrace); @@ -504,6 +504,8 @@ class TypedTreeTools { TEBuiltin(syntax.clone(), name); case TEReturn(keyword, e): TEReturn(keyword.clone(), if (e == null) null else cloneExpr(e)); + case TETypeof(keyword, e): + TETypeof(keyword.clone(), cloneExpr(e)); case TEThrow(keyword, e): TEThrow(keyword.clone(), cloneExpr(e)); case TEDelete(keyword, e): @@ -649,6 +651,10 @@ class TypedTreeTools { var mapped = f(e); if (mapped == e) e1 else e1.with(kind = TEReturn(keyword, mapped)); + case TETypeof(keyword, e): + var mapped = f(e); + if (mapped == e) e1 else e1.with(kind = TETypeof(keyword, mapped)); + case TEThrow(keyword, e): var mapped = f(e); if (mapped == e) e1 else e1.with(kind = TEThrow(keyword, mapped)); @@ -842,7 +848,7 @@ class TypedTreeTools { f(e.expr); } - case TEReturn(_, e) | TEThrow(_, e) | TEDelete(_, e): + case TEReturn(_, e) | TETypeof(_, e) | TEThrow(_, e) | TEDelete(_, e): f(e); case TEBlock(block): diff --git a/src/ax3/Typer.hx b/src/ax3/Typer.hx index 42bdc916..912f532b 100644 --- a/src/ax3/Typer.hx +++ b/src/ax3/Typer.hx @@ -505,6 +505,7 @@ class Typer { case ["XML"]: TTXML; case ["XMLList"]: TTXMLList; case ["RegExp"]: TTRegExp; + case ['Namespace']: TTAny; case path: TypedTree.declToInst(resolveDotPath(mod, path)); } case TVector(v): diff --git a/src/ax3/filters/AbstractFilter.hx b/src/ax3/filters/AbstractFilter.hx index 836d4f12..e320efef 100644 --- a/src/ax3/filters/AbstractFilter.hx +++ b/src/ax3/filters/AbstractFilter.hx @@ -4,6 +4,7 @@ class AbstractFilter { final context:Context; var currentPath:Null; + var tree: TypedTree; public function new(context) { this.context = context; @@ -74,6 +75,7 @@ class AbstractFilter { } public function run(tree:TypedTree) { + this.tree = tree; for (pack in tree.packages) { var mods = [for (mod in pack) mod]; // save the list because we might modify the package (e.g. rename the module) for (mod in mods) { diff --git a/src/ax3/filters/AddRequiredParens.hx b/src/ax3/filters/AddRequiredParens.hx index 962ab3d5..29287874 100644 --- a/src/ax3/filters/AddRequiredParens.hx +++ b/src/ax3/filters/AddRequiredParens.hx @@ -76,6 +76,7 @@ class AddRequiredParens extends AbstractFilter { // statements case TEReturn(_): mapExpr(loop.bind(_, 100), e); + case TETypeof(_): mapExpr(loop.bind(_, 100), e); case TEThrow(_): mapExpr(loop.bind(_, 100), e); case TEBreak(_): mapExpr(loop.bind(_, 100), e); case TEContinue(_): mapExpr(loop.bind(_, 100), e); diff --git a/src/ax3/filters/ArrayApi.hx b/src/ax3/filters/ArrayApi.hx index bc9a72ba..749b233e 100644 --- a/src/ax3/filters/ArrayApi.hx +++ b/src/ax3/filters/ArrayApi.hx @@ -103,7 +103,7 @@ class ArrayApi extends AbstractFilter { // join with no args case TECall(eMethod = {kind: TEField({type: TTArray(_)}, "join", fieldToken)}, args = {args: []}): e.with(kind = TECall(eMethod, args.with(args = [ - {expr: mk(TELiteral(TLString(new Token(0, TkStringDouble, '","', [], []))), TTString, TTString), comma: null} + {expr: mk(TELiteral(TLString(mkString(','))), TTString, TTString), comma: null} ]))); // push with multiple arguments diff --git a/src/ax3/filters/DateApi.hx b/src/ax3/filters/DateApi.hx index e1ead766..1c8870fe 100644 --- a/src/ax3/filters/DateApi.hx +++ b/src/ax3/filters/DateApi.hx @@ -135,6 +135,15 @@ class DateApi extends AbstractFilter { case "valueOf": e.with(kind = TEField(to, "getTime", new Token(fieldToken.pos, TkIdent, "getTime", fieldToken.leadTrivia, fieldToken.trailTrivia))); + case "dateUTC": + var methodName = "getUTCDate"; + var eMethod = mk(TEField(to, methodName, mkIdent(methodName, fieldToken.leadTrivia)), TTFunction, TTFunction); + e.with(kind = TECall(eMethod, { + openParen: mkOpenParen(), + args: [], + closeParen: mkCloseParen(fieldToken.trailTrivia) + })); + case _: mapExpr(processExpr, e); } diff --git a/src/ax3/filters/ExternModuleLevelImports.hx b/src/ax3/filters/ExternModuleLevelImports.hx index 3e1abe68..4cbb047e 100644 --- a/src/ax3/filters/ExternModuleLevelImports.hx +++ b/src/ax3/filters/ExternModuleLevelImports.hx @@ -90,7 +90,7 @@ class ExternModuleLevelImports extends AbstractFilter { closeBracket: mkCloseBracket() }, eobj: eGlobal, - eindex: mk(TELiteral(TLString(new Token(0, TkStringDouble, '"$name"', [], []))), TTString, TTString) + eindex: mk(TELiteral(TLString(mkString(name))), TTString, TTString) }), TTBuiltin, TTBuiltin); } diff --git a/src/ax3/filters/HaxeProperties.hx b/src/ax3/filters/HaxeProperties.hx index abed0312..194eaac8 100644 --- a/src/ax3/filters/HaxeProperties.hx +++ b/src/ax3/filters/HaxeProperties.hx @@ -45,6 +45,14 @@ class HaxeProperties extends AbstractFilter { if (set) prop.set = true else prop.get = true; + for (m in metadata) { + switch m { + case MetaFlash({name: {text: 'HxForceProp'}}): + isNewProperty = false; + case _: + } + } + return if (isNewProperty) prop else null; } diff --git a/src/ax3/filters/NumberApi.hx b/src/ax3/filters/NumberApi.hx index fe11ee07..5e46d99f 100644 --- a/src/ax3/filters/NumberApi.hx +++ b/src/ax3/filters/NumberApi.hx @@ -31,6 +31,12 @@ class NumberApi extends AbstractFilter { case TEField({kind: TOExplicit(_, {kind: TEBuiltin(builtinToken, "Number")})}, "MIN_VALUE", fieldToken): return mkBuiltin("ASCompat.MIN_FLOAT", TTInt, builtinToken.leadTrivia, fieldToken.trailTrivia); + case TEField({kind: TOExplicit(_, {kind: TEBuiltin(builtinToken, "Number")})}, "POSITIVE_INFINITY", fieldToken): + return mkBuiltin("ASCompat.MAX_FLOAT", TTInt, builtinToken.leadTrivia, fieldToken.trailTrivia); + + case TEField({kind: TOExplicit(_, {kind: TEBuiltin(builtinToken, "Number")})}, "NEGATIVE_INFINITY", fieldToken): + return mkBuiltin("ASCompat.MIN_FLOAT", TTInt, builtinToken.leadTrivia, fieldToken.trailTrivia); + case _: e; }; diff --git a/src/ax3/filters/RestArgs.hx b/src/ax3/filters/RestArgs.hx index 57b6cda4..f5372b3d 100644 --- a/src/ax3/filters/RestArgs.hx +++ b/src/ax3/filters/RestArgs.hx @@ -12,6 +12,7 @@ class RestArgs extends AbstractFilter { case TArgNormal(_): // nothing to do case TArgRest(dots, _, typeHint): + #if (haxe_ver < 4.20) if (typeHint == null) { typeHint = { colon: new Token(0, TkColon, ":", [], []), @@ -53,7 +54,37 @@ class RestArgs extends AbstractFilter { if (fun.expr != null) { // null if interface fun.expr = concatExprs(eArrayInit, fun.expr); } - + #else + if (typeHint != null) { + typeHint.type = TPath({first: new Token(0, TkIdent, "ASAny", [], []), rest: []}); + lastArg.type = TTAny; + } + if (fun.expr != null) { + final argLocal = mk( + TELocal(mkIdent(lastArg.name), lastArg.v), + TTArray(TTAny), + TTArray(TTAny) + ); + final e = mk(TEBinop( + { + kind: TEVars(VConst(new Token(0, TkIdent, '', [], [whitespace])), [{ + v: {name: lastArg.name, type: TTArray(TTAny)}, + syntax: { + name: mkIdent(lastArg.name), + type: typeHint + }, + init: null, + comma: null + }]), + type: TTArray(TTAny), + expectedType: TTArray(TTAny) + }, + OpAssign(new Token(0, TkEquals, "=", [whitespace], [whitespace])), + argLocal + ), TTArray(TTAny), TTArray(TTAny)); + fun.expr = concatExprs(e, fun.expr); + } + #end } } } diff --git a/src/ax3/filters/RewriteArrayAccess.hx b/src/ax3/filters/RewriteArrayAccess.hx index 2f36b66f..b5940cdf 100644 --- a/src/ax3/filters/RewriteArrayAccess.hx +++ b/src/ax3/filters/RewriteArrayAccess.hx @@ -13,12 +13,17 @@ class RewriteArrayAccess extends AbstractFilter { var eindex = processExpr(a.eindex); switch [eobj.type, eindex.type] { - case [TTArray(_) | TTVector(_) | TTXMLList | TTInst({name: "ByteArray", parentModule: {parentPack: {name: "flash.utils"}}}), TTInt | TTUint | TTNumber] - | [TTObject(_), _] + + case [TTArray(_) | TTVector(_) | TTXMLList, TTInt | TTUint | TTNumber] | [TTObject(_), _] : if (eindex.type == TTNumber) reportError(exprPos(e), "Array access using Number index"); e.with(kind = TEArrayAccess(a.with(eobj = eobj, eindex = eindex))); + case [TTInst({name: "ByteArray", parentModule: {parentPack: {name: "flash.utils"}}}), TTInt | TTUint | TTNumber] + : + if (eindex.type == TTNumber) reportError(exprPos(e), "ByteArray access using Number index"); + e.with(kind = TEArrayAccess(a.with(eobj = eobj, eindex = eindex)), type = TTUint); + case [TTArray(_), TTString]: reportError(exprPos(e), "String index used for array access on Array. Did you mean to use Dictionary/Object? Falling back to reflection."); e.with(kind = TEArrayAccess({ diff --git a/src/ax3/filters/RewriteAs.hx b/src/ax3/filters/RewriteAs.hx index 10109698..a6602762 100644 --- a/src/ax3/filters/RewriteAs.hx +++ b/src/ax3/filters/RewriteAs.hx @@ -90,7 +90,8 @@ class RewriteAs extends AbstractFilter { if (needsRetype) e.with(kind = TEHaxeRetype(e)) else e; } - case TTInst(cls): + case TTInst(cls) if (cls.name != 'ByteArray'): + var path = switch (typeRef.syntax) { case TPath(path): path; case _: throw "asset"; @@ -107,6 +108,9 @@ class RewriteAs extends AbstractFilter { e.with(kind = makeAs(eobj, eType, removeLeadingTrivia(e), removeTrailingTrivia(e))); } + case TTInst(cls) if (cls.name == 'ByteArray'): + e.with(kind = TEHaxeRetype(eobj)); + case _: throwError(keyword.pos, "Unsupported `as` expression"); } @@ -136,7 +140,7 @@ class RewriteAs extends AbstractFilter { } static function makeStdDowncast(eObj:TExpr, eType:TExpr, leadTrivia, trailTrivia):TExprKind { - var eMethod = mkBuiltin("Std.downcast", TTFunction, leadTrivia); + var eMethod = mkBuiltin("cast", TTFunction, leadTrivia); return TECall(eMethod, { openParen: mkOpenParen(), args: [ diff --git a/src/ax3/filters/RewriteE4X.hx b/src/ax3/filters/RewriteE4X.hx index 6d7e15ff..da38a0ba 100644 --- a/src/ax3/filters/RewriteE4X.hx +++ b/src/ax3/filters/RewriteE4X.hx @@ -6,6 +6,14 @@ class RewriteE4X extends AbstractFilter { override function processExpr(e:TExpr):TExpr { return switch e.kind { + case TEXmlChild(x) if (x.name == "appendChild" || x.name == "children"): + var eobj = processExpr(x.eobj); + assertIsXML(eobj); + var fieldObject = { + kind: TOExplicit(x.syntax.dot, eobj), + type: eobj.type + }; + return mk(TEField(fieldObject, x.name, mkIdent(x.name)), tMethod, tMethod); case TEXmlChild(x): var eobj = processExpr(x.eobj); assertIsXML(eobj); diff --git a/src/ax3/filters/RewriteIs.hx b/src/ax3/filters/RewriteIs.hx index e0ed51ee..4031455b 100644 --- a/src/ax3/filters/RewriteIs.hx +++ b/src/ax3/filters/RewriteIs.hx @@ -36,7 +36,34 @@ class RewriteIs extends AbstractFilter { })); case _: - final stdIs = mkBuiltin("Std.is", tStdIs, removeLeadingTrivia(e)); + final stdIs = mkBuiltin("Std.isOfType", tStdIs, removeLeadingTrivia(e)); + switch b.kind { + case TEDeclRef(_, {name: 'ByteArray'}): + b.kind = TEDeclRef( + {rest: [], first: mkIdent('ByteArrayData')}, + { + kind: TDClassOrInterface({ + syntax: null, + kind: null, + metadata: [], + modifiers: [], + parentModule: { + isExtern: false, + path: 'flash.utils.ByteArray', + parentPack: new TPackage('flash.utils'), + pack: null, + name: 'flash.utils.ByteArray', + privateDecls: [], + eof: null + }, + name: 'ByteArrayData', + members: [] + }), + name: 'ByteArrayData' + } + ); + case _: + } e.with(kind = TECall(stdIs, { openParen: mkOpenParen(), args: [ diff --git a/src/ax3/filters/RewriteMeta.hx b/src/ax3/filters/RewriteMeta.hx index 39579e08..9e3f6a78 100644 --- a/src/ax3/filters/RewriteMeta.hx +++ b/src/ax3/filters/RewriteMeta.hx @@ -45,7 +45,9 @@ class RewriteMeta extends AbstractFilter { processClass(classInfo.extend.superClass); } + var rmList: Array = []; var needsTypeAwareInterface = false; + var i: Int = 0; for (m in c.members) { switch m { case TMField(field): @@ -63,18 +65,151 @@ class RewriteMeta extends AbstractFilter { case "PreDestroy": needsTypeAwareInterface = true; newMetadata.push(haxeMetaFromFlash(m, "@:preDestroy")); + case "Inspectable": + needsTypeAwareInterface = true; + newMetadata.push(haxeMetaFromFlash(m, "@:inspectable")); + case "Bindable": + needsTypeAwareInterface = true; + newMetadata.push(haxeMetaFromFlash(m, "@:bindable")); + case "Event": + needsTypeAwareInterface = true; + newMetadata.push(haxeMetaFromFlash(m, "@:event")); + case "HxOverride": + needsTypeAwareInterface = true; + field.modifiers.push(FMOverride( + new Token(0, TokenKind.TkIdent, '', [], [new Trivia(TrWhitespace, ' ')]) + )); + case "HxCancelOverride": + needsTypeAwareInterface = true; + field.modifiers = field.modifiers.filter(m -> !m.match(FMOverride(_))); + case "HxRemove": + needsTypeAwareInterface = true; + rmList.push(i); + case "HxArrayArgType": + switch [field.kind, m.args.args.first, m.args.args.rest[0].element] { + case [TFFun(f), ELiteral(LString(_name)), ELiteral(LString(_type))]: + needsTypeAwareInterface = true; + final name: String = rmQuotesAndConvert(_name.text); + final type: String = rmQuotesAndConvert(_type.text); + for (arg in f.fun.sig.args) if (arg.name == name) arg.type = TTArray(TTInst({ + name: type, + syntax: null, + parentModule: null, + modifiers: [], + metadata: [], + members: [], + kind: null + })); + case _: + } + case 'ArrayElementType': + switch [field.kind, m.args.args.first] { + case [TFVar(v), ELiteral(LString(t))]: + v.type = TTArray(TTInst({ + name: rmQuotesAndConvert(t.text), + syntax: null, + parentModule: null, + modifiers: [], + metadata: [], + members: [], + kind: null + })); + case [TFSetter(f), ELiteral(LString(t))]: + f.fun.sig.args[0].type = TTArray(TTInst({ + name: rmQuotesAndConvert(t.text), + syntax: null, + parentModule: null, + modifiers: [], + metadata: [], + members: [], + kind: null + })); + case [TFGetter(f), ELiteral(LString(t))]: + f.fun.sig.ret.type = TTArray(TTInst({ + name: rmQuotesAndConvert(t.text), + syntax: null, + parentModule: null, + modifiers: [], + metadata: [], + members: [], + kind: null + })); + case [TFFun(f), ELiteral(LString(t))]: + if (f.fun.sig.ret.type.match(TTArray(_))) + f.fun.sig.ret.type = TTArray(TTInst({ + name: rmQuotesAndConvert(t.text), + syntax: null, + parentModule: null, + modifiers: [], + metadata: [], + members: [], + kind: null + })); + case _: + reportError(m.name.pos, "Metadata error: ArrayElementType"); + } case "Inline": newMetadata.push(meta); - // // TODO: Haxe `inline` generation is disabled, because Haxe cannot always - // // statically inline methods and emits `Cannot inline a not final return` error - // // we can still detect this by checking the method body and only generate `inline` - // // when possible, but that will require some work that we can do later :-P - - // switch field.kind { - // case TFFun(f): f.isInline = true; - // case TFGetter(f) | TFSetter(f): f.isInline = true; - // case TFVar(_): throwError(m.name.pos, "Inline meta on a var?"); - // } + // // TODO: Haxe `inline` generation is disabled, because Haxe cannot always + // // statically inline methods and emits `Cannot inline a not final return` error + // // we can still detect this by checking the method body and only generate `inline` + // // when possible, but that will require some work that we can do later :-P + + // switch field.kind { + // case TFFun(f): f.isInline = true; + // case TFGetter(f) | TFSetter(f): f.isInline = true; + // case TFVar(_): throwError(m.name.pos, "Inline meta on a var?"); + // } + case "Embed": + var map: Map = [for (a in m.args.args.rest.map(function(f) return f.element).concat([m.args.args.first])) { + var kv: {k: String, v: String} = switch a { + case EBinop(EIdent(n), OpAssign(_), ELiteral(LString(v))): + { k: n.text, v: StringTools.trim(v.text.substr(1, v.text.length - 2)) }; + case _: + reportError(m.name.pos, "Unknown embed metadata format"); null; + } + if (kv != null) kv.k => kv.v; + }]; + switch map['mimeType'] { + case "application/x-font", "application/x-font-truetype": + newMetadata.push(MetaHaxe( + mkIdent('@:font', m.openBracket.leadTrivia, []), + { + openParen: mkOpenParen(), + args: { + first: ELiteral(LString(mkString(map['source']))), + rest: [] + }, + closeParen: mkCloseParen() + } + )); + case "application/octet-stream": + newMetadata.push(MetaHaxe( + mkIdent('@:file', m.openBracket.leadTrivia, []), + { + openParen: mkOpenParen(), + args: { + first: ELiteral(LString(mkString(map['source']))), + rest: [] + }, + closeParen: mkCloseParen() + } + )); + case null: + newMetadata.push(MetaHaxe( + mkIdent('@:bitmap', m.openBracket.leadTrivia, []), + { + openParen: mkOpenParen(), + args: { + first: ELiteral(LString(mkString(map['source']))), + rest: [] + }, + closeParen: mkCloseParen() + } + )); + case t: + reportError(m.name.pos, "Unknown mimeType: " + t); + } case other: reportError(m.name.pos, "Unknown metadata: " + other); newMetadata.push(meta); @@ -86,8 +221,11 @@ class RewriteMeta extends AbstractFilter { field.metadata = newMetadata; case _: } + i++; } + c.members = [for (i in 0...c.members.length) if (rmList.indexOf(i) == -1) c.members[i]]; + if (needsTypeAwareInterface && typeAwareInterface != null && !extendsMagicBaseClass(c)) { final heritage = { iface: { @@ -128,4 +266,17 @@ class RewriteMeta extends AbstractFilter { } return MetaHaxe(mkIdent(metaString, flashMeta.openBracket.leadTrivia, trailTrivia), flashMeta.args); } + + inline static function rmQuotesAndConvert(s: String): String return convertAS3TypeToHaxeType(s.substr(1, s.length - 2)); + + static function convertAS3TypeToHaxeType(s: String): String { + return switch s { + case "Number": "Float"; + case "int": "Int"; + case "uint": "UInt"; + case "Boolean": "Bool"; + case "Array": "Array"; + case v: v; + }; + } } diff --git a/src/ax3/filters/RewriteNewArray.hx b/src/ax3/filters/RewriteNewArray.hx index 93b1dc49..9aa2525f 100644 --- a/src/ax3/filters/RewriteNewArray.hx +++ b/src/ax3/filters/RewriteNewArray.hx @@ -3,7 +3,7 @@ package ax3.filters; class RewriteNewArray extends AbstractFilter { override function processExpr(e:TExpr):TExpr { return switch e.kind { - case TENew(keyword, TNType({type: TTArray(tElem)}), args) if (args.args.length > 0): + case TENew(keyword, TNType({type: TTArray(tElem)}), args) if (args != null && args.args.length > 0): // TODO: insert typecheck where needed `(expr : Array)` switch args.args { case [{expr: {type: TTInt | TTUint | TTNumber}}]: diff --git a/src/ax3/filters/RewriteNonBoolOr.hx b/src/ax3/filters/RewriteNonBoolOr.hx index 0bd2882b..2bfb2dda 100644 --- a/src/ax3/filters/RewriteNonBoolOr.hx +++ b/src/ax3/filters/RewriteNonBoolOr.hx @@ -24,7 +24,8 @@ class RewriteNonBoolOr extends AbstractFilter { ethen: a, eelse: { keyword: mkIdent("else", [whitespace], [whitespace]), - expr: b + expr: b, + semiliconBefore: false } }), e.type, e.expectedType); } else { diff --git a/src/ax3/filters/RewriteTypesWithComment.hx b/src/ax3/filters/RewriteTypesWithComment.hx new file mode 100644 index 00000000..17508b1f --- /dev/null +++ b/src/ax3/filters/RewriteTypesWithComment.hx @@ -0,0 +1,38 @@ +package ax3.filters; + +using StringTools; + +class RewriteTypesWithComment extends AbstractFilter { + override function processExpr(e:TExpr):TExpr { + return switch e.kind { + case TEVars(k, [{v: {name: name, type: TTArray(_)}, syntax: syntax, init: init, comma: comma}]): + final c = context.fileLoader.getContent(currentPath); + final i = c.indexOf('//' , syntax.name.pos) + 2; + final end = c.indexOf('\n' , syntax.name.pos); + if (i < end) { + final v = c.substring(i, end).trim(); + if ((v.startsWith('Array<')) && v.endsWith('>')) + e.kind = TEVars(k, [{ + v: {name: name, type: tree.getType(v)}, + syntax: syntax, + init: init, + comma: comma + }]); + } + e; + case TENew(keyword, TNewObject.TNType({type: TTDictionary(_) | TTObject(_), syntax: TPath({first: syntax})}), _): + final c = context.fileLoader.getContent(currentPath); + final i = c.indexOf('//' , syntax.pos) + 2; + final end = c.indexOf('\n' , syntax.pos); + if (i < end) { + final v = c.substring(i, end).trim(); + if ((v.startsWith('Dictionary<') || v.startsWith('Object<')) && v.endsWith('>')) + e.kind = TENew(keyword, TNewObject.TNType({type: tree.getType(v), syntax: TPath({first: syntax, rest: []})}), null); + } + e; + case _: + mapExpr(processExpr, e); + } + } + +} \ No newline at end of file