1// SPDX-License-Identifier: MIT
2
3using System;
4using System.Collections.Generic;
5using System.IO;
6using System.Linq;
7using System.Text;
8using GitHub.DistributedTask.Expressions2;
9using GitHub.DistributedTask.Expressions2.Sdk;
10using GitHub.DistributedTask.Expressions2.Sdk.Functions;
11using GitHub.DistributedTask.Pipelines.ContextData;
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 DictionaryContextData)[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 DictionaryContextData Load(JObject ctx)
51 {
52 var d = new DictionaryContextData();
53 foreach (var p in ctx.Properties())
54 {
55 var token = p.Value;
56 d[p.Name] = token.ToPipelineContextData();
57 }
58 return d;
59 }
60
61 static int Main(string[] args)
62 {
63 if (args[0] == "--fold")
64 {
65 // Debug aid: the case folding of StringComparison.OrdinalIgnoreCase. Prints "XXXX YYYY" (hexadecimal
66 // code points) for every code point c whose class representative m(c) differs from c, after checking
67 // that string.Equals(a, b, OrdinalIgnoreCase) holds exactly when m(a) == m(b) over all the affected
68 // code points. m(c) is ToUpperInvariant(c) when OrdinalIgnoreCase agrees that they are equal.
69 var map = new SortedDictionary<int, int>();
70 for (var c = 0; c <= 0x10FFFF; c++)
71 {
72 if (c >= 0xD800 && c <= 0xDFFF) continue;
73 var str = char.ConvertFromUtf32(c);
74 var up = str.ToUpperInvariant();
75 if (up != str && char.ConvertToUtf32(up, 0) is var u && up.Length == char.ConvertFromUtf32(u).Length
76 && string.Equals(str, up, StringComparison.OrdinalIgnoreCase))
77 {
78 map[c] = u;
79 }
80 }
81 var members = new SortedSet<int>(map.Keys);
82 foreach (var v in map.Values) members.Add(v);
83 var list = new List<int>(members);
84 Func<int, int> m = c => map.TryGetValue(c, out var x) ? x : c;
85 var bad = 0;
86 foreach (var a in list)
87 {
88 foreach (var b in list)
89 {
90 var eq = string.Equals(char.ConvertFromUtf32(a), char.ConvertFromUtf32(b), StringComparison.OrdinalIgnoreCase);
91 if (eq != (m(a) == m(b))) { bad++; if (bad < 10) Console.Error.WriteLine($"inconsistent: {a:X} {b:X}"); }
92 }
93 }
94 // Nothing outside the table may be equal to anything but itself.
95 for (var c = 0; c <= 0x10FFFF; c++)
96 {
97 if (c >= 0xD800 && c <= 0xDFFF) continue;
98 if (members.Contains(c)) continue;
99 var str = char.ConvertFromUtf32(c);
100 foreach (var d in new[] { c ^ 1, c + 1, c - 1 })
101 {
102 if (d < 0 || d > 0x10FFFF || (d >= 0xD800 && d <= 0xDFFF)) continue;
103 if (string.Equals(str, char.ConvertFromUtf32(d), StringComparison.OrdinalIgnoreCase)) { bad++; if (bad < 10) Console.Error.WriteLine($"outside: {c:X} {d:X}"); }
104 }
105 }
106 Console.Error.WriteLine($"{map.Count} mappings, {bad} inconsistencies");
107 foreach (var kv in map) Console.WriteLine($"{kv.Key:X} {kv.Value:X}");
108 return bad == 0 ? 0 : 1;
109 }
110 if (args[0] == "--cmp")
111 {
112 // Debug aid: each input line is two space-separated strings written as dot-separated hexadecimal UTF-16 code
113 // units; prints Equals, Compare sign, Contains, StartsWith and EndsWith, all OrdinalIgnoreCase.
114 Func<string, string> parse = h => { var sb = new StringBuilder(); if (h != "-") foreach (var u in h.Split('.')) sb.Append((char)Convert.ToInt32(u, 16)); return sb.ToString(); };
115 foreach (var line in File.ReadLines(args[1]))
116 {
117 var parts = line.Split(' ');
118 var a = parse(parts[0]);
119 var b = parse(parts[1]);
120 Console.WriteLine($"{(string.Equals(a, b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)} {Math.Sign(string.Compare(a, b, StringComparison.OrdinalIgnoreCase))} {(a.IndexOf(b, StringComparison.OrdinalIgnoreCase) >= 0 ? 1 : 0)} {(a.StartsWith(b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)} {(a.EndsWith(b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)}");
121 }
122 return 0;
123 }
124 if (args[0] == "--num")
125 {
126 // Debug aid: each input line is a string as dot-separated hexadecimal UTF-16 code units ("-" for the
127 // empty string); prints the IEEE 754 bits (hexadecimal) of ExpressionUtility.ParseNumber of it.
128 foreach (var line in File.ReadLines(args[1]))
129 {
130 var sb = new StringBuilder();
131 if (line.Trim() != "-") foreach (var u in line.Trim().Split('.')) sb.Append((char)Convert.ToInt32(u, 16));
132 var d = ExpressionUtility.ParseNumber(sb.ToString());
133 Console.WriteLine(double.IsNaN(d) ? "NaN" : BitConverter.DoubleToInt64Bits(d).ToString("x"));
134 }
135 return 0;
136 }
137 if (args[0] == "--g15")
138 {
139 // Debug aid: one hexadecimal IEEE 754 bit pattern per input line, the G15 form per output line.
140 foreach (var line in File.ReadLines(args[1]))
141 {
142 var d = BitConverter.Int64BitsToDouble(Convert.ToInt64(line.Trim(), 16));
143 Console.WriteLine(d.ToString("G15", System.Globalization.CultureInfo.InvariantCulture));
144 }
145 return 0;
146 }
147 JObject root; using (var jr0 = new JsonTextReader(new StringReader(File.ReadAllText(args[0]))) { DateParseHandling = DateParseHandling.None }) root = JObject.Load(jr0);
148 var defaultCtx = (JObject)root["contexts"];
149 var funcs = new IFunctionInfo[]
150 {
151 new FunctionInfo<Success>("success", 0, 0),
152 new FunctionInfo<Always>("always", 0, 0),
153 new FunctionInfo<HashFiles>("hashFiles", 1, 255),
154 };
155 var output = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false));
156 var defaultData = Load(defaultCtx);
157 foreach (var c in (JArray)root["cases"])
158 {
159 var e = (string)c["e"];
160 var custom = c["ctx"] as JObject;
161 var data = custom != null ? Load(custom) : defaultData;
162 var names = (custom ?? defaultCtx).Properties().Select(p => p.Name);
163 var res = new JObject();
164 IExpressionNode tree = null;
165 try
166 {
167 tree = new ExpressionParser().CreateTree(e, null, names.Select(n => (INamedValueInfo)new NamedValueInfo<CtxNode>(n)).ToList(), funcs);
168 }
169 catch (Exception ex)
170 {
171 res["err"] = new JObject { ["t"] = "parse", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
172 }
173 if (res["err"] == null)
174 {
175 if (tree == null)
176 {
177 res["empty"] = true;
178 }
179 else
180 {
181 try
182 {
183 var r = tree.Evaluate(null, null, data, null);
184 res["k"] = r.Kind.ToString();
185 res["s"] = r.ConvertToString();
186 // toJson of the result
187 var tj = new ToJson();
188 tj.AddParameter(new Literal(r.Value));
189 try
190 {
191 var jr = ((IExpressionNode)tj).Evaluate(null, null, data, null);
192 res["j"] = jr.ConvertToString();
193 }
194 catch (Exception ex2)
195 {
196 res["jerr"] = ex2.Message;
197 }
198 }
199 catch (Exception ex)
200 {
201 res["err"] = new JObject { ["t"] = "eval", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
202 }
203 }
204 }
205 // ASCII-escaped, so that a lone surrogate (which UTF-8 cannot carry) survives.
206 var sw = new StringWriter();
207 using (var jw = new JsonTextWriter(sw) { StringEscapeHandling = StringEscapeHandling.EscapeNonAscii, Formatting = Formatting.None })
208 {
209 res.WriteTo(jw);
210 }
211 output.WriteLine(sw.ToString());
212 }
213 output.Flush();
214 return 0;
215 }
216}