diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b9fea1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dump/ +Nothing__ +hxpico8.hxproj +p8demo/ diff --git a/Array.hx b/Array.hx new file mode 100644 index 0000000..6ffae91 --- /dev/null +++ b/Array.hx @@ -0,0 +1,35 @@ +package; + +/** + * ... + * @author YellowAfterlife + */ +#if !macro +class Array { + +} +#else +extern class Array { + var length(default,null) : Int; + function new() : Void; + function concat( a : Array ) : Array; + function join( sep : String ) : String; + function pop() : Null; + function push(x : T) : Int; + function reverse() : Void; + function shift() : Null; + function slice( pos : Int, ?end : Int ) : Array; + function sort( f : T -> T -> Int ) : Void; + function splice( pos : Int, len : Int ) : Array; + function toString() : String; + function unshift( x : T ) : Void; + function insert( pos : Int, x : T ) : Void; + function remove( x : T ) : Bool; + function indexOf( x : T, ?fromIndex:Int ) : Int; + function lastIndexOf( x : T, ?fromIndex:Int ) : Int; + function copy() : Array; + function iterator() : Iterator; + function map( f : T -> S ) : Array; + function filter( f : T -> Bool ) : Array; +} +#end diff --git a/Collection.hx b/Collection.hx new file mode 100644 index 0000000..0646e55 --- /dev/null +++ b/Collection.hx @@ -0,0 +1,25 @@ +package; + +/** + * List maps to a Lua array. + * Indexes start at 1. + * @author YellowAfterlife + */ +abstract Collection(Array) { + public inline function new() { + this = []; + } + @:from public static inline function create(values:Array):Collection { + return Pico.collection(values); + } + // + public var size(get, never):Int; + private inline function get_size():Int return Pico.count(this); + // + public inline function add(value:T):Void Pico.add(this, value); + public inline function del(value:T):Void Pico.del(this, value); + public inline function remove(value:T):Void Pico.del(this, value); + public inline function iterator():Iterator { + return Pico.all(this); + } +} diff --git a/Fixed.hx b/Fixed.hx new file mode 100644 index 0000000..24c90c0 --- /dev/null +++ b/Fixed.hx @@ -0,0 +1,7 @@ +package; + +/** + * + * @author YellowAfterlife + */ +typedef Fixed = Float; diff --git a/HxOverrides.hx b/HxOverrides.hx new file mode 100644 index 0000000..fba83c2 --- /dev/null +++ b/HxOverrides.hx @@ -0,0 +1,14 @@ +package; + +@:noDoc +extern class HxOverrides { + static function dateStr( date :Date ) : String; + static function strDate( s : String ) : Date; + static function cca( s : String, index : Int ) : Null; + static function substr( s : String, pos : Int, ?len : Int ) : String; + static function indexOf( a : Array, obj : T, i : Int) : Void; + static function lastIndexOf( a : Array, obj : T, i : Int) : Void; + static function remove( a : Array, obj : T ) : Void; + static function iter( a : Array ) : Iterator; + +} \ No newline at end of file diff --git a/Map.hx b/Map.hx new file mode 100644 index 0000000..5565fee --- /dev/null +++ b/Map.hx @@ -0,0 +1,141 @@ +package; +#if !macro +//{ Pico +/** + * Map equivalent in GameMaker. No GC/ARC - call destroy() to free memory. + * @author YellowAfterlife + */ +@:forward abstract Map(Dynamic) { + public inline function new() { + this = { }; + } + @:arrayAccess public inline function get(key:K):V { + return untyped this[key]; + } + public inline function set(key:K, value:V):Void { + untyped this[key] = value; + } + @:arrayAccess public inline function arrayWrite(key:K, value:V):V { + return untyped this[key] = value; + } +} +//} +#elseif (haxe_ver >= 3.2) +//{ Haxe >= 3.2 +import haxe.ds.StringMap; +import haxe.ds.IntMap; +import haxe.ds.HashMap; +import haxe.ds.ObjectMap; +import haxe.ds.WeakMap; +import haxe.ds.EnumValueMap; +import haxe.Constraints.IMap; + +@:multiType(K) abstract Map(IMap ) { + public function new(); + public inline function set(key:K, value:V) this.set(key, value); + @:arrayAccess public inline function get(key:K) return this.get(key); + public inline function exists(key:K) return this.exists(key); + public inline function remove(key:K) return this.remove(key); + public inline function keys():Iterator { + return this.keys(); + } + public inline function iterator():Iterator { + return this.iterator(); + } + public inline function toString():String { + return this.toString(); + } + @:arrayAccess @:noCompletion public inline function arrayWrite(k:K, v:V):V { + this.set(k, v); + return v; + } + @:to static inline function toStringMap(t:IMap):StringMap { + return new StringMap(); + } + @:to static inline function toIntMap(t:IMap):IntMap { + return new IntMap(); + } + @:to static inline function toEnumValueMapMap(t:IMap):EnumValueMap { + return new EnumValueMap(); + } + @:to static inline function toObjectMap(t:IMap):ObjectMap { + return new ObjectMap(); + } + @:from static inline function fromStringMap(map:StringMap):Map< String, V > { + return cast map; + } + @:from static inline function fromIntMap(map:IntMap):Map< Int, V > { + return cast map; + } + @:from static inline function fromObjectMap(map:ObjectMap):Map { + return cast map; + } +} + +@:deprecated typedef IMap = haxe.Constraints.IMap; +//} +#else +//{ Haxe < 3.2 +import haxe.ds.StringMap; +import haxe.ds.IntMap; +import haxe.ds.HashMap; +import haxe.ds.ObjectMap; +import haxe.ds.WeakMap; +import haxe.ds.EnumValueMap; +@:multiType(K) abstract Map(IMap ) { + public function new(); + public inline function set(key:K, value:V) this.set(key, value); + @:arrayAccess public inline function get(key:K) return this.get(key); + public inline function exists(key:K) return this.exists(key); + public inline function remove(key:K) return this.remove(key); + public inline function keys():Iterator { + return this.keys(); + } + public inline function iterator():Iterator { + return this.iterator(); + } + public inline function toString():String { + return this.toString(); + } + @:arrayAccess @:noCompletion public inline function arrayWrite(k:K, v:V):V { + this.set(k, v); + return v; + } + @:to static inline function toStringMap(t:IMap):StringMap { + return new StringMap(); + } + @:to static inline function toIntMap(t:IMap):IntMap { + return new IntMap(); + } + @:to static inline function toEnumValueMapMap(t:IMap):EnumValueMap { + return new EnumValueMap(); + } + @:to static inline function toObjectMap(t:IMap):ObjectMap { + return new ObjectMap(); + } + @:from static inline function fromStringMap(map:StringMap):Map< String, V > { + return map; + } + @:from static inline function fromIntMap(map:IntMap):Map< Int, V > { + return map; + } + @:from static inline function fromObjectMap(map:ObjectMap):Map { + return map; + } +} + +interface IMap { + public function get(k:K):Null; + public function set(k:K, v:V):Void; + public function exists(k:K):Bool; + public function remove(k:K):Bool; + public function keys():Iterator; + public function iterator():Iterator; + public function toString():String; +} + +private typedef Hashable = { + function hashCode():Int; +} +//} +#end diff --git a/Math.hx b/Math.hx new file mode 100644 index 0000000..e28124a --- /dev/null +++ b/Math.hx @@ -0,0 +1,41 @@ +package; + +/** + * ... + * @author YellowAfterlife + */ +extern class Math { + static inline function abs(f:Float):Float return Pico.abs(f); + static inline function sign(f:Float):Int return Pico.sign(f); + static inline function min(a:Float, b:Float):Float return Pico.min(a, b); + static inline function max(a:Float, b:Float):Float return Pico.max(a, b); + static inline function mid(a:Float, b:Float, c:Float):Float return Pico.mid(a, b, c); + static inline function clamp(f:Float, a:Float, b:Float):Float { + return Pico.mid(f, a, b); + } + // integer shortcuts: + static inline function iabs(i:Int):Int return cast Pico.abs(i); + static inline function imin(a:Int, b:Int):Int return cast Pico.min(a, b); + static inline function imax(a:Int, b:Int):Int return cast Pico.max(a, b); + static inline function imid(a:Int, b:Int, c:Int):Int return cast Pico.mid(a, b, c); + static inline function iclamp(f:Int, a:Int, b:Int):Int { + return cast Pico.mid(f, a, b); + } + // trig: + static inline var PI:Float = 1.0; + static inline function sin(r:Float):Float return Pico.sin(r); + static inline function cos(r:Float):Float return Pico.cos(r); + static inline function tan(r:Float):Float { + return sin(r) / cos(r); + } + static inline function atan2(y:Float, x:Float):Float { + return Pico.atan2(x, y); + } + // + static inline function sqrt(f:Float):Float return Pico.sqrt(f); + // + static inline function floor(f:Float):Int return Pico.flr(f); + static inline function ceil(f:Float):Int return -Pico.flr( -f); + // + static inline function random():Float return Pico.rand(1); +} \ No newline at end of file diff --git a/Pico.hx b/Pico.hx new file mode 100644 index 0000000..ab5d112 --- /dev/null +++ b/Pico.hx @@ -0,0 +1,167 @@ +package; + +/** + * ... + * @author YellowAfterlife + */ +@:native("") +extern class Pico { + //{ events + @:native("_update") static var onUpdate:Void->Void; + /// executed on game draw. onUpdate must be defined for it to work. + @:native("_draw") static var onDraw:Void->Void; + /// executed on game start. Most often you don't need this one. + @:native("_init") static var onInit:Void->Void; + //} + //{ system + /// Returns: + /// mode 0: memory usage (0...256) + /// mode 1: CPU usage (0..1) + static function stat(mode:Int):Fixed; + static function flip():Void; + static function time():Fixed; + //} + //{ sprites + /// Gets the color of a spritesheet pixel at the given coordinates. + static function sget(x:Fixed, y:Fixed):Int; + /// Sets the color of a spritesheet pixel at the given coordinates. + /// If color is not specified, the current color is used. + static function sset(x:Fixed, y:Fixed, ?color:Int):Void; + /// Returns the value of sprite's flag. + static function fget(sprite:Int, flag:Int):Void; + /// Returns sprite's flags as bits + @:native("fset") static function fgetx(sprite:Int):Void; + /// Changes the value of sprite's flag + static function fset(sprite:Int, flag:Int, value:Bool):Void; + /// Changes sprite's flags. + @:native("fset") static function fsetx(sprite:Int, flagBits:Int):Void; + //} + //{ colors + static inline var clBlack = 0; + static inline var clDarkBlue = 1; + static inline var clDarkPurple = 2; + static inline var clDarkGreen = 3; + static inline var clBrown = 4; + static inline var clDarkGray = 5; + static inline var clLightGray = 6; + static inline var clWhite = 7; + static inline var clRed = 8; + static inline var clOrange = 9; + static inline var clYellow = 10; + static inline var clGreen = 11; + static inline var clBlue = 12; + static inline var clIndigo = 13; + static inline var clPink = 14; + static inline var clPeach = 15; + //} + //{ drawing + + /// Clears the screen. + static function cls():Void; + + /// Sets the screen offset for all subsequent drawing operations. + /// Call without arguments to reset to default (0, 0). + static function camera(?x:Fixed, ?y:Fixed):Void; + + /// Sets the screen's clipping region in pixels. + /// Call without arguments to reset. + static function clip(x:Int, y:Int, w:Int, h:Int):Void; + + /// Sets the default color for the drawing operations + static function color(color:Int):Void; + + /// Gets the color of a pixel at the given screen coordinates. + static function pget(x:Fixed, y:Fixed):Int; + + /// Sets the color of a pixel at the given screen coordinates. + /// If color is not specified, the current color is used. + static function pset(x:Fixed, y:Fixed, ?color:Int):Void; + + /// Replaces all instances of color_from by color_to in the subsequent draw calls. + /// Call pal() without arguments to reset to system defaults. + /// If specified, palette determines the palette to switch colors in: + /// 0: draw palette (default) - colors are remapped on draw (for example, to re-color sprites) + /// 1: screen palette - colors are remapped on display (for example, to achive fading or global color transitions). + static function pal(?from:Int, ?to:Int, ?palette:Int):Void; + + /// Changes whether a particular color should be drawn (affects spr, sspr, map). + /// For example, palt(8, false) will disable drawing of red pixels. + /// Calling this function with no arguments will reset state to default, where all colors but black (#0) are drawn. + static function palt(?color:Int, visible:Bool):Void; + + // + + /// Sets the cursor position and carriage return margin. + static function cursor(x:Fixed, y:Fixed):Void; + + /// Prints a string. + /// If only first argument is supplied, and the cursor reaches the end of the screen, a carriage return and vertical scroll is automatically applied. (terminal-like behaviour) + static function print(value:Dynamic, ?x:Fixed, ?y:Fixed):Void; + // + static function line(x1:Fixed, y1:Fixed, x2:Fixed, y:Fixed, ?color:Int):Void; + static function circ(x:Fixed, y:Fixed, r:Fixed, ?color:Int):Void; + static function circfill(x:Fixed, y:Fixed, r:Fixed, ?color:Int):Void; + static function rect(x1:Fixed, y1:Fixed, x2:Fixed, y:Fixed, ?color:Int):Void; + static function rectfill(x1:Fixed, y1:Fixed, x2:Fixed, y:Fixed, ?color:Int):Void; + static function spr(sprite:Int, x:Int, y:Int, ?cols:Int, ?rows:Int, ?flip:Bool):Void; + static function sspr(left:Fixed, top:Fixed, width:Fixed, height:Fixed, x:Fixed, y:Fixed, ?dw:Int, ?dh:Int, ?flip:Bool):Void; + static function map(col:Int, row:Int, x:Fixed, y:Fixed, ?cols:Int, ?rows:Int, ?flags:Int):Void; + //} + //{ collections + /// Converts 0-based declaration into 1-based declaration + static function collection(values:Array):Collection; + static function add(co:Collection, value:T):Void; + static function del(co:Collection, value:T):Void; + static function count(co:Collection):Int; + @:native("foreach") static function forEach(co:Collection, func:T->Void):Void; + static function all(co:Collection):Iterator; + //} + //{ input + static inline var btLeft:Int = 0; + static inline var btRight:Int = 1; + static inline var btUp:Int = 2; + static inline var btDown:Int = 3; + static inline var btA:Int = 4; + static inline var btB:Int = 5; + /// Returns whether the given button is down. + static function btn(?index:Int, ?player:Int):Bool; + /// Returns whether the given button was pressed (includes "auto-fire") + static function btnp(?index:Int, ?player:Int):Bool; + //} + //{ audio + static function sfx(sound:Int, ?channel:Int, ?offset:Fixed):Void; + static function music(?pattern:Int, ?fade:Fixed, ?channelMask:Int):Void; + //} + //{ map + static function mget(col:Fixed, row:Fixed):Int; + static function mset(col:Fixed, row:Fixed, sprite:Int):Void; + //} + //{ memory + static function peek(adr:Int):Int; + static function poke(adr:Int, byte:Int):Int; + static function memcpy(dest:Int, src:Int, len:Int):Void; + static function memset(dest:Int, byte:Int, len:Int):Void; + static function reload(dest:Int, src:Int, len:Int):Void; + static function cstore(?dest:Int, ?src:Int, ?len:Int):Void; + //} + //{ math + static function sub(s:String, from:Int, ?to:Int):String; + static function flr(x:Fixed):Int; + static function abs(x:Fixed):Fixed; + static function sign(x:Fixed):Int; + static function sqrt(x:Fixed):Fixed; + static function max(x:Fixed, y:Fixed):Fixed; + static function min(x:Fixed, y:Fixed):Fixed; + static function mid(x:Fixed, y:Fixed, z:Fixed):Fixed; + static function cos(x:Fixed):Fixed; + static function sin(x:Fixed):Fixed; + static function atan2(x:Fixed, y:Fixed):Fixed; + /// Returns a random number between 0 (incl.) and x (excl.) + @:native("rnd") static function rand(x:Fixed):Fixed; + /// Returns a random integer between 0 (incl.) and x (excl.) + static inline function irand(x:Int):Int { + return flr(rand(x)); + } + @:native("srand") static function srand(seed:Int):Void; + //} +} \ No newline at end of file diff --git a/Type.hx b/Type.hx new file mode 100644 index 0000000..ea276ac --- /dev/null +++ b/Type.hx @@ -0,0 +1,47 @@ +package; + +/** + * ... + * @author YellowAfterlife + */ +enum ValueType { + TNull; + TInt; + TFloat; + TBool; + TObject; + TFunction; + TClass( c : Class ); + TEnum( e : Enum ); + TUnknown; +} + +@:coreApi extern class Type { + static function getClass( o : T ) : Class; + static function getEnum( o : EnumValue ) : Enum; + static function getSuperClass( c : Class ) : Class; + static function getClassName( c : Class ) : String; + static function getEnumName( e : Enum ) : String; + static function resolveClass( name : String ) : Class; + static function resolveEnum( name : String ) : Enum; + static function createInstance( cl : Class, args : Array ) : T; + static function createEmptyInstance( cl : Class ) : T; + static function createEnum( e : Enum, constr : String, ?params : Array ) : T; + static function createEnumIndex( e : Enum, index : Int, ?params : Array ) : T; + static function getInstanceFields( c : Class ) : Array; + static function getClassFields( c : Class ) : Array; + static function getEnumConstructs( e : Enum ) : Array; + static function typeof( v : Dynamic ) : ValueType; + static function enumEq( a : T, b : T ) : Bool; + static function enumConstructor( e : EnumValue ) : String; + inline static function enumParameters( e : EnumValue ) : Array { + return untyped e.slice(2); + } + + inline static function enumIndex( e : EnumValue ) : Int { + return untyped e[1]; + } + + static function allEnums( e : Enum ) : Array; + +} \ No newline at end of file diff --git a/haxelib.json b/haxelib.json new file mode 100644 index 0000000..1280390 --- /dev/null +++ b/haxelib.json @@ -0,0 +1,11 @@ +{ + "name": "hxpico8", + "url": "http://yal.cc", + "license": "MIT", + "tags": ["lua", "retro", "pico8", "voxatron"], + "description": "Experimental Lua target for Pico-8 fantasy console.", + "version": "1.0.0", + "releasenote": "", + "contributors": ["YellowAfterlife"], + "dependencies": {} +} \ No newline at end of file diff --git a/js/Boot.hx b/js/Boot.hx new file mode 100644 index 0000000..d04812d --- /dev/null +++ b/js/Boot.hx @@ -0,0 +1,27 @@ +package js; + +/** + * ... + * @author YellowAfterlife + */ +extern class Boot { + private static function __unhtml(s:String):String; + private static function __trace(v:Dynamic, i:haxe.PosInfos):Void; + private static function __clear_trace():Void; + static function isClass(o:Dynamic):Bool; + static function isEnum(e:Dynamic):Bool; + static function getClass(o:Dynamic):Dynamic; + @:ifFeature("has_enum") private static function __string_rec(o:Dynamic, s:String):String; + private static function __interfLoop(cc:Dynamic, cl:Dynamic):Bool; + @:ifFeature("typed_catch") private static function __instanceof(o:Dynamic, c:Dynamic):Bool; + @:ifFeature("typed_cast") private static function __cast(o:Dynamic, t:Dynamic):Dynamic; + static var __toStr; + static function __nativeClassName(o:Dynamic):String; + static function __isNativeObj(o:Dynamic):Bool; + static function __resolveNativeClass(name:String):Dynamic; +} + +private extern class HaxeError extends js.Error { + var val:Dynamic; + public function new(val:Dynamic); +} \ No newline at end of file diff --git a/p8gen/PgBuffer.hx b/p8gen/PgBuffer.hx new file mode 100644 index 0000000..ceea274 --- /dev/null +++ b/p8gen/PgBuffer.hx @@ -0,0 +1,52 @@ +package p8gen; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgBuffer extends StringBuf { + var indent:Int = 0; + inline function addString(s:String) add(s); + inline function addInt(i:Int) add(i); + inline function addBuffer(b:PgBuffer) add(b); + inline function addChar2(c1:Int, c2:Int) { + addChar(c1); + addChar(c2); + } + /// adds a separator (space). For reading ease, not functional + inline function addSep() { + #if debug + addChar(" ".code); + #end + } + inline function addSepChar(c:Int) { + addSep(); + addChar(c); + addSep(); + } + inline function addSepChar2(c1:Int, c2:Int) { + addSep(); + addChar(c1); + addChar(c2); + addSep(); + } + inline function addComma() { + addChar(",".code); + addSep(); + } + inline function addLine(indentDelta:Int = 0) { + indent += indentDelta; + addChar("\n".code); + #if debug + var i = indent; + while (--i >= 0) addChar("\t".code); + #end + } + // same as addLine but for when it's merely a separator + inline function addLineSep() { + #if debug + addLine(); + #end + } +} \ No newline at end of file diff --git a/p8gen/PgExpr.hx b/p8gen/PgExpr.hx new file mode 100644 index 0000000..34c083c --- /dev/null +++ b/p8gen/PgExpr.hx @@ -0,0 +1,410 @@ +package p8gen; +import haxe.macro.Type; +import p8gen.expr.*; +import p8gen.PgMain.error; +import p8gen.PgMain.notSupported; +import p8gen.struct.*; +using p8gen.PgExpr; +using p8gen.PgPath; +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgExpr { + static function addConst(r:PgBuffer, c:TConstant) { + switch (c) { + case TInt(i): r.addInt(i); + case TFloat(f): r.add(f); + case TString(s): { + var dq:Bool = (s.indexOf('"') < 0) || (s.indexOf("'") >= 0); + r.addChar(dq ? '"'.code : "'".code); + for (i in 0 ... s.length) { + var c:Int = StringTools.fastCodeAt(s, i); + switch (c) { + case '"'.code: + if (dq) r.addChar("\\".code); + r.addChar(c); + case "\r".code: r.addChar2("\\".code, "r".code); + case "\n".code: r.addChar2("\\".code, "n".code); + default: r.addChar(c); + } + } + r.addChar(dq ? '"'.code : "'".code); + } + case TBool(b): r.addString(b ? "true" : "false"); + case TThis: r.addString("this"); + case TNull: r.addString("nil"); + case TSuper: r.addString("super"); + default: + } + } + static function addExpr(r:PgBuffer, e:TypedExpr) { + //{ + function intForCond(v_id:Int, nodes:Array):Bool { + if (nodes.length < 1) return false; + switch (nodes[0].expr) { + case TVar(_, { expr: TUnop(OpIncrement, true, { expr: TLocal({ id: v_idx }) }) }): + if (v_idx != v_id) return false; + default: return false; + } + return true; + } + function intForGen(min:TypedExpr, max:TypedExpr, block:TypedExpr):Void { + r.addString("for "); + var nodes = switch (block.expr) { + case TBlock(el): el; + default: null; + } + switch (nodes[0].expr) { + case TVar(v, _): r.addString(v.name); + default: + } + r.addSepChar("=".code); + r.addExpr(min); + r.addComma(); + r.addExpr(offsetInt(max, -1)); + r.addString(" do"); + r.addLine(1); + r.addExpr(modExpr(block, TBlock(nodes.slice(1)))); + r.addLine( -1); + r.addString("end"); + } + //} + switch (e.expr) { + case TConst(c): addConst(r, c); + case TLocal(v): r.addString(v.name); + case TArray(arr, idx): { + switch (arr.t) { + case TEnum(et_ref, _) if (PgEnum.map.baseGet(et_ref.get()).isSimple): + r.addExpr(arr); + default: + r.addExpr(arr); + r.addChar("[".code); + r.addExpr(idx); + r.addChar("]".code); + } + } + case TBinop(OpAssignOp(op), e1, e2): { + r.addExpr(e1); + switch (op) { + case OpAdd: r.addSepChar2("+".code, "=".code); + case OpMult: r.addSepChar2("*".code, "=".code); + case OpDiv: r.addSepChar2("/".code, "=".code); + case OpSub: r.addSepChar2("-".code, "=".code); + case OpMod: r.addSepChar2("%".code, "=".code); + default: notSupported(e.pos); + } + r.addExpr(e2); + } + case TBinop(op = OpAnd | OpOr | OpXor | OpShl | OpShr, e1, e2): { + switch (op) { + case OpAnd: r.addString("band"); + case OpOr: r.addString("bor"); + case OpXor: r.addString("bxor"); + case OpShl: r.addString("shl"); + case OpShr: r.addString("shr"); + default: + } + r.addChar("(".code); + r.addExpr(e1); + r.addComma(); + r.addExpr(e2); + r.addChar(")".code); + } + case TBinop(OpAdd, e1, e2): { + var _e1_isString = switch (e1.t) { + case TInst(ct_ref, _) if (ct_ref.get().name == "String"): true; + default: false; + } + var _e2_isString = switch (e2.t) { + case TInst(ct_ref, _) if (ct_ref.get().name == "String"): true; + default: false; + } + r.addExpr(e1); + if (_e1_isString || _e2_isString) { + r.addSepChar2(".".code, ".".code); + } else { + r.addSepChar("+".code); + } + r.addExpr(e2); + } + case TBinop(op, e1, e2): { + r.addExpr(e1); + switch (op) { + case OpAdd: r.addSepChar("+".code); + case OpMult: r.addSepChar("*".code); + case OpDiv: r.addSepChar("/".code); + case OpSub: r.addSepChar("-".code); + case OpAssign: r.addSepChar("=".code); + case OpEq: r.addSepChar2("=".code, "=".code); + case OpNotEq: r.addSepChar2("!".code, "=".code); + case OpGt: r.addSepChar(">".code); + case OpGte: r.addSepChar2(">".code, "=".code); + case OpLt: r.addSepChar("<".code); + case OpLte: r.addSepChar2("<".code, "=".code); + case OpBoolAnd: r.addString(" and "); + case OpBoolOr: r.addString(" or "); + case OpMod: r.addSepChar("%".code); + default: notSupported(e.pos); + } + r.addExpr(e2); + } + case TField(e1, fa): PgExprField.addExprField(r, e1, fa); + // TTypeExpr + case TParenthesis(e1): { + r.addChar("(".code); + r.addExpr(e1); + r.addChar(")".code); + } + case TObjectDecl(fields): { + r.addChar("{".code); + if (fields.length > 0) { + r.addLine(1); + var trail = false; + for (f in fields) { + if (trail) { + r.addChar(",".code); + r.addLineSep(); + } else trail = true; + r.addString(f.name); + r.addSepChar("=".code); + r.addExpr(f.expr); + } + r.addLine( -1); + } else r.addSep(); + r.addChar("}".code); + } + case TArrayDecl(args): { + r.addChar("{".code); + if (args.length > 0) { + r.addSep(); + r.addChar("[".code); + r.addInt(0); + r.addChar("]".code); + r.addSepChar("=".code); + var trail = false; + for (arg in args) { + if (trail) r.addComma(); else trail = true; + r.addExpr(arg); + } + r.addSep(); + } else r.addSep(); + r.addChar("}".code); + } + case TCall(e1, args): PgExprCall.addExprCall(r, e1, args); + case TNew(ct_ref, _, args): { + r.addFieldPath(PgClass.map.baseGet(ct_ref.get()), "create"); + r.addChar("(".code); + PgExprCall.addArgs(r, args); + r.addChar(")".code); + } + case TUnop(OpNeg, false, e1): r.addChar("-".code); r.addExpr(e1); + case TUnop(OpNot, false, e1): r.addString("not "); r.addExpr(e1); + case TUnop(OpNegBits, false, e1): r.addString("bnot("); r.addExpr(e1); r.addChar(")".code); + case TFunction(f): PgExprFunction.addExprFunction(r, null, f); + case TVar(v, e1): { + r.addString("local "); + r.addString(v.name); + if (e1 != null) { + r.addSepChar("=".code); + r.addExpr(e1); + } + } + case TBlock([ //{ for (i in a ... b) -> for i = a, b - 1 + { expr: TVar({ id: v_id }, min) }, + { expr: TWhile( + { expr: TParenthesis({ expr: TBinop(OpLt, { expr: TLocal({ id: v_id2 }) }, max) }) }, + block = { expr: TBlock(nodes) }, + true + ) } + ]) if (v_id == v_id2 && intForCond(v_id, nodes)): { + intForGen(min, max, block); + } //} + case TBlock([ //{ for (i in a ... b) -> for i = a, b - 1 + { expr: TVar({ id: v_id }, min) }, + { expr: TVar({ id: q_id }, max) }, + { expr: TWhile( + { expr: TParenthesis({ expr: TBinop(OpLt, + { expr: TLocal({ id: v_id2 }) }, + { expr: TLocal({ id: q_id2 }) } + ) }) }, + block = { expr: TBlock(nodes) }, + true + ) } + ]) if (v_id == v_id2 && q_id == q_id2 && intForCond(v_id, nodes)): { + intForGen(min, max, block); + } //} + case TBlock(list): { + var trail = false; + for (ei in list) { + switch (ei.expr) { + case TConst(_) | TLocal(_): + case TBlock(_) if (isEmpty(ei)): + default: + if (trail) { + r.addLine(); + } else trail = true; + r.addExpr(ei); + } + } + } + case TFor(v, eiter, eblock): { + r.addString("for "); + r.addString(v.name); + r.addString(" in "); + r.addExpr(eiter); + r.addString(" do"); + r.addLine(1); + r.addExpr(eblock); + r.addLine( -1); + r.addString("end"); + } + case TIf(econd, ethen, eelse): { + r.addString("if "); + r.addExpr(unpackExpr(econd)); + r.addString(" then"); + r.addLine(1); + r.addExpr(ethen); + r.addLine( -1); + if (eelse != null) { + r.addString("else"); + eelse = unpackExpr(eelse); + switch (eelse.expr) { + case TIf(_, _, _): r.addExpr(eelse); + default: + r.addLine(1); + r.addExpr(eelse); + r.addLine( -1); + r.addString("end"); + } + } else r.addString("end"); + } + case TWhile(econd, eiter, true): { // while (cond) iter; + r.addString("while "); + r.addExpr(unpackExpr(econd)); + r.addString(" do"); + r.addLine(1); + r.addExpr(eiter); + r.addLine( -1); + r.addString("end"); + } + case TWhile(econd, eiter, false): { // do iter while (cond); + r.addString("repeat"); + r.addLine(1); + r.addExpr(eiter); + r.addLine( -1); + r.add("until "); + r.addExpr(invertExpr(econd)); + } + case TSwitch(e1, ecases, edef): { + e1 = unpackExpr(e1); + var es = { + var b = new PgBuffer(); + b.addExpr(e1); + b.toString(); + }; + var trail = false; + for (ecase in ecases) { + if (trail) r.addString("else"); else trail = true; + r.addString("if "); + var trail2 = false; + for (evalue in ecase.values) { + if (trail2) r.addString(" or "); else trail2 = true; + r.addString(es); + r.addSepChar2("=".code, "=".code); + r.addExpr(evalue); + } + r.addString(" then"); + r.addLine(1); + r.addExpr(ecase.expr); + r.addLine(-1); + } + if (edef != null) { + r.addString("else"); + r.addLine(1); + r.addExpr(edef); + r.addLine(-1); + } + if (trail) r.addString("end"); + } + // TTry + case TReturn(e1): { + r.addString("return"); + if (e1 != null) { + r.addChar(" ".code); + r.addExpr(e1); + } + } + case TBreak: r.addString("break"); + case TContinue: r.addString("continue"); + // TThrow + case TCast(e1, _): r.addExpr(e1); + case TMeta(_, e1): r.addExpr(e1); + case TEnumParameter(e1, ef, i): { + r.addExpr(e1); + r.addChar("[".code); + r.addInt(i + 2); + r.addChar("]".code); + } + default: { + //notSupported(e.pos); + r.addString("--[[" + e.expr.getName() + "]]"); + } + } + } + static inline function modExpr(src:TypedExpr, expr:TypedExprDef):TypedExpr { + return { expr: expr, pos: src.pos, t: src.t }; + } + static inline function makeVar(name:String, ?type:Type, ?id:Int):TVar { + return { name: name, id: id, t: type, capture: false, extra: null }; + } + static function offsetInt(e:TypedExpr, d:Int):TypedExpr { + e = unpackExpr(e); + var rx:TypedExprDef; + switch (e.expr) { + case TConst(TInt(i)): rx = TConst(TInt(i + d)); + case TBinop(OpAdd, e1, e2 = { expr: TConst(TInt(i)) }): + rx = TBinop(OpAdd, e1, modExpr(e2, TConst(TInt(i + d)))); + default: + if (d > 0) { + rx = TBinop(OpAdd, e, modExpr(e, TConst(TInt(d)))); + } else { + rx = TBinop(OpSub, e, modExpr(e, TConst(TInt(-d)))); + } + } + return modExpr(e, rx); + } + /// inverts a condition-expression + static function invertExpr(e:TypedExpr):TypedExpr { + e = unpackExpr(e); + var rx:TypedExprDef; + switch (e.expr) { + case TUnop(OpNot, _, e1): rx = e1.expr; + default: rx = TUnop(OpNot, false, e); + } + return modExpr(e, rx); + } + static function unpackExpr(e:TypedExpr):TypedExpr { + while (e != null) switch (e.expr) { + case TParenthesis(e1): e = e1; + case TCast(e1, _): e = e1; + case TBlock([e1]): e = e1; + case TMeta(_, e1): e = e1; + default: break; + } + return e; + } + static function isEmpty(e:TypedExpr) { + switch (e.expr) { + case TBlock(m): + if (m.length == 0) { + return true; + } else if (m.length == 1) { + return isEmpty(m[0]); + } + default: + } + return false; + } +} \ No newline at end of file diff --git a/p8gen/PgMain.hx b/p8gen/PgMain.hx new file mode 100644 index 0000000..b2d58a0 --- /dev/null +++ b/p8gen/PgMain.hx @@ -0,0 +1,120 @@ +package p8gen; +import haxe.io.Path; +import haxe.macro.Compiler; +import haxe.macro.Context; +import haxe.macro.Expr.Position; +import haxe.macro.JSGenApi; +import p8gen.struct.*; +import sys.FileSystem; +import sys.io.File; +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgMain { + static var api:JSGenApi; + static inline function error(text:String, pos:Position) { + Context.error(text, pos); + } + static inline function notSupported(pos:Position) { + error("Not supported.", pos); + } + static function build() { + // + var hasSuperClass = []; + for (t in api.types) switch (t) { + case TInst(ct_ref, _): + var ct = ct_ref.get(); + var c = new PgClass(ct); + if (ct.superClass != null) hasSuperClass.push(c); + case TEnum(et_ref, _): + new PgEnum(et_ref.get()); + default: + } + for (c in hasSuperClass) { + c.parent = PgClass.map.baseGet(c.classType.superClass.t.get()); + } + for (c in PgClass.list) { + var p = c.parent; + while (p != null) { + p.children.push(c); + p = p.parent; + } + } + // + var api_main = api.main; + if (api_main != null) { + api_main = PgExpr.unpackExpr(api_main); + switch (api_main.expr) { + case TCall({ expr: TField(e, FStatic(ct_ref, cf_ref))}, []): + var cf = cf_ref.get(); + switch (cf.kind) { + case FMethod(MethInline): + var c = PgClass.map.baseGet(ct_ref.get()); + for (f in c.statics) if (f.name == cf.name) { + f.isExtern = true; + api_main = f.func.expr; + break; + } + default: + } + default: + } + } + // + var r = new PgBuffer(); + #if debug + r.addString("-- "); + r.addString(Date.now().toString()); + r.addLine(); + #end + for (t in PgType.list) t.print(r); + if (api_main != null) { + PgExpr.addExpr(r, api_main); + r.addLine(); + } + // and save it: + var path = api.outputFile; + var rstr = r.toString(); + inline function outerror(text:String) { + error(text, Context.makePosition({ file: path, min: 0, max: 0 })); + } + switch (Path.extension(path.toLowerCase())) { + case "p8hx": { + path = path.substring(0, path.length - 2); + if (!FileSystem.exists(path)) outerror("Can't modify file as it is missing."); + var code = File.getContent(path); + // + var mark_before = "__lua__"; + var pos = code.indexOf(mark_before); + if (pos < 0) outerror('The file is missing "code start" mark ($mark_before)'); + var code_before = code.substring(0, pos + mark_before.length); + // + var mark_after = "__gfx__"; + pos = code.lastIndexOf(mark_after); + if (pos < 0) outerror('The file is missing "code end" mark ($mark_after)'); + var code_after = code.substring(pos); + // + var code_out = { + var b = new PgBuffer(); + b.addString(code_before); + b.addChar("\n".code); + b.addString(rstr); + if (rstr.charCodeAt(rstr.length - 1) != "\n".code) b.addChar("\n".code); + b.addString(code_after); + b.toString(); + }; + File.saveContent(path, code_out); + } + case "lua": File.saveContent(path, rstr); + default: outerror("Output file should be .lua (for saving code alone) or .p8hx (for patching code in a .p8 file of same path)"); + } + } + static function use() { + Compiler.setCustomJSGenerator(function(_api) { + api = _api; + build(); + }); + } +} \ No newline at end of file diff --git a/p8gen/PgOpt.hx b/p8gen/PgOpt.hx new file mode 100644 index 0000000..d9f7d64 --- /dev/null +++ b/p8gen/PgOpt.hx @@ -0,0 +1,160 @@ +package p8gen; +import haxe.macro.Type; +import haxe.macro.TypedExprTools; +import p8gen.PgExpr.*; +/** + * Handles mini-optimizations + * @author YellowAfterlife + */ +@:publicFields +class PgOpt { + static inline function iter(e:TypedExpr, f:TypedExpr->Void) { + TypedExprTools.iter(e, f); + } + static function optExpr(e:TypedExpr) { + var f:TypedExpr->Void = null; + var z:Bool; + //{ `v = { ...; x; }` -> `{ ...; v = x; }` + f = function(e:TypedExpr) switch (e.expr) { + case TBinop(OpAssign, e1, e2x ): + iter(e, f); + var e2 = unpackExpr(e2x); + switch (e2.expr) { + case TBlock(list): + var value = list.pop(); + list.push(modExpr(e, TBinop(OpAssign, e1, value))); + e.expr = TBlock(list); + e.pos = e2.pos; + default: + } + default: iter(e, f); + }; f(e); + //} + //{ switch (enum[1]) { ... } -> { var g = enum[1]; switch (g) { ... } + f = function(e:TypedExpr) switch (e.expr) { + case TSwitch(e1, cases, edef): + iter(e, f); + switch (e1.expr) { + case TConst(_) | TLocal(_): + default: + var comps = 0; + for (c in cases) { + comps += c.values.length; + if (comps > 1) break; + } + if (comps > 1) { + var v = makeVar("_g_switch", e1.t); + var exprs = [modExpr(e1, TVar(v, unpackExpr(e1)))]; + if (comps == 0) { + if (edef != null) exprs.push(edef); + } else { + exprs.push(modExpr(e, TSwitch(modExpr(e1, TLocal(v)), cases, edef))); + } + e.expr = TBlock(exprs); + } + } + default: iter(e, f); + }; f(e); + //} + //{ Merge single-use automatically declared variables before expressions + f = function(e:TypedExpr) { + iter(e, f); + switch (e.expr) { + case TBlock(arr): + var check = true; + var n = arr.length; + var firstPass = true; + while (check) { + check = false; + var k = 0; + while (k < n) { + switch (arr[k].expr) { + case TVar(v, ev) if (ev != null && k < n - 1): + do { + // don't break some expressions: + switch (ev.expr) { + case TFunction(_): break; + // (used in loops): + case TUnop(OpIncrement, _, _): break; + case TUnop(OpDecrement, _, _): break; + default: + } + // + var v_id = v.id; + var next = arr[k + 1]; + // don't merge into certain expressions: + switch (next.expr) { + case TIf(_, _, _): break; + case TWhile(_, _, _): break; + case TSwitch(_, _, _): break; + case TFunction(_): break; + default: + } + // next expression must contain exactly one mention: + if (countLocal(next, v_id) != 1) break; + var valid = true; + // Don't merge into local function calls: + var f = null; f = function(e:TypedExpr) { + iter(e, f); + switch (e.expr) { + case TFunction(_): valid = false; + default: + } + }; f(next); + if (!valid) break; + // consider a `local;` after use-line: + var trail = false; + if (firstPass && k < n - 2) { + switch (arr[k + 2].expr) { + case TLocal(v2) if (v2.id == v_id): trail = true; + default: + } + } + // ensure that these are the only uses around: + var num = trail ? 2 : 1; + if (countLocal(e, v_id) != num) break; + // + if (trail) { arr.splice(k + 2, 1); n--; } + replaceLocal(next, v_id, ev); + arr.splice(k, 1); k--; n--; + check = true; + } while (false); + default: + } + k++; + } // while (k < n) + firstPass = false; + } + if (arr.length == 1) e.expr = arr[0].expr; + default: + } // switch + }; f(e); + //} + // + } + static function countLocal(e:TypedExpr, localId:Int):Int { + var r:Int = 0; + var f:TypedExpr->Void = null; + f = function(e:TypedExpr):Void { + switch (e.expr) { + case TLocal(v): if (v.id == localId) r++; + default: iter(e, f); + } + }; f(e); + return r; + } + static function replaceLocal(e:TypedExpr, localId:Int, n:TypedExpr):Void { + var f:TypedExpr->Void = null; + f = function(e:TypedExpr):Void { + switch (e.expr) { + case TLocal(v): + if (v.id == localId) { + e.expr = n.expr; + e.pos = n.pos; + e.t = n.t; + } + default: TypedExprTools.iter(e, f); + } + }; f(e); + } +} \ No newline at end of file diff --git a/p8gen/PgPath.hx b/p8gen/PgPath.hx new file mode 100644 index 0000000..dc4eca7 --- /dev/null +++ b/p8gen/PgPath.hx @@ -0,0 +1,26 @@ +package p8gen; +import p8gen.struct.PgType; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgPath { + static inline function addPath(r:PgBuffer, pkg:Array, name:String) { + for (p in pkg) { + r.addString(p); + r.addChar("_".code); + } + r.addString(name); + } + static inline function addPath2(r:PgBuffer, pkg:Array, name:String, field:String) { + var l = r.length; + addPath(r, pkg, name); + if (r.length > l) r.addChar("_".code); + r.addString(field); + } + static inline function addFieldPath(r:PgBuffer, type:PgType, field:String) { + addPath2(r, type.pack, type.name, field); + } +} \ No newline at end of file diff --git a/p8gen/PgTypeMap.hx b/p8gen/PgTypeMap.hx new file mode 100644 index 0000000..d36f153 --- /dev/null +++ b/p8gen/PgTypeMap.hx @@ -0,0 +1,39 @@ +package p8gen; +import haxe.macro.Type; +import p8gen.struct.PgType; + +/** + * ... + * @author YellowAfterlife + */ +abstract PgTypeMap(Map>) from Map> to Map> { + public inline function new() { + this = new Map(); + } + public inline function exists(module:String, name:String):Bool { + var map = this.get(module); + return map != null ? map.exists(name) : false; + } + public inline function get(module:String, name:String):T { + var map = this.get(module); + return map != null ? map[name] : null; + } + public inline function set(module:String, name:String, value:T):Void { + var map = this.get(module); + if (map == null) this[module] = map = new Map(); + map[name] = value; + } + public inline function baseGet(t:BaseType):T { + return get(t.module, t.name); + } + public inline function baseSet(t:BaseType, v:T):Void { + set(t.module, t.name, v); + } + public inline function wrapGet(t:PgType):T { + return get(t.module, t.name); + } + public inline function wrapSet(t:PgType, v:T):Void { + set(t.module, t.name, v); + } + public inline function keys() return this.keys(); +} diff --git a/p8gen/expr/PgExprCall.hx b/p8gen/expr/PgExprCall.hx new file mode 100644 index 0000000..bd74140 --- /dev/null +++ b/p8gen/expr/PgExprCall.hx @@ -0,0 +1,136 @@ +package p8gen.expr; +import haxe.macro.Type; +import p8gen.PgBuffer; +import p8gen.struct.PgClass; +import p8gen.PgMain.error; +using p8gen.PgExpr; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgExprCall { + static function addArgs(r:PgBuffer, args:Array, ?inst:TypedExpr) { + var comma:Bool = false; + if (inst != null) { + switch (inst.expr) { + case TConst(TSuper): r.addString("this"); + default: r.addExpr(inst); + } + comma = true; + } + for (arg in args) { + if (comma) r.addComma(); else comma = true; + r.addExpr(arg); + } + } + static function addExprCall(r:PgBuffer, e:TypedExpr, args:Array) { + var fname = { + var b = new PgBuffer(); + b.addExpr(e); + b.toString(); + }; + // + var inst:TypedExpr = null; + switch (e.expr) { + case TField(e, fa): + inst = e; + switch (fa) { + case FEnum(et_ref, ef): + r.addChar("{".code); + r.addSep(); + r.addInt(ef.index); + if (args.length > 0) { + r.addComma(); + addArgs(r, args); + } + r.addSep(); + r.addChar("}".code); + return; + case FStatic(_, _): inst = null; + default: + } + default: + } + // + switch (fname) { + case "`trace": { + var i:Int = 0, n:Int = args.length; + r.addString("print("); + switch (args[n - 1].expr) { + case TObjectDecl(fields): + if (fields[0].name == "fileName" && fields[1].name == "lineNumber") { + r.addChar('"'.code); + switch (fields[0].expr.expr) { + case TConst(TString(s)): r.addString(s); + default: + } + r.addChar(":".code); + switch (fields[1].expr.expr) { + case TConst(TInt(i)): r.addInt(i); + default: + } + r.addChar2(":".code, " ".code); + while (i < n) { + switch (args[i].expr) { + case TConst(TString(s)): { + r.addString(s); + i++; + continue; + }; + case TBinop(OpAdd, { expr: TConst(TString(s)) }, e2): { + r.addString(s); + r.addChar('"'.code); + r.addSepChar2(".".code, ".".code); + r.addExpr(e2); + i++; + }; + default: r.addChar('"'.code); + } + break; + } + } // (if correct format) + n--; + default: + } + while (i < n) { + r.addSepChar2(".".code, ".".code); + r.addExpr(args[i]); + i++; + } + r.addChar(")".code); + } + case "collection": { + switch (args[0].expr) { + case TArrayDecl(args): + r.addChar("{".code); + if (args.length > 0) { + r.addSep(); + var trail = false; + for (arg in args) { + if (trail) r.addComma(); else trail = true; + r.addExpr(arg); + } + } + r.addSep(); + r.addChar("}".code); + default: r.addExpr(args[0]); + } + } + case "super": { + r.addString(PgClass.current.parent.constructor.path); + r.addChar("(".code); + inst = { expr: TConst(TThis), pos: e.pos, t: e.t }; + addArgs(r, args, inst); + r.addChar(")".code); + } + default: { + r.addString(fname); + r.addChar("(".code); + addArgs(r, args, inst); + r.addChar(")".code); + } + } + } +} \ No newline at end of file diff --git a/p8gen/expr/PgExprField.hx b/p8gen/expr/PgExprField.hx new file mode 100644 index 0000000..7948afb --- /dev/null +++ b/p8gen/expr/PgExprField.hx @@ -0,0 +1,73 @@ +package p8gen.expr; +import haxe.macro.Type; +import p8gen.PgBuffer; +import p8gen.PgMain.error; +import p8gen.struct.PgEnum; +using p8gen.PgExpr; +using p8gen.PgPath; + +/** + * ... + * @author YellowAfterlife + */ +class PgExprField { + public static function addExprField(r:PgBuffer, e:TypedExpr, fa:FieldAccess) { + var ct:ClassType; + var cf:ClassField; + switch (fa) { + case FInstance(ct_ref, _, cf_ref) | FClosure({ c: ct_ref }, cf_ref): { + ct = ct_ref.get(); + cf = cf_ref.get(); + var cf_name = cf.name; + if (cf_name == "length" && ct.name == "Array" && ct.pack.length == 0) { + r.addChar("#".code); + r.addExpr(e); + return; + } + // decide whether to use static (class_field) or dynamic (inst.field) access: + var is_dynamic = false; + switch (cf.kind) { + case FVar(_, _): is_dynamic = true; + case FMethod(MethDynamic): + switch (e.expr) { + case TConst(TSuper): + default: is_dynamic = true; + } + default: + } + // + if (is_dynamic) { + r.addExpr(e); + r.addChar(".".code); + r.addString(cf_name); + } else { + r.addPath2(ct.pack, ct.name, cf_name); + } + } + case FStatic(ct_ref, cf_ref): { + ct = ct_ref.get(); + cf = cf_ref.get(); + r.addPath2(ct.pack, ct.name, cf.name); + } + case FAnon(cf_ref): { + r.addExpr(e); + r.addChar(".".code); + r.addString(cf_ref.get().name); + } + case FDynamic(field): { + r.addExpr(e); + r.addChar(".".code); + r.addString(field); + } + case FEnum(et_ref, ef): { + var et = et_ref.get(); + if (PgEnum.map.baseGet(et).isSimple) { + r.addInt(ef.index); + } else { + + } + } + default: error("This field access type is not supported.", e.pos); + } + } +} \ No newline at end of file diff --git a/p8gen/expr/PgExprFunction.hx b/p8gen/expr/PgExprFunction.hx new file mode 100644 index 0000000..c251198 --- /dev/null +++ b/p8gen/expr/PgExprFunction.hx @@ -0,0 +1,61 @@ +package p8gen.expr; +import haxe.macro.Type; +import p8gen.PgBuffer; +import p8gen.PgOpt; +import p8gen.struct.PgArgument; +using p8gen.PgExpr; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgExprFunction { + static function addOptArgs(r:PgBuffer, args:Array, ?expr:TypedExpr) { + for (arg in args) { + var value = arg.value; + if (value != null) { + var arg_v = arg.v; + if (expr != null) { + if (PgOpt.countLocal(expr, arg_v.id) == 0) continue; + } + var name = arg_v.name; + r.addString("if "); + r.addString(name); + r.addSepChar2("=".code, "=".code); + r.addString("nil then "); + r.addString(name); + r.addSepChar("=".code); + r.addConst(value); + r.addString(" end"); + r.addLine(); + } + } + } + static function addExprFunction(r:PgBuffer, name:String, func:TFunc, ?inst:Bool) { + r.addString("function"); + if (name != null) { + r.addChar(" ".code); + r.addString(name); + } + r.addChar("(".code); + var trail = false; + if (inst) { + r.addString("this"); + trail = true; + } + for (arg in func.args) { + if (trail) r.addComma(); else trail = true; + r.addString(arg.v.name); + } + r.addChar(")".code); + var expr = func.expr; + if (!PgExpr.isEmpty(expr)) { + r.addLine(1); + addOptArgs(r, func.args, expr); + r.addExpr(expr); + r.addLine( -1); + } else r.addSep(); + r.addString("end"); + } +} \ No newline at end of file diff --git a/p8gen/struct/PgArgument.hx b/p8gen/struct/PgArgument.hx new file mode 100644 index 0000000..92c09c2 --- /dev/null +++ b/p8gen/struct/PgArgument.hx @@ -0,0 +1,11 @@ +package p8gen.struct; +import haxe.macro.Type; + +/** + * ... + * @author YellowAfterlife + */ +typedef PgArgument = { + var v:TVar; + var value:Null; +} \ No newline at end of file diff --git a/p8gen/struct/PgBase.hx b/p8gen/struct/PgBase.hx new file mode 100644 index 0000000..04db17c --- /dev/null +++ b/p8gen/struct/PgBase.hx @@ -0,0 +1,12 @@ +package p8gen.struct; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgBase { + var name:String; + var path:String; + var isExtern:Bool; +} \ No newline at end of file diff --git a/p8gen/struct/PgClass.hx b/p8gen/struct/PgClass.hx new file mode 100644 index 0000000..7b93cf9 --- /dev/null +++ b/p8gen/struct/PgClass.hx @@ -0,0 +1,217 @@ +package p8gen.struct; +import haxe.macro.Type; +import haxe.macro.Type.ClassType; +import p8gen.PgBuffer; +import p8gen.PgTypeMap; +import p8gen.expr.*; +using p8gen.PgPath; +using p8gen.PgExpr; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgClass extends PgType { + static var map:PgTypeMap = new PgTypeMap(); + static var list:Array = []; + static var current:PgClass = null; + var classType:ClassType; + var parent:PgClass = null; + var children:Array = []; + var statics:Array = []; + var fields:Array = []; + var staticsMap:Map = new Map(); + var fieldsMap:Map = new Map(); + var constructor:PgClassField; + function new(t:ClassType) { + super(t); + map.wrapSet(this, this); + list.push(this); + classType = t; + for (f in t.statics.get()) statics.push(new PgClassField(this, f, false)); + for (f in t.fields.get()) fields.push(new PgClassField(this, f, true)); + for (f in statics) staticsMap[f.name] = f; + for (f in fields) fieldsMap[f.name] = f; + if (t.constructor != null) { + constructor = new PgClassField(this, t.constructor.get(), true); + } + } + /// prints _create/_new functions + function printCtr(r:PgBuffer) { + var ctr = constructor; + var needsNew = false; + for (c in children) { + if (c.constructor != null) { + needsNew = true; + break; + } + } + var ctr_expr = ctr.func.expr; + inline function addInit() { + var r = { + var b = new PgBuffer(); + b.indent = r.indent; + b; + }; + r.addChar("{".code); + var len = r.length; + r.indent++; + var trail = false; + inline function handleTrail() { + if (trail) r.addChar(",".code); else trail = true; + r.addLineSep(); + } + // add dynamic method initializers: + var c = this; + var methodsPut:Map = new Map(); + while (c != null) { + for (f in c.fields) { + var f_name = f.name; + if (f.expr != null && methodsPut[f_name] == null) { + switch (f.kind) { + case FMethod(MethDynamic): + handleTrail(); + r.addString(f_name); + r.addSepChar("=".code); + r.addFieldPath(c, f_name); + methodsPut[f_name] = true; + default: + } + } + } + c = c.parent; + } + // if possible, merge in variable initializations: + if (!needsNew) { + var list = switch (ctr_expr.expr) { + case TBlock(list): list; + default: [ctr_expr]; + } + while (list.length > 0) { + switch (list[0].expr) { + case TBinop(OpAssign, + { expr: TField({ expr: TConst(TThis) }, FInstance(_, _, cf_ref)) }, + { expr: TConst(c) }): + var name = cf_ref.get().name; + if (!fieldsMap.exists(name)) break; + handleTrail(); + r.addString(name); + r.addSepChar("=".code); + r.addConst(c); + list.shift(); + default: break; + } + } + ctr_expr = PgExpr.modExpr(ctr_expr, TBlock(list)); + } + r.indent--; + if (r.length > len) { + r.addLine(); + } else r.addSep(); + r.addChar("}".code); + return r; + } + var ctr_args = ctr.args; + if (needsNew) { // _new + _create + var new_name = { + var b = new PgBuffer(); + b.addFieldPath(this, "new"); + b.toString(); + }; + PgExprFunction.addExprFunction(r, new_name, ctr.func, true); + r.addLine(); + // _create: + r.addString("function "); + r.addFieldPath(this, "create"); + r.addChar("(".code); + if (ctr_args.length > 0) r.addString("..."); + r.addChar(")".code); + r.addLine(1); + // + r.addString("local this"); + r.addSepChar("=".code); + r.addBuffer(addInit()); + r.addLine(); + r.addFieldPath(this, "new"); + r.addString("(...)"); + r.addLine(); + r.addString("return this"); + // + r.addLine( -1); + r.addString("end"); + r.addLine(); + } else { // _create + r.addString("function "); + r.addFieldPath(this, "create"); + r.addChar("(".code); + var trail = false; + for (arg in ctr_args) { + if (trail) r.addComma(); else trail = true; + r.addString(arg.v.name); + } + r.addChar(")".code); + r.addLine(1); + // + var init = addInit(); + if (ctr_expr.isEmpty()) { + r.addString("return"); + r.addSep(); + r.addBuffer(init); + } else { + r.addString("local this"); + r.addSepChar("=".code); + r.addBuffer(init); + r.addLine(); + r.addExpr(ctr_expr); + r.addLine(); + r.addString("return this"); + } + // + r.addLine( -1); + r.addString("end"); + r.addLine(); + } + } + override function print(r:PgBuffer) { + if (isExtern) return; + current = this; + var b = new PgBuffer(); + for (f in statics) if (!f.isExtern) { + switch (f.kind) { + case FVar(_, _): + if (f.expr != null) { + b.addString(f.path); + b.addSepChar("=".code); + PgExpr.addExpr(b, f.expr); + b.addLine(); + } + case FMethod(k): + if (f.func != null) { + PgExprFunction.addExprFunction(b, f.path, f.func); + b.addLine(); + } + } + } + for (f in fields) if (!f.isExtern) { + switch (f.kind) { + case FMethod(_): + if (f.func != null) { + PgExprFunction.addExprFunction(b, f.path, f.func, true); + b.addLine(); + } + default: + } + } + if (constructor != null) printCtr(b); + if (b.length > 0) { + #if debug + r.addString("-- "); + r.addString(path); + r.addChar(":".code); + r.addLine(); + #end + r.addBuffer(b); + } + } +} \ No newline at end of file diff --git a/p8gen/struct/PgClassField.hx b/p8gen/struct/PgClassField.hx new file mode 100644 index 0000000..5d0d184 --- /dev/null +++ b/p8gen/struct/PgClassField.hx @@ -0,0 +1,36 @@ +package p8gen.struct; +import haxe.macro.Type; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgClassField extends PgField { + var parentClass:PgClass; + var kind:FieldKind; + var func:TFunc = null; + /// field' initial value + var expr:TypedExpr; + function new(t:PgClass, f:ClassField, isInst:Bool) { + parentType = t; + parentClass = t; + name = f.name; + type = f.type; + kind = f.kind; + inst = isInst; + expr = f.expr(); + isExtern = f.meta.has(":remove"); + if (expr != null) { + PgOpt.optExpr(expr); + switch (expr.expr) { + case TFunction(tfunc): + func = tfunc; + args = cast tfunc.args; + type = tfunc.t; + default: + } + } + updatePath(); + } +} \ No newline at end of file diff --git a/p8gen/struct/PgEnum.hx b/p8gen/struct/PgEnum.hx new file mode 100644 index 0000000..ed19471 --- /dev/null +++ b/p8gen/struct/PgEnum.hx @@ -0,0 +1,29 @@ +package p8gen.struct; +import haxe.macro.Type; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgEnum extends PgType { + static var map:PgTypeMap = new PgTypeMap(); + var enumType:EnumType; + var constructs:Array = []; + var isSimple:Bool; + public function new(t:EnumType) { + super(t); + map.wrapSet(this, this); + enumType = t; + for (name in t.names) { + constructs.push(new PgEnumField(this, t.constructs.get(name))); + } + isSimple = true; + for (c in constructs) { + if (c.args.length > 0) { + isSimple = false; + break; + } + } + } +} \ No newline at end of file diff --git a/p8gen/struct/PgEnumField.hx b/p8gen/struct/PgEnumField.hx new file mode 100644 index 0000000..8c844c7 --- /dev/null +++ b/p8gen/struct/PgEnumField.hx @@ -0,0 +1,36 @@ +package p8gen.struct; +import haxe.macro.Type; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgEnumField extends PgField { + var parentEnum:PgEnum; + var index:Int; + function new(t:PgEnum, f:EnumField) { + parentEnum = t; + parentType = t; + name = f.name; + index = f.index; + updatePath(); + args = []; + switch (f.type) { + case TFun(f_args, f_out): { // Field(...); + type = f_out; + for (arg in f_args) args.push({ + value: arg.opt ? TNull : null, + v: { + id: -1, + name: arg.name, + t: arg.t, + capture: null, + extra: null + }, + }); + } + default: type = f.type; // Field; + } + } +} \ No newline at end of file diff --git a/p8gen/struct/PgField.hx b/p8gen/struct/PgField.hx new file mode 100644 index 0000000..d1ff6f0 --- /dev/null +++ b/p8gen/struct/PgField.hx @@ -0,0 +1,23 @@ +package p8gen.struct; +import haxe.macro.Type; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgField extends PgBase { + var parentType:PgType; + var type:Type; + /// whether it's an instance field (instance methods take 'this' arg) + var inst:Bool = false; + var args:Array = null; + inline function updatePath() { + path = { + var s = parentType.path; + if (s != "") s += "_"; + s += name; + s; + }; + } +} \ No newline at end of file diff --git a/p8gen/struct/PgType.hx b/p8gen/struct/PgType.hx new file mode 100644 index 0000000..6e72123 --- /dev/null +++ b/p8gen/struct/PgType.hx @@ -0,0 +1,31 @@ +package p8gen.struct; +import p8gen.PgBuffer; +import haxe.macro.Type.BaseType; + +/** + * ... + * @author YellowAfterlife + */ +@:publicFields +class PgType extends PgBase { + static var list:Array = []; + var baseType:BaseType; + var pack:Array; + var module:String; + function new(t:BaseType) { + baseType = t; + isExtern = t.isExtern || t.meta.has(":remove"); + module = t.module; + name = t.name; + pack = t.pack; + path = { + var b = new PgBuffer(); + PgPath.addPath(b, pack, name); + b.toString(); + }; + list.push(this); + } + function print(r:PgBuffer) { + + } +} \ No newline at end of file