1// SPDX-License-Identifier: MIT
2
3using System;
4using System.Collections.Generic;
5using System.IO;
6using System.Linq;
7using System.Text;
8using GitHub.Actions.Expressions;
9using GitHub.Actions.Expressions.Data;
10using GitHub.Actions.Expressions.Sdk;
11using GitHub.Actions.Expressions.Sdk.Functions;
12using Newtonsoft.Json;
13using Newtonsoft.Json.Linq;
14
15// Differential harness: runs expressions through the runner's own
16// Expressions2 code (compiled from the runner repository) and prints one JSON
17// line per case.
18//
19// Input (argv[0]): JSON {"contexts": {name: json}, "cases": [{"e": expr, "ctx": {name: json} | null}]}
20// Output: JSON lines {"k": kind, "s": string form, "j": toJson, "err": {"t": "parse"|"eval", "m": message}}
21
22internal sealed class CtxNode : NamedValue
23{
24 protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
25 {
26 resultMemory = null;
27 return (context.State as DictionaryExpressionData)[Name];
28 }
29}
30
31internal sealed class Success : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
32internal sealed class Always : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
33internal sealed class HashFiles : Function
34{
35 protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m)
36 {
37 m = null;
38 var s = "hash:";
39 for (var i = 0; i < Parameters.Count; i++)
40 {
41 if (i > 0) s += "|";
42 s += Parameters[i].Evaluate(c).ConvertToString();
43 }
44 return s;
45 }
46}
47
48internal static class P
49{
50 static DictionaryExpressionData Load(JObject ctx)
51 {
52 var d = new DictionaryExpressionData();
53 foreach (var p in ctx.Properties())
54 {
55 var token = p.Value;
56 d[p.Name] = token.ToExpressionData();
57 }
58 return d;
59 }
60
61 static int Main(string[] args)
62 {
63 JObject root; using (var jr0 = new JsonTextReader(new StringReader(File.ReadAllText(args[0]))) { DateParseHandling = DateParseHandling.None }) root = JObject.Load(jr0);
64 var defaultCtx = (JObject)root["contexts"];
65 var funcs = new IFunctionInfo[]
66 {
67 new FunctionInfo<Success>("success", 0, 0),
68 new FunctionInfo<Always>("always", 0, 0),
69 new FunctionInfo<HashFiles>("hashFiles", 1, 255),
70 };
71 var output = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false));
72 var defaultData = Load(defaultCtx);
73 foreach (var c in (JArray)root["cases"])
74 {
75 var e = (string)c["e"];
76 var custom = c["ctx"] as JObject;
77 var data = custom != null ? Load(custom) : defaultData;
78 var names = (custom ?? defaultCtx).Properties().Select(p => p.Name);
79 var res = new JObject();
80 IExpressionNode tree = null;
81 try
82 {
83 tree = new ExpressionParser().CreateTree(e, null, names.Select(n => (INamedValueInfo)new NamedValueInfo<CtxNode>(n)).ToList(), funcs);
84 }
85 catch (Exception ex)
86 {
87 res["err"] = new JObject { ["t"] = "parse", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
88 }
89 if (res["err"] == null)
90 {
91 if (tree == null)
92 {
93 res["empty"] = true;
94 }
95 else
96 {
97 try
98 {
99 var r = tree.Evaluate(null, null, data, null);
100 res["k"] = r.Kind.ToString();
101 res["s"] = r.ConvertToString();
102 // toJson of the result
103 var tj = new ToJson();
104 tj.AddParameter(new Literal(r.Value));
105 try
106 {
107 var jr = ((IExpressionNode)tj).Evaluate(null, null, data, null);
108 res["j"] = jr.ConvertToString();
109 }
110 catch (Exception ex2)
111 {
112 res["jerr"] = ex2.Message;
113 }
114 }
115 catch (Exception ex)
116 {
117 res["err"] = new JObject { ["t"] = "eval", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
118 }
119 }
120 }
121 // ASCII-escaped, so that a lone surrogate (which UTF-8 cannot carry) survives.
122 var sw = new StringWriter();
123 using (var jw = new JsonTextWriter(sw) { StringEscapeHandling = StringEscapeHandling.EscapeNonAscii, Formatting = Formatting.None })
124 {
125 res.WriteTo(jw);
126 }
127 output.WriteLine(sw.ToString());
128 }
129 output.Flush();
130 return 0;
131 }
132}