/* * Copyright (C)2005-2019 Haxe Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package haxe.macro; import haxe.macro.Expr; using Lambda; using StringTools; /** This class provides some utility methods to convert elements from the macro context to a human-readable String representation. */ class Printer { var tabs:String; var tabString:String; public function new(?tabString = "\t") { tabs = ""; this.tabString = tabString; } public function printUnop(op:Unop) return switch (op) { case OpIncrement: "++"; case OpDecrement: "--"; case OpNot: "!"; case OpNeg: "-"; case OpNegBits: "~"; case OpSpread: "..."; } public function printBinop(op:Binop) return switch (op) { case OpAdd: "+"; case OpMult: "*"; case OpDiv: "/"; case OpSub: "-"; case OpAssign: "="; case OpEq: "=="; case OpNotEq: "!="; case OpGt: ">"; case OpGte: ">="; case OpLt: "<"; case OpLte: "<="; case OpAnd: "&"; case OpOr: "|"; case OpXor: "^"; case OpBoolAnd: "&&"; case OpBoolOr: "||"; case OpShl: "<<"; case OpShr: ">>"; case OpUShr: ">>>"; case OpMod: "%"; case OpInterval: "..."; case OpArrow: "=>"; case OpIn: "in"; case OpAssignOp(op): printBinop(op) + "="; } function escapeString(s:String, delim:String) { return delim + s.replace("\n", "\\n") .replace("\t", "\\t") .replace("\r", "\\r") .replace("'", "\\'") .replace('"', "\\\"") #if sys .replace("\x00", "\\x00") #end + delim; } public function printFormatString(s:String) { return escapeString(s, "'"); } public function printString(s:String) { return escapeString(s, '"'); } public function printConstant(c:Constant) return switch (c) { case CString(s, SingleQuotes): printFormatString(s); case CString(s, _): printString(s); case CIdent(s), CInt(s), CFloat(s): s; case CRegexp(s, opt): '~/$s/$opt'; } public function printTypeParam(param:TypeParam) return switch (param) { case TPType(ct): printComplexType(ct); case TPExpr(e): printExpr(e); } public function printTypePath(tp:TypePath) return (tp.pack.length > 0 ? tp.pack.join(".") + "." : "") + tp.name + (tp.sub != null ? '.${tp.sub}' : "") + (tp.params == null ? "" : tp.params.length > 0 ? "<" + tp.params.map(printTypeParam).join(", ") + ">" : ""); // TODO: check if this can cause loops public function printComplexType(ct:ComplexType) return switch (ct) { case TPath(tp): printTypePath(tp); case TFunction(args, ret): var wrapArgumentsInParentheses = switch args { // type `:(a:X) -> Y` has args as [TParent(TNamed(...))], i.e `a:X` gets wrapped in `TParent()`. We don't add parentheses to avoid printing `:((a:X)) -> Y` case [TParent(t)]: false; // this case catches a single argument that's a type-path, so that `X -> Y` prints `X -> Y` not `(X) -> Y` case [TPath(_) | TOptional(TPath(_))]: false; default: true; } var argStr = args.map(printComplexType).join(", "); (wrapArgumentsInParentheses ? '($argStr)' : argStr) + " -> " + (switch ret { // wrap return type in parentheses if it's also a function case TFunction(_): '(${printComplexType(ret)})'; default: (printComplexType(ret): String); }); case TAnonymous(fields): "{ " + [for (f in fields) printField(f) + "; "].join("") + "}"; case TParent(ct): "(" + printComplexType(ct) + ")"; case TOptional(ct): "?" + printComplexType(ct); case TNamed(n, ct): n + ":" + printComplexType(ct); case TExtend(tpl, fields): var types = [for (t in tpl) "> " + printTypePath(t) + ", "].join(""); var fields = [for (f in fields) printField(f) + "; "].join(""); '{${types}${fields}}'; case TIntersection(tl): tl.map(printComplexType).join(" & "); } public function printMetadata(meta:MetadataEntry) return '@${meta.name}' + ((meta.params != null && meta.params.length > 0) ? '(${printExprs(meta.params, ", ")})' : ""); public function printAccess(access:Access) return switch (access) { case AStatic: "static"; case APublic: "public"; case APrivate: "private"; case AOverride: "override"; case AInline: "inline"; case ADynamic: "dynamic"; case AMacro: "macro"; case AFinal: "final"; case AExtern: "extern"; case AAbstract: "abstract"; case AOverload: "overload"; } public function printField(field:Field) { inline function orderAccess(access: Array) { // final should always be printed last // (does not modify input array) return access.has(AFinal) ? access.filter(a -> !a.match(AFinal)).concat([AFinal]) : access; } return (field.doc != null && field.doc != "" ? "/**\n" + tabs + tabString + StringTools.replace(field.doc, "\n", "\n" + tabs + tabString) + "\n" + tabs + "**/\n" + tabs : "") + (field.meta != null && field.meta.length > 0 ? field.meta.map(printMetadata).join('\n$tabs') + '\n$tabs' : "") + (field.access != null && field.access.length > 0 ? orderAccess(field.access).map(printAccess).join(" ") + " " : "") + switch (field.kind) { case FVar(t, eo): ((field.access != null && field.access.has(AFinal)) ? '' : 'var ') + '${field.name}' + opt(t, printComplexType, " : ") + opt(eo, printExpr, " = "); case FProp(get, set, t, eo): 'var ${field.name}($get, $set)' + opt(t, printComplexType, " : ") + opt(eo, printExpr, " = "); case FFun(func): 'function ${field.name}' + printFunction(func); } } public function printTypeParamDecl(tpd:TypeParamDecl) return (tpd.meta != null && tpd.meta.length > 0 ? tpd.meta.map(printMetadata).join(" ") + " " : "") + tpd.name + (tpd.params != null && tpd.params.length > 0 ? "<" + tpd.params.map(printTypeParamDecl).join(", ") + ">" : "") + (tpd.constraints != null && tpd.constraints.length > 0 ? ":(" + tpd.constraints.map(printComplexType).join(", ") + ")" : ""); public function printFunctionArg(arg:FunctionArg) return (arg.opt ? "?" : "") + arg.name + opt(arg.type, printComplexType, ":") + opt(arg.value, printExpr, " = "); public function printFunction(func:Function, ?kind:FunctionKind) { var skipParentheses = switch func.args { case [{ type:null }]: kind == FArrow; case _: false; } return (func.params == null ? "" : func.params.length > 0 ? "<" + func.params.map(printTypeParamDecl).join(", ") + ">" : "") + (skipParentheses ? "" : "(") + func.args.map(printFunctionArg).join(", ") + (skipParentheses ? "" : ")") + (kind == FArrow ? " ->" : "") + opt(func.ret, printComplexType, ":") + opt(func.expr, printExpr, " "); } public function printVar(v:Var) { var s = v.name + opt(v.type, printComplexType, ":") + opt(v.expr, printExpr, " = "); return switch v.meta { case null|[]: s; case meta: meta.map(printMetadata).join(" ") + " " + s; } } public function printObjectFieldKey(of:ObjectField) { return switch (of.quotes) { case null | Unquoted: of.field; case Quoted: '"${of.field}"'; // TODO: Have to escape that? } } public function printObjectField(of:ObjectField) { return '${printObjectFieldKey(of)} : ${printExpr(of.expr)}'; } public function printExpr(e:Expr) return e == null ? "#NULL" : switch (e.expr) { case EConst(c): printConstant(c); case EArray(e1, e2): '${printExpr(e1)}[${printExpr(e2)}]'; case EBinop(op, e1, e2): '${printExpr(e1)} ${printBinop(op)} ${printExpr(e2)}'; case EField(e1, n): '${printExpr(e1)}.$n'; case EParenthesis(e1): '(${printExpr(e1)})'; case EObjectDecl(fl): "{ " + fl.map(function(fld) return printObjectField(fld)).join(", ") + " }"; case EArrayDecl(el): '[${printExprs(el, ", ")}]'; case ECall(e1, el): '${printExpr(e1)}(${printExprs(el, ", ")})'; case ENew(tp, el): 'new ${printTypePath(tp)}(${printExprs(el, ", ")})'; case EUnop(op, true, e1): printExpr(e1) + printUnop(op); case EUnop(op, false, e1): printUnop(op) + printExpr(e1); case EFunction(FNamed(no,inlined), func): (inlined ? 'inline ' : '') + 'function $no' + printFunction(func); case EFunction(kind, func): (kind != FArrow ? "function" : "") + printFunction(func, kind); case EVars(vl): "var " + vl.map(printVar).join(", "); case EBlock([]): '{ }'; case EBlock(el): var old = tabs; tabs += tabString; var s = '{\n$tabs' + printExprs(el, ';\n$tabs'); tabs = old; s + ';\n$tabs}'; case EFor(e1, e2): 'for (${printExpr(e1)}) ${printExpr(e2)}'; case EIf(econd, eif, null): 'if (${printExpr(econd)}) ${printExpr(eif)}'; case EIf(econd, eif, eelse): 'if (${printExpr(econd)}) ${printExpr(eif)} else ${printExpr(eelse)}'; case EWhile(econd, e1, true): 'while (${printExpr(econd)}) ${printExpr(e1)}'; case EWhile(econd, e1, false): 'do ${printExpr(e1)} while (${printExpr(econd)})'; case ESwitch(e1, cl, edef): var old = tabs; tabs += tabString; var s = 'switch ${printExpr(e1)} {\n$tabs' + cl.map(function(c) return 'case ${printExprs(c.values, ", ")}' + (c.guard != null ? ' if (${printExpr(c.guard)}):' : ":") + (c.expr != null ? (opt(c.expr, printExpr)) + ";" : "")) .join('\n$tabs'); if (edef != null) s += '\n${tabs}default:' + (edef.expr == null ? "" : printExpr(edef) + ";"); tabs = old; s + '\n$tabs}'; case ETry(e1, cl): 'try ${printExpr(e1)}' + cl.map(function(c) return ' catch(${c.name}${c.type == null ? '' : (':' + printComplexType(c.type))}) ${printExpr(c.expr)}').join(""); case EReturn(eo): "return" + opt(eo, printExpr, " "); case EBreak: "break"; case EContinue: "continue"; case EUntyped(e1): "untyped " + printExpr(e1); case EThrow(e1): "throw " + printExpr(e1); case ECast(e1, cto) if (cto != null): 'cast(${printExpr(e1)}, ${printComplexType(cto)})'; case ECast(e1, _): "cast " + printExpr(e1); case EIs(e1, ct): '${printExpr(e1)} is ${printComplexType(ct)}'; case EDisplay(e1, _): '#DISPLAY(${printExpr(e1)})'; case EDisplayNew(tp): '#DISPLAY(${printTypePath(tp)})'; case ETernary(econd, eif, eelse): '${printExpr(econd)} ? ${printExpr(eif)} : ${printExpr(eelse)}'; case ECheckType(e1, ct): '(${printExpr(e1)} : ${printComplexType(ct)})'; case EMeta({ name:":implicitReturn" }, { expr:EReturn(e1) }): printExpr(e1); case EMeta(meta, e1): printMetadata(meta) + " " + printExpr(e1); } public function printExprs(el:Array, sep:String) { return el.map(printExpr).join(sep); } function printExtension(tpl:Array, fields:Array) { return '{\n$tabs>' + tpl.map(printTypePath).join(',\n$tabs>') + "," + (fields.length > 0 ? ('\n$tabs' + fields.map(printField).join(';\n$tabs') + ";\n}") : ("\n}")); } function printStructure(fields:Array) { return fields.length == 0 ? "{ }" : '{\n$tabs' + fields.map(printField).join(';\n$tabs') + ";\n}"; } public function printTypeDefinition(t:TypeDefinition, printPackage = true):String { var old = tabs; tabs = tabString; var str = t == null ? "#NULL" : (printPackage && t.pack.length > 0 && t.pack[0] != "" ? "package " + t.pack.join(".") + ";\n" : "") + (t.doc != null && t.doc != "" ? "/**\n" + tabString + StringTools.replace(t.doc, "\n", "\n" + tabString) + "\n**/\n" : "") + (t.meta != null && t.meta.length > 0 ? t.meta.map(printMetadata).join(" ") + " " : "") + (t.isExtern ? "extern " : "") + switch (t.kind) { case TDEnum: "enum " + t.name + ((t.params != null && t.params.length > 0) ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + " {\n" + [ for (field in t.fields) tabs + (field.doc != null && field.doc != "" ? "/**\n" + tabs + tabString + StringTools.replace(field.doc, "\n", "\n" + tabs + tabString) + "\n" + tabs + "**/\n" + tabs : "") + (field.meta != null && field.meta.length > 0 ? field.meta.map(printMetadata).join(" ") + " " : "") + (switch (field.kind) { case FVar(t, _): field.name + opt(t, printComplexType, ":"); case FProp(_, _, _, _): throw "FProp is invalid for TDEnum."; case FFun(func): field.name + printFunction(func); }) + ";"].join("\n") + "\n}"; case TDStructure: "typedef " + t.name + ((t.params != null && t.params.length > 0) ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + " = {\n" + [ for (f in t.fields) { tabs + printField(f) + ";"; } ].join("\n") + "\n}"; case TDClass(superClass, interfaces, isInterface, isFinal, isAbstract): (isFinal ? "final " : "") + (isAbstract ? "abstract " : "") + (isInterface ? "interface " : "class ") + t.name + (t.params != null && t.params.length > 0 ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + (superClass != null ? " extends " + printTypePath(superClass) : "") + (interfaces != null ? (isInterface ? [for (tp in interfaces) " extends " + printTypePath(tp)] : [ for (tp in interfaces) " implements " + printTypePath(tp) ]).join("") : "") + " {\n" + [ for (f in t.fields) { tabs + printFieldWithDelimiter(f); } ].join("\n") + "\n}"; case TDAlias(ct): "typedef " + t.name + ((t.params != null && t.params.length > 0) ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + " = " + (switch (ct) { case TExtend(tpl, fields): printExtension(tpl, fields); case TAnonymous(fields): printStructure(fields); case _: printComplexType(ct); }) + ";"; case TDAbstract(tthis, from, to): "abstract " + t.name + ((t.params != null && t.params.length > 0) ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + (tthis == null ? "" : "(" + printComplexType(tthis) + ")") + (from == null ? "" : [for (f in from) " from " + printComplexType(f)].join("")) + (to == null ? "" : [for (t in to) " to " + printComplexType(t)].join("")) + " {\n" + [ for (f in t.fields) { tabs + printFieldWithDelimiter(f); } ].join("\n") + "\n}"; case TDField(kind, access): tabs = old; (access != null && access.length > 0 ? access.map(printAccess).join(" ") + " " : "") + switch (kind) { case FVar(type, eo): ((access != null && access.has(AFinal)) ? '' : 'var ') + '${t.name}' + opt(type, printComplexType, " : ") + opt(eo, printExpr, " = ") + ";"; case FProp(get, set, type, eo): 'var ${t.name}($get, $set)' + opt(type, printComplexType, " : ") + opt(eo, printExpr, " = ") + ";"; case FFun(func): 'function ${t.name}' + printFunction(func) + switch func.expr { case {expr: EBlock(_)}: ""; case _: ";"; }; } } tabs = old; return str; } function printFieldWithDelimiter(f:Field):String { return printField(f) + switch (f.kind) { case FVar(_, _), FProp(_, _, _, _): ";"; case FFun({expr: null}): ";"; case FFun({expr: {expr: EBlock(_)}}): ""; case FFun(_): ";"; case _: ""; }; } function opt(v:T, f:T->String, prefix = "") return v == null ? "" : (prefix + f(v)); public function printExprWithPositions(e:Expr) { var buffer = new StringBuf(); function format4(i:Int) { return StringTools.lpad(Std.string(i), " ", 4); } function loop(tabs:String, e:Expr) { function add(s:String, ?p = null) { if (p == null) { p = e.pos; } var p = #if macro haxe.macro.Context.getPosInfos(p) #else e.pos #end; buffer.add('${format4(p.min)}-${format4(p.max)} $tabs$s\n'); } function loopI(e:Expr) loop(tabs + tabString, e); switch (e.expr) { case EConst(c): add(printConstant(c)); case EArray(e1, e2): add("EArray"); loopI(e1); loopI(e2); case EBinop(op, e1, e2): add("EBinop " + printBinop(op)); loopI(e1); loopI(e2); case EField(e, field): add("EField " + field); loopI(e); case EParenthesis(e): add("EParenthesis"); loopI(e); case EObjectDecl(fields): add("EObjectDecl"); for (field in fields) { add(field.field); // TODO: we don't have the field pos? loopI(field.expr); } case EArrayDecl(values): add("EArrayDecl"); values.iter(loopI); case ECall(e, params): add("ECall"); loopI(e); params.iter(loopI); case ENew(tp, params): add("ENew " + printTypePath(tp)); params.iter(loopI); case EUnop(op, postFix, e): add("EUnop " + printUnop(op)); loopI(e); case EVars(vars): add("EVars"); for (v in vars) { if (v.expr != null) { add(v.name); loopI(v.expr); } } case EFunction(_, f): add("EFunction"); if (f.expr != null) { loopI(f.expr); } case EBlock(exprs): add("EBlock"); exprs.iter(loopI); case EFor(it, expr): add("EFor"); loopI(it); loopI(expr); case EIf(econd, eif, eelse): add("EIf"); loopI(econd); loopI(eif); if (eelse != null) { loopI(eelse); } case EWhile(econd, e, normalWhile): add("EWhile"); loopI(econd); loopI(e); case ESwitch(e, cases, edef): add("ESwitch"); loopI(e); for (c in cases) { for (pat in c.values) { loop(tabs + tabString + tabString, pat); } if (c.expr != null) { loop(tabs + tabString + tabString + tabString, c.expr); } } if (edef != null) { loop(tabs + tabString + tabString + tabString, edef); } case ETry(e, catches): add("ETry"); loopI(e); for (c in catches) { loop(tabs + tabString + tabString, c.expr); } case EReturn(e): add("EReturn"); if (e != null) { loopI(e); } case EBreak: add("EBreak"); case EContinue: add("EContinue"); case EUntyped(e): add("EUntyped"); loopI(e); case EThrow(e): add("EThrow"); loopI(e); case ECast(e, t): add("ECast"); loopI(e); case EIs(e, t): add("EIs"); loopI(e); case EDisplay(e, displayKind): add("EDisplay"); loopI(e); case EDisplayNew(t): add("EDisplayNew"); case ETernary(econd, eif, eelse): add("ETernary"); loopI(econd); loopI(eif); loopI(eelse); case ECheckType(e, t): add("ECheckType"); loopI(e); case EMeta(s, e): add("EMeta " + printMetadata(s)); loopI(e); } } loop("", e); return buffer.toString(); } }