├── AST.pq
├── AST2json.pq
├── Decompile.pq
├── Decompiler
├── Decompiler.sln
└── Decompiler
│ ├── Decompiler.mproj
│ ├── Decompiler.pq
│ ├── Decompiler.query.pq
│ ├── Decompiler16.png
│ ├── Decompiler20.png
│ ├── Decompiler24.png
│ ├── Decompiler32.png
│ ├── Decompiler40.png
│ ├── Decompiler48.png
│ ├── Decompiler64.png
│ ├── Decompiler80.png
│ ├── Diagnostics.pq
│ ├── RowExpression.From.pq
│ └── resources.resx
├── Expression.pq
├── FunctionSignature.pq
├── LICENSE
├── README.md
└── img
└── DecompileCall.PNG
/AST.pq:
--------------------------------------------------------------------------------
1 | =Value.ResourceExpression(List.Range)[Expression][Arguments]{0}[Function][Expression][Expression]
--------------------------------------------------------------------------------
/AST2json.pq:
--------------------------------------------------------------------------------
1 | (placeholder) =>
2 | let
3 | resource = Value.ResourceExpression(placeholder),
4 | func = (_) => try Value.Metadata(Value.Type(_))[Documentation.Name] otherwise
5 | try Value.ResourceExpression(_)[Name] otherwise "function",
6 | trace = (_) => if _ is function then func(_)
7 | else if _ is type then "type"
8 | else if _ is null then "null"
9 | else if _ is list then List.Accumulate(_,{},(s,c)=>s&{@trace(c)})
10 | else if _ is record then List.Accumulate(Record.FieldNames(_),[],(s,c)=>Record.AddField(s,c,@trace(Record.Field(_,c))))
11 | else Text.From(_)
12 | in Json.FromValue(trace(resource))
--------------------------------------------------------------------------------
/Decompile.pq:
--------------------------------------------------------------------------------
1 | (fn) =>
2 | let
3 | // Entry point for routing the calls for various AST node "Kind"s
4 | handleExp = (x) =>
5 | let
6 | kind = x[Kind],
7 | expr = if (kind = "Invocation") then invocationExp(x) else
8 | if (kind = "ElementAccess") then elementAccessExp(x) else
9 | if (kind = "If") then ifExp(x) else
10 | if (kind = "Binary") then binaryExp(x) else
11 | if (kind = "FieldAccess") then fieldAccessExp(x) else
12 | if (kind = "Identifier") then identifierExp(x) else
13 | if (kind = "Constant") then constantExp(x) else
14 | Text.Format("Unkonw Kind: #{0}",{kind})
15 | in
16 | expr,
17 |
18 | // "Invocation" node handling
19 | invocationExp = (x) =>
20 | let
21 | f = x[Function],
22 | args = x[Arguments],
23 | funcStr = handleExp(f),
24 | argsStr = Text.Combine(List.Transform(args, each handleExp(_)),",")
25 | in
26 | Text.Format("#{0}(#{1})",{funcStr, argsStr}),
27 |
28 | // Get function name from a handle
29 | // TODO: have a peek at #shared first as docs are missleading
30 | functionName = (x) =>
31 | //try getting the name from the associated meta Documentation
32 | try Value.Metadata(Value.Type(x))[Documentation.Name]
33 | otherwise
34 | // or "ResourceExpression"
35 | try Value.ResourceExpression(x)[Name] otherwise "function",
36 |
37 | // "If" node handling
38 | ifExp = (x) =>
39 | let
40 | cond = handleExp(x[Condition]),
41 | left = handleExp(x[TrueCase]),
42 | right = handleExp(x[FalseCase])
43 | in
44 | Text.Format("if(#{0}) then #{1} else #{2}", { cond, left, right }),
45 |
46 | // "Operator" node handling
47 | operatorExp = (x) =>
48 | let
49 | op =
50 | if (x = "Equals") then "=" else
51 | if (x = "NotEquals") then "!=" else
52 | if (x = "GreaterThan") then ">" else
53 | if (x = "GreaterThanOrEquals") then ">=" else
54 | if (x = "LessThan") then "<" else
55 | if (x = "LessThanOrEquals") then "<=" else
56 | if (x = "And") then "and" else
57 | if (x = "Or") then "or" else
58 | if (x = "Not") then "not" else
59 | if (x = "Add") then "+" else
60 | if (x = "Subtract") then "-" else
61 | if (x = "Multiply") then "*" else
62 | if (x = "Divide") then "/" else
63 | if (x = "Concatenate") then "&" else
64 | x
65 | in
66 | op,
67 | // "Identifier" node handling
68 | identifierExp = (x) => x[Name],
69 | // "Constnt" node handling
70 | constantExp = (x) => x[Value],
71 | // "FieldAccess" node handling
72 | fieldAccessExp = (x) => x[MemberName],
73 |
74 | // "ElementAccess" node handling
75 | elementAccessExp = (x) =>
76 | let
77 | refList = x[Collection][Value],
78 | idx = x[Key][Value]
79 | in functionName(refList{idx}),
80 |
81 | // Handles node of a "Binary" expression
82 | binaryExp = (x) =>
83 | let
84 | op = operatorExp(x[Operator]),
85 | left = handleExp(x[Left]),
86 | right = handleExp(x[Right]),
87 | format = if (op = "&") then "#{0} & #{2}"
88 | else "#{0} #{1} #{2}"
89 | in
90 | Text.Format(format, {left, op, right}),
91 |
92 | // Looping through the Members of a shallow AST and calls expression->text function:handleExp(x)
93 | run = (x)=>
94 | let
95 | members = x[Members]
96 | in
97 | List.Transform(members,each Text.Format("#{0} = #{1}",{_[Name],handleExp(_[Value])})),
98 |
99 | AST = Value.ResourceExpression(fn)[Expression][Arguments]{0}[Function][Expression][Expression],
100 | expression = run(AST)
101 | in
102 | expression
--------------------------------------------------------------------------------
/Decompiler/Decompiler.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Decompiler", "Decompiler\Decompiler.mproj", "{E6D19E2E-3BB6-466B-A579-C4F840F09D4B}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x86 = Debug|x86
11 | Release|x86 = Release|x86
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {E6D19E2E-3BB6-466B-A579-C4F840F09D4B}.Debug|x86.ActiveCfg = Debug|x86
15 | {E6D19E2E-3BB6-466B-A579-C4F840F09D4B}.Debug|x86.Build.0 = Debug|x86
16 | {E6D19E2E-3BB6-466B-A579-C4F840F09D4B}.Release|x86.ActiveCfg = Release|x86
17 | {E6D19E2E-3BB6-466B-A579-C4F840F09D4B}.Release|x86.Build.0 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {86AFB98A-7D7C-4F27-953B-B4136DECB826}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Decompiler
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | bin\Release\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 | Code
45 |
46 |
47 | Code
48 |
49 |
50 | Code
51 |
52 |
53 | Code
54 |
55 |
56 | Code
57 |
58 |
59 | Code
60 |
61 |
62 | Code
63 |
64 |
65 | Code
66 |
67 |
68 | Code
69 |
70 |
71 | Code
72 |
73 |
74 | Content
75 |
76 |
77 | Content
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler.pq:
--------------------------------------------------------------------------------
1 | // This file contains your Data Connector logic
2 | section Decompiler;
3 |
4 | [DataSource.Kind="Decompiler", Publish="Decompiler.Publish"]
5 | shared Decompiler.Contents = (optional message as text) =>
6 | let
7 | _message = if (message <> null) then message else "(no message)",
8 | a = "Hello from Decompiler: " & _message
9 | in
10 | a;
11 |
12 | // Data Source Kind description
13 | Decompiler = [
14 | Authentication = [
15 | // Key = [],
16 | // UsernamePassword = [],
17 | // Windows = [],
18 | Implicit = []
19 | ],
20 | Label = Extension.LoadString("DataSourceLabel")
21 | ];
22 |
23 | // Data Source UI publishing description
24 | Decompiler.Publish = [
25 | Beta = true,
26 | Category = "Other",
27 | ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") },
28 | LearnMoreUrl = "https://powerbi.microsoft.com/",
29 | SourceImage = Decompiler.Icons,
30 | SourceTypeImage = Decompiler.Icons
31 | ];
32 |
33 | Decompiler.Icons = [
34 | Icon16 = { Extension.Contents("Decompiler16.png"), Extension.Contents("Decompiler20.png"), Extension.Contents("Decompiler24.png"), Extension.Contents("Decompiler32.png") },
35 | Icon32 = { Extension.Contents("Decompiler32.png"), Extension.Contents("Decompiler40.png"), Extension.Contents("Decompiler48.png"), Extension.Contents("Decompiler64.png") }
36 | ];
37 |
38 |
39 | // Utils
40 |
41 |
42 | Extension.LoadFunction = (name as text) =>
43 | let
44 | binary = Extension.Contents(name),
45 | asText = Text.FromBinary(binary)
46 | in
47 | Expression.Evaluate(asText, #shared);
48 |
49 | RowExpression.From = Extension.LoadFunction("RowExpression.From.pq");
50 |
51 | Diagnostics = Extension.LoadFunction("Diagnostics.pq");
52 |
53 | Diagnostics.LogValue = Diagnostics[LogValue];
54 | Diagnostics.LogValue2 = Diagnostics[LogValue2];
55 | Diagnostics.LogFailure = Diagnostics[LogFailure];
56 | Diagnostics.WrapFunctionResult = Diagnostics[WrapFunctionResult];
57 | Diagnostics.WrapHandlers = Diagnostics[WrapHandlers];
58 |
59 | Value.IfNull = (a, b) => if a <> null then a else b;
60 |
61 | Value.ToText = (value, optional depth) =>
62 | let
63 | nextDepth = if depth = null then 3 else depth - 1,
64 | result = if depth = 0 then "..."
65 | else if value is null then ""
66 | else if value is function then ""
67 | else if value is table then "#table({" & Text.Combine(Table.ColumnNames(value), ",") & "},{" & Text.Combine(
68 | List.Transform(Table.ToRows(Table.FirstN(value, 2)), each @Value.ToText(_, nextDepth)), "},#(cr)#(lf){") & "})"
69 | //& "Row Count (" & Number.ToText(Table.RowCount(value)) & ")"
70 | else if value is list then "{" & Text.Combine(List.Transform(List.FirstN(value, 10), each @Value.ToText(_, nextDepth)), ",") & "}"
71 | else if value is record then "[" & Text.Combine(List.Transform(Record.FieldNames(value), each _ & "=" & @Value.ToText(Record.Field(value, _), nextDepth)), ",") & "]"
72 | else if value is type then List.First(Table.Schema(#table({"type"}, {{value}}))[TypeName], "")
73 | else Text.From(value)
74 | in
75 | try result otherwise "";
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler.query.pq:
--------------------------------------------------------------------------------
1 | // Use this file to write queries to test your data connector
2 | let
3 | result = Decompiler.Contents()
4 | in
5 | result
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler16.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler20.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler24.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler32.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler40.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler48.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler64.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Decompiler80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/Decompiler/Decompiler/Decompiler80.png
--------------------------------------------------------------------------------
/Decompiler/Decompiler/Diagnostics.pq:
--------------------------------------------------------------------------------
1 | let
2 | Value.ToText = (value, optional depth) =>
3 | let
4 | nextDepth = if depth = null then 3 else depth - 1,
5 | result = if depth = 0 then "..."
6 | else if value is null then ""
7 | else if value is function then ""
8 | else if value is table then "#table({" & Text.Combine(Table.ColumnNames(value), ",") & "},{" & Text.Combine(
9 | List.Transform(Table.ToRows(Table.FirstN(value, 2)), each @Value.ToText(_, nextDepth)), "},#(cr)#(lf){") & "})"
10 | //& "Row Count (" & Number.ToText(Table.RowCount(value)) & ")"
11 | else if value is list then "{" & Text.Combine(List.Transform(List.FirstN(value, 10), each @Value.ToText(_, nextDepth)), ",") & "}"
12 | else if value is record then "[" & Text.Combine(List.Transform(Record.FieldNames(value), each _ & "=" & @Value.ToText(Record.Field(value, _), nextDepth)), ",") & "]"
13 | else if value is type then List.First(Table.Schema(#table({"type"}, {{value}}))[TypeName], "")
14 | else Text.From(value)
15 | in
16 | try result otherwise "",
17 | Diagnostics.LogValue = (prefix, value, result, optional delayed) => Diagnostics.Trace(TraceLevel.Information, prefix & ": " & Value.ToText(value), result, delayed),
18 | Diagnostics.LogValue2 = (prefix, value) => Diagnostics.Trace(TraceLevel.Information, prefix & ": " & Value.ToText(value), value),
19 | Diagnostics.LogFailure = (text, function) =>
20 | let
21 | result = try function()
22 | in
23 | if result[HasError] then Diagnostics.LogValue(text, result[Error], () => error result[Error], true) else result[Value],
24 | Diagnostics.WrapFunctionResult = (innerFunction as function, outerFunction as function) as function =>
25 | Function.From(Value.Type(innerFunction), (list) => outerFunction(Function.Invoke(innerFunction, list))),
26 | Diagnostics.WrapHandlers = (handlers as record) as record =>
27 | Record.FromList(
28 | List.Transform(
29 | Record.FieldNames(handlers),
30 | (h) => Diagnostics.WrapFunctionResult(Record.Field(handlers, h), (fn) => Diagnostics.LogFailure(h, fn))),
31 | Record.FieldNames(handlers))
32 | in
33 | [ LogValue = Diagnostics.LogValue, LogValue2 = Diagnostics.LogValue2, LogFailure = Diagnostics.LogFailure, WrapFunctionResult = Diagnostics.WrapFunctionResult, WrapHandlers = Diagnostics.WrapHandlers]
--------------------------------------------------------------------------------
/Decompiler/Decompiler/RowExpression.From.pq:
--------------------------------------------------------------------------------
1 | (fn, depth)=>
2 | let
3 | Value.FixType = (value, optional depth) =>
4 | let
5 | nextDepth = if depth = null then 3 else depth - 1,
6 | result = if depth = 0 then null
7 | else if value is type then TextType(value)
8 | else if value is table then Table.TransformColumns(value, {}, @Value.FixType)
9 | else if value is list then List.Transform(value, each @Value.FixType(_, nextDepth))
10 | else if value is record then
11 | Record.FromList(List.Transform(Record.ToList(value), each @Value.FixType(_, nextDepth)), Record.FieldNames(value))
12 | else if value is function then ""
13 | else value
14 | in
15 | result,
16 |
17 | TextType = (t as type) as text =>
18 | let
19 | nonNullableType = Type.NonNullable(t),
20 | TypeDescription = if Type.Is(nonNullableType, type binary) then "binary"
21 | else if Type.Is(nonNullableType, type date) then "date"
22 | else if Type.Is(nonNullableType, type datetime) then "datetime"
23 | else if Type.Is(nonNullableType, type datetimezone) then "datetimezone"
24 | else if Type.Is(nonNullableType, type duration) then "duration"
25 | else if Type.Is(nonNullableType, type function) then "function"
26 | else if Type.Is(nonNullableType, type list) then "list"
27 | else if Type.Is(nonNullableType, type logical) then "logical"
28 | else if Type.Is(nonNullableType, type none) then "none"
29 | else if Type.Is(nonNullableType, type null) then "null"
30 | else if Type.Is(nonNullableType, type number) then "number"
31 | else if Type.Is(nonNullableType, type record) then "record"
32 | else if Type.Is(nonNullableType, type table) then "table"
33 | else if Type.Is(nonNullableType, type text) then "text"
34 | else if Type.Is(nonNullableType, type time) then "time"
35 | else if Type.Is(nonNullableType, type type) then "type"
36 | else if Type.Is(nonNullableType, type action) then "action"
37 | else if Type.Is(type anynonnull, nonNullableType) then "any"
38 | else error "Unknown type",
39 | TypeString = if TypeDescription = "any" then
40 | if Type.IsNullable(t) then
41 | "any" else "anynonnull"
42 | else if Type.IsNullable(t) then
43 | "nullable " & TypeDescription
44 | else TypeDescription
45 | in
46 | TypeString,
47 |
48 | FunctionSignature = (functionType as type) =>
49 | let
50 | Parameters = Record.ToTable(Type.FunctionParameters(functionType)), // Name, Value
51 | WithIndex = Table.AddIndexColumn(Parameters, "Required"),
52 | MinParameters = Type.FunctionRequiredParameters(functionType),
53 | WithRequired = Table.TransformColumns(WithIndex, {{"Required", (index) => index < MinParameters}}),
54 | WithNullable = Table.AddColumn(WithRequired, "Nullable", each Type.IsNullable([Value])),
55 | WithMetadata = Table.AddColumn(WithNullable, "Metadata", each Value.Metadata([Value])),
56 | WithDocumentation = Table.ExpandRecordColumn(WithMetadata, "Metadata",
57 | {"Documentation.FieldCaption", "Documentation.FieldDescription", "Documentation.SampleValues", "Documentation.AllowedValues", "Documentation.DefaultValue"},
58 | {"fieldCaption", "fieldDescription", "sampleValues", "allowedValue", "defaultValue"}),
59 | // TODO: Add documentation to the allowed values
60 | Renamed = Table.RenameColumns(WithDocumentation,
61 | {
62 | {"Name", "name"},
63 | {"Value", "parameterType"},
64 | {"Required", "isRequired"},
65 | {"Nullable", "isNullable"}
66 | })
67 | in
68 | Renamed,
69 |
70 | Value.ToJsonText = (x,d) => Text.FromBinary(Json.FromValue(Value.FixType(x, d))),
71 | Result = Value.ToJsonText(fn,depth)
72 | in
73 | Result
--------------------------------------------------------------------------------
/Decompiler/Decompiler/resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Connect to Decompiler
122 |
123 |
124 | Decompiler
125 |
126 |
127 | Decompiler
128 |
129 |
--------------------------------------------------------------------------------
/Expression.pq:
--------------------------------------------------------------------------------
1 | let
2 | // Entry point for routing the calls for various AST node "Kind"s
3 | // TODO: throw
4 | handleExp = (x) =>
5 | let
6 | kind = x[Kind],
7 | expr = if (kind = "Invocation") then invocationExp(x) else
8 | if (kind = "ElementAccess") then elementAccessExp(x) else
9 | if (kind = "If") then ifExp(x) else
10 | if (kind = "Binary") then binaryExp(x) else
11 | if (kind = "FieldAccess") then fieldAccessExp(x) else
12 | if (kind = "Identifier") then identifierExp(x) else
13 | if (kind = "Constant") then constantExp(x) else
14 | if (kind = "Record") then recordExp(x) else
15 | if (kind = "Unary") then unaryExp(x) else
16 | if (kind = "Throw") then throwExp(x) else
17 | if (kind = "Function") then funcExp(x) else
18 | Text.Format("Unkonw Kind: #{0}",{kind})
19 | in
20 | expr,
21 |
22 | // Handle records
23 | recordExp = (x) =>
24 | let
25 | members = x[Members],
26 | lines = List.Transform(members,
27 | each Text.Format("#{0} = #{1}",
28 | {_[Name],handleExp(_[Value])})
29 | )
30 | in
31 | Text.Combine(lines,"#(cr)#(lf)"),
32 |
33 | // Handle Unary expressions
34 | unaryExp = (x) =>
35 | let
36 | op = x[Operator],
37 | inExp = x[Expression],
38 | kind = inExp[Kind],
39 | exp = if (op = "Not") then "not (" & handleExp(inExp) & ")" else
40 | if (op = "Negative") then "-(" & handleExp(inExp) & ")" else
41 | handleExp(inExp)
42 | in
43 | exp,
44 |
45 | // Throw
46 | throwExp = (x) =>Text.Format("error #{0}", handleExp(x[Expression])),
47 |
48 | // Function kind
49 | funcExp = (x) =>"[function]",
50 |
51 | // "Invocation" node handling
52 | invocationExp = (x) =>
53 | let
54 | f = x[Function],
55 | args = x[Arguments],
56 | funcStr = handleExp(f),
57 | argsStr = Text.Combine(List.Transform(args, each handleExp(_)),",")
58 | in
59 | Text.Format("#{0}(#{1})",{funcStr, argsStr}),
60 |
61 | // Get function name from a handle
62 | // TODO: have a peek at #shared first as docs are missleading
63 | functionName = (x) =>
64 | //try getting the name from the associated meta Documentation
65 | try Value.Metadata(Value.Type(x))[Documentation.Name]
66 | otherwise
67 | // or "ResourceExpression"
68 | try Value.ResourceExpression(x)[Name] otherwise "function",
69 |
70 | // "If" node handling
71 | ifExp = (x) =>
72 | let
73 | cond = handleExp(x[Condition]),
74 | left = handleExp(x[TrueCase]),
75 | right = handleExp(x[FalseCase])
76 | in
77 | Text.Format("if(#{0}) then #{1} else #{2}", { cond, left, right }),
78 |
79 | // "Operator" node handling
80 | operatorExp = (x) =>
81 | let
82 | op =
83 | if (x = "Equals") then "=" else
84 | if (x = "NotEquals") then "!=" else
85 | if (x = "GreaterThan") then ">" else
86 | if (x = "GreaterThanOrEquals") then ">=" else
87 | if (x = "LessThan") then "<" else
88 | if (x = "LessThanOrEquals") then "<=" else
89 | if (x = "And") then "and" else
90 | if (x = "Or") then "or" else
91 | if (x = "Not") then "not" else
92 | if (x = "Add") then "+" else
93 | if (x = "Subtract") then "-" else
94 | if (x = "Multiply") then "*" else
95 | if (x = "Divide") then "/" else
96 | if (x = "Concatenate") then "&" else
97 | x
98 | in
99 | op,
100 | // "Identifier" node handling
101 | identifierExp = (x) => x[Name],
102 | // "Constnt" node handling
103 | constantExp = (x) => x[Value],
104 | // "FieldAccess" node handling
105 | fieldAccessExp = (x) => x[MemberName],
106 |
107 | // "ElementAccess" node handling
108 | elementAccessExp = (x) =>
109 | let
110 | refList = x[Collection][Value],
111 | idx = x[Key][Value]
112 | in functionName(refList{idx}),
113 |
114 | // Handles node of a "Binary" expression
115 | binaryExp = (x) =>
116 | let
117 | op = operatorExp(x[Operator]),
118 | left = handleExp(x[Left]),
119 | right = handleExp(x[Right]),
120 | format = if (op = "&") then "#{0} & #{2}"
121 | else "#{0} #{1} #{2}"
122 | in
123 | Text.Format(format, {left, op, right}),
124 |
125 | // Looping through the Members of a shallow AST and calls expression->text function:handleExp(x)
126 | run = (x)=>
127 | let
128 | members = x[Members]
129 | in
130 | List.Transform(members,
131 | each Text.Format("#{0} = #{1}",
132 | {_[Name],handleExp(_[Value])})
133 | ),
134 |
135 | // Function to decompile
136 | fn = List.Range,
137 | // Getting a "shalow" view on the AST
138 | // TODO: replace with a search
139 | AST =
140 | try Value.ResourceExpression(fn)[Expression][Function][Expression] otherwise
141 | Value.ResourceExpression(fn)[Expression][Arguments]{0}[Function][Expression][Expression],
142 | // Start
143 | expression = run(AST)
144 | in
145 | expression
--------------------------------------------------------------------------------
/FunctionSignature.pq:
--------------------------------------------------------------------------------
1 | (placeholder as function)=>
2 | let
3 | //Serialize type to text
4 | TypeAsText = (value as any) =>
5 | let
6 | prefix = if Type.IsNullable(value) then "nullable " else ""
7 | in
8 | prefix&(
9 | if Type.Is(value, type binary) then "binary" else
10 | if Type.Is(value, type date) then "date" else
11 | if Type.Is(value, type datetime) then "datetime" else
12 | if Type.Is(value, type datetimezone) then "datetimezone" else
13 | if Type.Is(value, type duration) then "duration" else
14 | if Type.Is(value, type function) then "function" else
15 | if Type.Is(value, type list) then "list" else
16 | if Type.Is(value, type logical) then "logical" else
17 | if Type.Is(value, type none) then "none" else
18 | if Type.Is(value, type null) then "null" else
19 | if Type.Is(value, type number) then "number" else
20 | if Type.Is(value, type record) then "record" else
21 | if Type.Is(value, type table) then "table" else
22 | if Type.Is(value, type text) then "text" else
23 | if Type.Is(value, type time) then "time" else
24 | if Type.Is(value, type type) then "type" else
25 | if Type.Is(value, type any) then "any"
26 | else error "unknown"),
27 | //if parameter is Optional set prefix
28 | OptionalPrefix = (_)=>if Type.IsNullable(_) then "optional " else "",
29 | //get list of function parameters
30 | parameters = Type.FunctionParameters(Value.Type(placeholder)),
31 | //create a text list of parameters and associate types "[optional] paramname as type"
32 | parametersWithTypes = List.Accumulate(Record.FieldNames(parameters),{},
33 | (state,cur)=>state&{
34 | OptionalPrefix(Record.Field(parameters,cur))&
35 | cur&" as "&TypeAsText(Record.Field(parameters,cur))})
36 | in
37 | //merge parameter list and prefix with "function (" and suffix with function return type
38 | "function ("&
39 | Text.Combine(parametersWithTypes,", ")&
40 | ") as "&
41 | TypeAsText(Type.FunctionReturn(Value.Type(placeholder)))
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Power Query Decompiler
2 | This repository contains a proof of concept on decompiling Power Query (M) functions.
3 | 
4 |
5 | ## Structure
6 |
7 | `AST.pq` - a deep call to `Value.ResourceExpression` in order to demo how the Abstract Syntax Tree (AST) looks for `List.Range`
8 | ```
9 | =Value.ResourceExpression(List.Range)
10 | [Expression]
11 | [Arguments]{0}
12 | [Function]
13 | [Expression]
14 | [Expression]
15 | ```
16 |
17 | `AST2json.pq` - serializing the output from `Value.ResourceExpression` to `JSON`
18 |
19 | `Decompile.pq` - function definition that translates an AST into an expression
20 |
21 | `Expression.pq` - same code as `Decompile(x)` function exposed as an expression(query)
22 |
23 | ## Functions that are "decompilable"
24 |
25 | The query that would return the list of functions that should deompile is:
26 | ```
27 | = List.Select(Record.FieldNames(#shared),each Value.ResourceExpression(Record.Field(#shared,_))[Kind]="Function")
28 | ```
29 | Which brings up this output:
30 |
31 | `Date.DayOfWeekName` , `Date.IsInCurrentDay` , `Date.IsInCurrentMonth` , `Date.IsInCurrentQuarter` , `Date.IsInCurrentWeek` , `Date.IsInCurrentYear` , `Date.IsInNextDay` , `Date.IsInNextMonth` , `Date.IsInNextNDays` , `Date.IsInNextNMonths` , `Date.IsInNextNQuarters` , `Date.IsInNextNWeeks` , `Date.IsInNextNYears` , `Date.IsInNextQuarter` , `Date.IsInNextWeek` , `Date.IsInNextYear` , `Date.IsInPreviousDay` , `Date.IsInPreviousMonth` , `Date.IsInPreviousNDays` , `Date.IsInPreviousNMonths` , `Date.IsInPreviousNQuarters` , `Date.IsInPreviousNWeeks` , `Date.IsInPreviousNYears` , `Date.IsInPreviousQuarter` , `Date.IsInPreviousWeek` , `Date.IsInPreviousYear` , `Date.IsInYearToDate` , `Date.MonthName` , `DateTime.IsInCurrentHour` , `DateTime.IsInCurrentMinute` , `DateTime.IsInCurrentSecond` , `DateTime.IsInNextHour` , `DateTime.IsInNextMinute` , `DateTime.IsInNextNHours` , `DateTime.IsInNextNMinutes` , `DateTime.IsInNextNSeconds` , `DateTime.IsInNextSecond` , `DateTime.IsInPreviousHour` , `DateTime.IsInPreviousMinute` , `DateTime.IsInPreviousNHours` , `DateTime.IsInPreviousNMinutes` , `DateTime.IsInPreviousNSeconds` , `DateTime.IsInPreviousSecond` , `List.FindText` , `List.MatchesAll` , `List.MatchesAny` , `List.NonNullCount` , `List.Range` , `List.RemoveItems` , `List.RemoveLastN` , `List.ReplaceValue` , `Replacer.ReplaceValue` , `SqlExpression.SchemaFrom` , `Table.AddColumn` , `Table.AlternateRows` , `Table.Buffer` , `Table.ColumnCount` , `Table.ColumnsOfType` , `Table.CombineColumns` , `Table.Contains` , `Table.ContainsAll` , `Table.ContainsAny` , `Table.DemoteHeaders` , `Table.DuplicateColumn` , `Table.ExpandListColumn` , `Table.ExpandTableColumn` , `Table.FillUp` , `Table.FindText` , `Table.FirstValue` , `Table.HasColumns` , `Table.InsertRows` , `Table.IsDistinct` , `Table.IsEmpty` , `Table.Last` , `Table.LastN` , `Table.MatchesAllRows` , `Table.MatchesAnyRows` , `Table.Max` , `Table.MaxN` , `Table.Min` , `Table.MinN` , `Table.Partition` , `Table.PositionOf` , `Table.PositionOfAny` , `Table.PrefixColumns` , `Table.Profile` , `Table.Range` , `Table.RemoveLastN` , `Table.RemoveMatchingRows` , `Table.RemoveRows` , `Table.Repeat` , `Table.ReplaceMatchingRows` , `Table.ReplaceRows` , `Table.ReplaceValue` , `Table.ReverseRows` , `Table.Schema` , `Table.SplitColumn` , `Table.ToColumns` , `Table.ToRows` , `Table.TransformRows` , `Table.Transpose` , `Table.View` , `Text.AfterDelimiter` , `Text.BeforeDelimiter` , `Text.BetweenDelimiters` , `Text.Format` , `Type.TableSchema`
32 |
--------------------------------------------------------------------------------
/img/DecompileCall.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryDecompiler/d1bf7ba7fc6a9aec0325d45fbc0570b8f45bbe7c/img/DecompileCall.PNG
--------------------------------------------------------------------------------