From c756001d6db1386d7af85f586d0bdc9682e33973 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Wed, 23 Jul 2025 19:12:36 +0200 Subject: [PATCH 1/2] it makes me wanna merge scripts without looking --- polymod/format/ParseRules.hx | 267 ++++++++++++++++++ polymod/hscript/_internal/PolymodPrinterEx.hx | 171 +++++++++++ 2 files changed, 438 insertions(+) diff --git a/polymod/format/ParseRules.hx b/polymod/format/ParseRules.hx index 3070df9d..36323fed 100644 --- a/polymod/format/ParseRules.hx +++ b/polymod/format/ParseRules.hx @@ -11,6 +11,12 @@ import polymod.util.Util; #if unifill import unifill.Unifill; #end +#if hscript +import hscript.Expr; +import polymod.hscript._internal.PolymodExprEx; +import polymod.hscript._internal.PolymodParserEx; +import polymod.hscript._internal.PolymodPrinterEx; +#end import UnicodeString; class ParseRules @@ -32,6 +38,7 @@ class ParseRules case JSON: new JSONParseFormat(); case LINES: new LinesParseFormat(EndLineType.LF); case PLAINTEXT: new PlainTextParseFormat(); + #if hscript case SCRIPT: new ScriptParseFormat(); #end default: new PlainTextParseFormat(); } formats.set(extension, format); @@ -697,6 +704,263 @@ class PlainTextParseFormat implements BaseParseFormat // } } +#if hscript +class ScriptParseFormat implements BaseParseFormat // +{ + public var format(default, null):TextFileFormat; + + var parser:PolymodParserEx = new PolymodParserEx(); + var printer:PolymodPrinterEx = new PolymodPrinterEx(); + + public function new() + { + format = SCRIPT; + } + + public function parse(str:String):Array + { + var output:Array = []; + try + { + output = parser.parseModule(str); + } + catch (e:ErrorEx) + { + Polymod.error(MERGE, 'Script merge error: ${PolymodPrinterEx.errorExToString(e)}'); + return []; + } + + return output; + } + + public function append(baseText:String, appendText:String, id:String):String + { + Polymod.warning(MERGE, '($id) Script files do not support append functionality!'); + return baseText; + } + + public function merge(baseText:String, mergeText:String, id:String):String + { + var baseDecls:Array = parse(baseText); + var mergeDecls:Array = parse(mergeText); + var output:String = baseText; + + if (baseDecls.length == 0 || mergeDecls.length == 0) + { + return baseText; + } + + for (decl in mergeDecls) + { + switch (decl) + { + case DImport(path, star, name): + // Simply push the import, duplicate imports are handled later. + + baseDecls.push(decl); + case DPackage(path): + // If the base text doesn't have any package, add it in. Otherwise, don't replace it. + + var hasPackage:Bool = false; + for (decl2 in baseDecls) + { + if (decl2.match(DPackage(_))) + { + hasPackage = true; + break; + } + } + + if (!hasPackage) baseDecls.push(decl); + case DTypedef(c1): + // For Typedefs: if the base script has one with the same name and is an anonymous structure, add the fields from the merged script. + // Otherwise, add it regularly. + + var hasTypedef:Bool = false; + var removeDecl:Null = null; + for (decl2 in baseDecls) + { + switch (decl2) + { + case DTypedef(c2): + if (c2.name != c1.name) continue; + hasTypedef = true; + + if (!Type.enumEq(c2.t, c1.t)) + { + // If the merge typedef has the @:mergeOverride metadata, override it. Otherwise, don't do anything. + var metaNames:Array = [for (m in c1.meta) m.name]; + if (metaNames.contains(":mergeOverride")) + { + removeDecl = decl2; + hasTypedef = false; + } + break; + } + + // Only copy over the fields if the typedef is an anonymous structure. + // I wish there was an easier way to do this. + switch(c1.t) + { + case CTAnon(t1): + switch(c2.t) + { + case CTAnon(t2): + var baseFieldNames:Array = [for (fld in t2) fld.name]; + for (fld in t1) + { + if (!baseFieldNames.contains(fld.name)) t2.push(fld); + } + + default: + } + + default: + } + + default: + } + } + + if (removeDecl != null) baseDecls.remove(removeDecl); + if (!hasTypedef) baseDecls.push(decl); + case DClass(c1): + // For classes, we handle things differently. + // If the class doesn't exist, add it. + // If the entire merged class has the @:mergeOverride metadata, replace the entire class. + // Otherwise add the missing fields, including overriding the fields with @:mergeOverride. + // If a function has the @:mergeInsert metadata with an integer parameter, add the function content to the index from the parameter. + + var hasClass:Bool = false; + var removeDecl:Null = null; + for (decl2 in baseDecls) + { + switch(decl2) + { + case DClass(c2): + if (c1.name != c2.name) continue; + hasClass = true; + + // Override the class if it has the @:mergeOverride metadata. + var metaNames:Array = [for (m in c1.meta) m.name]; + if (metaNames.contains(":mergeOverride")) + { + removeDecl = decl2; + hasClass = false; + c1.meta.remove(c1.meta[metaNames.indexOf(":mergeOverride")]); + break; + } + + var fldNames:Array = [for (fld in c2.fields) fld.name]; + var removeFields:Array = []; + for (fld in c1.fields) + { + // Add the field if it doesn't exist in the class. + if (!fldNames.contains(fld.name)) + { + c2.fields.push(fld); + continue; + } + + // If the field has the @:mergeOverride metadata, override the class field. + var fldMetaNames:Array = [for (m in fld.meta) m.name]; + if (fldMetaNames.contains(":mergeOverride")) + { + removeFields.push(c2.fields[fldNames.indexOf(fld.name)]); + fld.meta.remove(fld.meta[fldMetaNames.indexOf(":mergeOverride")]); + c2.fields.push(fld); + continue; + } + + // If the field contains the @:mergeInsert metadata, insert the function expressions in the index. + var metaInsertIndex:Int = fldMetaNames.indexOf(":mergeInsert"); + if (metaInsertIndex >= 0 && fld.meta[metaInsertIndex].params.length > 0) + { + var insertIndex:Int = switch(fld.meta[metaInsertIndex].params[0] #if hscriptPos .e #end) + { + case EConst(c): + switch(c) + { + case CInt(v): v; + default: 0; + } + default: 0; + } + + switch(fld.kind) + { + case KFunction(f1): + switch(c2.fields[fldNames.indexOf(fld.name)].kind) + { + case KFunction(f2): + var funcExpr = #if hscriptPos f2.expr.e; #else f2.expr; #end + + // If the function isn't in a block, turn it into a block. + switch(funcExpr) + { + case EBlock(b): + b.insert(insertIndex, f1.expr); + default: + var exprArray:Array = [f2.expr]; + exprArray.insert(insertIndex, f1.expr); + funcExpr = EBlock(exprArray); + } + default: + } + default: + } + } + } + + for (fld in removeFields) + { + c2.fields.remove(fld); + } + + default: + } + } + + if (removeDecl != null) baseDecls.remove(removeDecl); + if (!hasClass) baseDecls.push(decl); + case DEnum(e1): + // For Enums: if the base script has one with the same name, add the fields from the merged script. Otherwise, add it regularly. + // Logic is similar to the one for Typedefs, just without the metadata stuff considering enums don't support them. + + var hasEnum:Bool = false; + for (decl2 in baseDecls) + { + switch (decl2) + { + case DEnum(e2): + if (e1.name != e2.name) continue; + hasEnum = true; + + // Enums don't support metadata, so we're skipping over the logic for @:mergeOverride. + var fldNames:Array = [for (e in e2.fields) e.name]; + for (fld in e1.fields) + { + if (!fldNames.contains(fld.name)) e2.fields.push(fld); + } + + default: + } + } + + if (!hasEnum) baseDecls.push(decl); + default: + } + } + + var realOutput:String = printer.modulesToString(baseDecls); + if (realOutput.length > 0) return realOutput; + + // Failsafe in case the printing didn't work. + return output; + } +} +#end + enum TextFileFormat { PLAINTEXT; @@ -705,6 +969,9 @@ enum TextFileFormat TSV; XML; JSON; + #if hscript + SCRIPT; + #end } enum EndLineType diff --git a/polymod/hscript/_internal/PolymodPrinterEx.hx b/polymod/hscript/_internal/PolymodPrinterEx.hx index 7dc987fe..236104ef 100644 --- a/polymod/hscript/_internal/PolymodPrinterEx.hx +++ b/polymod/hscript/_internal/PolymodPrinterEx.hx @@ -1,5 +1,6 @@ package polymod.hscript._internal; +import hscript.Expr; import hscript.Printer; class PolymodPrinterEx extends Printer @@ -37,4 +38,174 @@ class PolymodPrinterEx extends Printer return message; #end } + + public function modulesToString(m:Array) + { + var output:String = ""; + if (m.length == 0) return output; + + // Order the modules by priority (see hscript.Expr.ModuleDecl). + m.sort(function(a:ModuleDecl, b:ModuleDecl) + { + var orderA:Int = Type.enumIndex(a); + var orderB:Int = Type.enumIndex(b); + + return orderA == orderB ? 0 : orderA > orderB ? 1 : -1; + }); + + // Stringify every ModuleDecl. + for (module in m) + { + switch (module) + { + case DPackage(path): + output += "package " + path.join(".") + ";"; + + case DImport(path, star, name): + output += "import " + path.join("."); + if ((star ?? false)) + { + output += ".*"; + } + else + { + if (name != null) output += " as " + name; + } + output += ";"; + + case DClass(c): + output += metaToString(c.meta); + output += c.isPrivate ? "private " : ""; + output += c.isExtern ? "extern " : ""; + output += "class " + c.name; + if (Reflect.fields(c.params).length > 0) output += "<>"; // Once params are actually functional, this should be implemented. + output += " "; + + if (c.extend != null) output += "extends " + this.typeToString(c.extend) + " "; + if (c.implement.length > 0) output += "implements " + c.implement.join(", "); + + output += "\n{"; + output += classFieldsToString(c.fields); + output += "}"; + + case DTypedef(t): + + output += metaToString(t.meta); + output += t.isPrivate ? "private " : ""; + output += "typedef " + t.name; + if (Reflect.fields(t.params).length > 0) output += "<>"; // Once params are actually functional, this should be implemented. + output += " = " + this.typeToString(t.t); + + case DEnum(e): + output += "enum " + e.name; + output += "\n{\n"; + + for (fld in e.fields) + { + output += fld.name; + if (fld.args.length > 0) + { + output += "("; + for (i in 0...fld.args.length) + { + var arg:EnumArgDecl = fld.args[i]; + output += arg.name + (arg.type != null ? this.typeToString(arg.type) : ""); + if (i < fld.args.length - 1) output += ", "; + } + output += ")"; + } + output += "\n"; + } + + output += "}"; + } + + output += "\n"; + } + + return output; + } + + function classFieldsToString(fields:Array) + { + if (fields.length == 0) return "\n"; + var output:String = "\n"; + for (fld in fields) + { + output += metaToString(fld.meta); + + for (acc in fld.access) + { + switch (acc) + { + case APublic: output += "public "; + case APrivate: output += "private "; + case AInline: output += "inline "; + case AOverride: output += "override "; + case AStatic: output += "static "; + case AMacro: output += "macro "; + } + } + + switch (fld.kind) + { + case KFunction(f): + output += "function " + fld.name + "("; + for (i in 0...f.args.length) + { + var arg:Argument = f.args[i]; + if (arg.opt ?? false) output += "?"; + output += arg.name + this.typeToString(arg.t); + if (arg.value != null) output += " = " + this.exprToString(arg.value); + + if (i < f.args.length - 1) output += ", "; + } + + output += ")"; + if (f.ret != null) output += this.typeToString(f.ret); + + output += this.exprToString(f.expr); + + case KVar(v): + output += "var " + fld.name; + if (v.get != null || v.set != null) + { + output += "(" + (v.get ?? "default") + ", " + (v.set ?? "default") + ")"; + } + + if (v.type != null) output += this.typeToString(v.type); + if (v.expr != null) output += " = " + this.exprToString(v.expr); + output += ";"; + } + + output += "\n"; + } + + return output; + } + + function metaToString(meta:Metadata) + { + if (meta.length == 0) return ""; + + var output:String = ""; + for (m in meta) + { + output += "@" + m.name; + if (m.params != null) + { + output += "("; + for (i in 0...m.params.length) + { + var param:Expr = m.params[i]; + output += this.exprToString(param); + if (i < m.params.length - 1) output += ", "; + } + output += ")"; + } + output += "\n"; + } + + return output; + } } From a030f675607177a2b80af1c70fc380a420a5e3dd Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:23:46 +0200 Subject: [PATCH 2/2] eric's requested changes also fix implements since i forgot to lol! --- polymod/format/ParseRules.hx | 111 +++++++++--------- polymod/hscript/_internal/PolymodPrinterEx.hx | 5 +- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/polymod/format/ParseRules.hx b/polymod/format/ParseRules.hx index 36323fed..11fb6bdc 100644 --- a/polymod/format/ParseRules.hx +++ b/polymod/format/ParseRules.hx @@ -767,13 +767,14 @@ class ScriptParseFormat implements BaseParseFormat // if (decl2.match(DPackage(_))) { hasPackage = true; + Polymod.warning(MERGE, 'Base Script already has a Package. Skipping.'); break; } } if (!hasPackage) baseDecls.push(decl); case DTypedef(c1): - // For Typedefs: if the base script has one with the same name and is an anonymous structure, add the fields from the merged script. + // For Typedefs: if the base script has one with the same name, override it with @:mergeOverride metadata. // Otherwise, add it regularly. var hasTypedef:Bool = false; @@ -783,41 +784,24 @@ class ScriptParseFormat implements BaseParseFormat // switch (decl2) { case DTypedef(c2): - if (c2.name != c1.name) continue; - hasTypedef = true; - - if (!Type.enumEq(c2.t, c1.t)) + if (c2.name == c1.name) { - // If the merge typedef has the @:mergeOverride metadata, override it. Otherwise, don't do anything. - var metaNames:Array = [for (m in c1.meta) m.name]; - if (metaNames.contains(":mergeOverride")) - { - removeDecl = decl2; - hasTypedef = false; - } - break; - } + hasTypedef = true; - // Only copy over the fields if the typedef is an anonymous structure. - // I wish there was an easier way to do this. - switch(c1.t) - { - case CTAnon(t1): - switch(c2.t) + if (!Type.enumEq(c2.t, c1.t)) + { + // If the merge typedef has the @:mergeOverride metadata, override it. + var metaNames:Array = [for (m in c1.meta) m.name]; + if (metaNames.contains(":mergeOverride")) { - case CTAnon(t2): - var baseFieldNames:Array = [for (fld in t2) fld.name]; - for (fld in t1) - { - if (!baseFieldNames.contains(fld.name)) t2.push(fld); - } - - default: + removeDecl = decl2; + hasTypedef = false; + break; } + } - default: + Polymod.warning(MERGE, 'Base script already has a typedef ${c1.name}. Skipping.'); } - default: } } @@ -828,7 +812,7 @@ class ScriptParseFormat implements BaseParseFormat // // For classes, we handle things differently. // If the class doesn't exist, add it. // If the entire merged class has the @:mergeOverride metadata, replace the entire class. - // Otherwise add the missing fields, including overriding the fields with @:mergeOverride. + // Otherwise add the missing fields with @:mergeAdd, including overriding the fields with @:mergeOverride. // If a function has the @:mergeInsert metadata with an integer parameter, add the function content to the index from the parameter. var hasClass:Bool = false; @@ -855,26 +839,18 @@ class ScriptParseFormat implements BaseParseFormat // var removeFields:Array = []; for (fld in c1.fields) { - // Add the field if it doesn't exist in the class. - if (!fldNames.contains(fld.name)) - { - c2.fields.push(fld); - continue; - } - - // If the field has the @:mergeOverride metadata, override the class field. var fldMetaNames:Array = [for (m in fld.meta) m.name]; - if (fldMetaNames.contains(":mergeOverride")) + + // If the field has no metadata, do nothing. + if (fldMetaNames.length == 0) { - removeFields.push(c2.fields[fldNames.indexOf(fld.name)]); - fld.meta.remove(fld.meta[fldMetaNames.indexOf(":mergeOverride")]); - c2.fields.push(fld); + Polymod.warning(MERGE, 'Field ${fld.name} from the merge class ${c1.name} has no metadata. Skipping.'); continue; } // If the field contains the @:mergeInsert metadata, insert the function expressions in the index. var metaInsertIndex:Int = fldMetaNames.indexOf(":mergeInsert"); - if (metaInsertIndex >= 0 && fld.meta[metaInsertIndex].params.length > 0) + if (metaInsertIndex >= 0 && (fld.meta[metaInsertIndex].params?.length ?? 0) > 0) { var insertIndex:Int = switch(fld.meta[metaInsertIndex].params[0] #if hscriptPos .e #end) { @@ -906,10 +882,42 @@ class ScriptParseFormat implements BaseParseFormat // funcExpr = EBlock(exprArray); } default: + Polymod.warning(MERGE, 'Field ${fld.name} from the base class ${c2.name} is not a function. Skipping.'); + } default: + Polymod.warning(MERGE, 'Field ${fld.name} from the merge class ${c1.name} is not a function. Skipping.'); + } + continue; + } + + // If the field has the @:mergeOverride metadata, override the class field. + if (fldMetaNames.contains(":mergeOverride")) + { + removeFields.push(c2.fields[fldNames.indexOf(fld.name)]); + fld.meta.remove(fld.meta[fldMetaNames.indexOf(":mergeOverride")]); + c2.fields.push(fld); + continue; } + + // If the field has the @:mergeAdd metadata, add it to the class if it doesn't already exist. + if (fldMetaNames.contains(":mergeAdd")) + { + if (!fldNames.contains(fld.name)) + { + c2.fields.push(fld); + } + else + { + Polymod.warning(MERGE, 'Field ${fld.name} from the merge class ${c1.name} already exists in the base class. Skipping.'); + } + + continue; + } + + // Otherwise, throw a warning. + Polymod.warning(MERGE, 'Field ${fld.name} from the merge class ${c1.name} doesn\'t have any merge metadata. Skipping.'); } for (fld in removeFields) @@ -924,8 +932,8 @@ class ScriptParseFormat implements BaseParseFormat // if (removeDecl != null) baseDecls.remove(removeDecl); if (!hasClass) baseDecls.push(decl); case DEnum(e1): - // For Enums: if the base script has one with the same name, add the fields from the merged script. Otherwise, add it regularly. - // Logic is similar to the one for Typedefs, just without the metadata stuff considering enums don't support them. + // For Enums: if the base script has an enum with the same name, throw a warning. + // Otherwise, add it to the base script. var hasEnum:Bool = false; for (decl2 in baseDecls) @@ -933,14 +941,11 @@ class ScriptParseFormat implements BaseParseFormat // switch (decl2) { case DEnum(e2): - if (e1.name != e2.name) continue; - hasEnum = true; - - // Enums don't support metadata, so we're skipping over the logic for @:mergeOverride. - var fldNames:Array = [for (e in e2.fields) e.name]; - for (fld in e1.fields) + if (e1.name == e2.name) { - if (!fldNames.contains(fld.name)) e2.fields.push(fld); + hasEnum = true; + Polymod.error(MERGE, 'Script merge error: Base Script already has an Enum ' + e1.name + ". Skipping."); + continue; } default: diff --git a/polymod/hscript/_internal/PolymodPrinterEx.hx b/polymod/hscript/_internal/PolymodPrinterEx.hx index 236104ef..dcf81357 100644 --- a/polymod/hscript/_internal/PolymodPrinterEx.hx +++ b/polymod/hscript/_internal/PolymodPrinterEx.hx @@ -82,7 +82,10 @@ class PolymodPrinterEx extends Printer output += " "; if (c.extend != null) output += "extends " + this.typeToString(c.extend) + " "; - if (c.implement.length > 0) output += "implements " + c.implement.join(", "); + for (imp in c.implement) + { + output += "implements " + imp + " "; + } output += "\n{"; output += classFieldsToString(c.fields);