1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.XPath
 10 {
 11     public enum FunctionKind
 12     {
 13         booleannumberstringlastpositioncountmax
 14     }
 15 
 16     public string FunctionName(FunctionKind kind)
 17     {
 18         switch (kind)
 19         {
 20             case FunctionKind.boolean: return "boolean";
 21             case FunctionKind.number: return "number";
 22             case FunctionKind.string: return "string";
 23             case FunctionKind.last: return "last";
 24             case FunctionKind.position: return "position";
 25             case FunctionKind.count: return "count";
 26         }
 27         return "<unknown function kind>";
 28     }
 29 
 30     public abstract class Function
 31     {
 32         public Function(FunctionKind kind_) : kind(kind_)name(FunctionName(kind))minArity(1)maxArity(1)
 33         {
 34         }
 35         public Function(FunctionKind kind_int minArity_int maxArity_) : kind(kind_)name(FunctionName(kind))minArity(minArity_)maxArity(maxArity_)
 36         {
 37         }
 38         public virtual default ~Function();
 39         public inline FunctionKind Kind() const
 40         {
 41             return kind;
 42         }
 43         public inline const string& Name() const
 44         {
 45             return name;
 46         }
 47         public inline int MinArity() const
 48         {
 49             return minArity;
 50         }
 51         public inline int MaxArity() const
 52         {
 53             return maxArity;
 54         }
 55         public string ArityStr() const
 56         {
 57             if (minArity == maxArity)
 58             {
 59                 return ToString(minArity);
 60             }
 61             else if (minArity == 0)
 62             {
 63                 return "at most " + ToString(maxArity);
 64             }
 65             else
 66             {
 67                 return "at least " + ToString(minArity) + " and at most " + ToString(maxArity);
 68             }
 69         }
 70         [nodiscard]
 71         public Result<UniquePtr<Object>> Evaluate(Context& contextconst List<Object*>& arguments)
 72         {
 73             int n = cast<int>(arguments.Count());
 74             if (n < minArity || n > maxArity)
 75             {
 76                 int errorId = AllocateError("wrong number of arguments for \'" + name + "()\' function: " + ArityStr() + " arguments expected, " + ToString(n) + 
 77                     " arguments provided");
 78                 return Result<UniquePtr<Object>>(ErrorId(errorId));
 79             }
 80             return DoEvaluate(contextarguments);
 81         }
 82         protected abstract Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments);
 83         private FunctionKind kind;
 84         private string name;
 85         private int minArity;
 86         private int maxArity;
 87     }
 88 
 89     public class BooleanFunction : Function
 90     {
 91         public BooleanFunction() : base(FunctionKind.boolean)
 92         {
 93         }
 94         [nodiscard]
 95         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
 96         {
 97             Object* arg = arguments.Front();
 98             switch (arg->Kind())
 99             {
100                 case ObjectKind.nodeSet:
101                 {
102                     NodeSet* nodeSet = cast<NodeSet*>(arg);
103                     return EvaluateToBoolean(nodeSet);
104                 }
105                 case ObjectKind.boolean:
106                 {
107                     Boolean* boolean = cast<Boolean*>(arg);
108                     return EvaluateToBoolean(boolean);
109                 }
110                 case ObjectKind.number:
111                 {
112                     Number* number = cast<Number*>(arg);
113                     return EvaluateToBoolean(number);
114                 }
115                 case ObjectKind.string:
116                 {
117                     Str* string = cast<Str*>(arg);
118                     return EvaluateToBoolean(string);
119                 }
120             }
121             return Result<UniquePtr<Object>>(UniquePtr<Object>(new Boolean(false)));
122         }
123     }
124 
125     public class NumberFunction : Function
126     {
127         public NumberFunction() : base(FunctionKind.number01)
128         {
129         }
130         [nodiscard]
131         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
132         {
133             Object* arg = null;
134             NodeSet nodeSet;
135             if (arguments.IsEmpty())
136             {
137                 nodeSet.Add(context.Node());
138                 arg = &nodeSet;
139             }
140             else
141             {
142                 arg = arguments.Front();
143             }
144             switch (arg->Kind())
145             {
146                 case ObjectKind.nodeSet:
147                 {
148                     NodeSet* nodeSet = cast<NodeSet*>(arg);
149                     return EvaluateToNumber(nodeSet);
150                 }
151                 case ObjectKind.boolean:
152                 {
153                     Boolean* boolean = cast<Boolean*>(arg);
154                     return EvaluateToNumber(boolean);
155                 }
156                 case ObjectKind.number:
157                 {
158                     Number* number = cast<Number*>(arg);
159                     return EvaluateToNumber(number);
160                 }
161                 case ObjectKind.string:
162                 {
163                     Str* string = cast<Str*>(arg);
164                     return EvaluateToNumber(string);
165                 }
166             }
167             int errorId = AllocateError("invalid argument to \'" + Name() + "()\' function");
168             return Result<UniquePtr<Object>>(ErrorId(errorId));
169         }
170     }
171 
172     public class StringFunction : Function
173     {
174         public StringFunction() : base(FunctionKind.string01)
175         {
176         }
177         [nodiscard]
178         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
179         {
180             Object* arg = null;
181             NodeSet nodeSet;
182             if (arguments.IsEmpty())
183             {
184                 nodeSet.Add(context.Node());
185                 arg = &nodeSet;
186             }
187             else
188             {
189                 arg = arguments.Front();
190             }
191             switch (arg->Kind())
192             {
193                 case ObjectKind.nodeSet:
194                 {
195                     NodeSet* nodeSet = cast<NodeSet*>(arg);
196                     return EvaluateToString(nodeSet);
197                 }
198                 case ObjectKind.boolean:
199                 {
200                     Boolean* boolean = cast<Boolean*>(arg);
201                     return EvaluateToString(boolean);
202                 }
203                 case ObjectKind.number:
204                 {
205                     Number* number = cast<Number*>(arg);
206                     return EvaluateToString(number);
207                 }
208                 case ObjectKind.string:
209                 {
210                     Str* string = cast<Str*>(arg);
211                     return EvaluateToString(string);
212                 }
213             }
214             int errorId = AllocateError("invalid argument to \'" + Name() + "()\' function");
215             return Result<UniquePtr<Object>>(ErrorId(errorId));
216         }
217     }
218 
219     public class LastFunction : Function
220     {
221         public LastFunction() : base(FunctionKind.last00)
222         {
223         }
224         [nodiscard]
225         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
226         {
227             return Result<UniquePtr<Object>>(UniquePtr<Object>(new Number(context.Size())));
228         }
229     }
230 
231     public class PositionFunction : Function
232     {
233         public PositionFunction() : base(FunctionKind.position00)
234         {
235         }
236         [nodiscard]
237         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
238         {
239             return Result<UniquePtr<Object>>(UniquePtr<Object>(new Number(context.Pos())));
240         }
241     }
242 
243     public class CountFunction : Function
244     {
245         public CountFunction() : base(FunctionKind.count)
246         {
247         }
248         [nodiscard]
249         public override Result<UniquePtr<Object>> DoEvaluate(Context& contextconst List<Object*>& arguments)
250         {
251             Result<NodeSet*> nodeSetResult = NodeSetCast(arguments.Front()this);
252             if (nodeSetResult.Error())
253             {
254                 return Result<UniquePtr<Object>>(ErrorId(nodeSetResult.GetErrorId()));
255             }
256             NodeSet* nodeSet = nodeSetResult.Value();
257             return Result<UniquePtr<Object>>(UniquePtr<Object>(new Number(nodeSet->Count())));
258         }
259     }
260 
261     public class FunctionLibrary
262     {
263         static FunctionLibrary() : instance(new FunctionLibrary())
264         {
265         }
266         public static FunctionLibrary& Instance()
267         {
268             return *instance;
269         }
270         private FunctionLibrary()
271         {
272             functions.Resize(cast<int>(FunctionKind.max));
273             Install(new BooleanFunction());
274             Install(new NumberFunction());
275             Install(new StringFunction());
276             Install(new LastFunction());
277             Install(new PositionFunction());
278             Install(new CountFunction());
279         }
280         [nodiscard]
281         public Result<Function*> GetFunction(FunctionKind kind) const
282         {
283             int index = cast<int>(kind);
284             #assert(index >= 0 && index < functions.Count());
285             Function* function = functions[index].Get();
286             if (function == null)
287             {
288                 int errorId = AllocateError("System.XPath.FunctionLibrary: function \'" + FunctionName(kind) + "()\' not installed");
289                 return Result<Function*>(ErrorId(errorId));
290             }
291             return Result<Function*>(function);
292         }
293         [nodiscard]
294         public Result<Function*> GetFunction(const string& name) const
295         {
296             auto it = functionMap.Find(name);
297             if (it != functionMap.End())
298             {
299                 return Result<Function*>(it->second);
300             }
301             else
302             {
303                 int errorId = AllocateError("System.XPath.FunctionLibrary: function \'" + name + "()\' not found");
304                 return Result<Function*>(ErrorId(errorId));
305             }
306         }
307         public void Install(Function* function)
308         {
309             functions[cast<int>(function->Kind())].Reset(function);
310             functionMap[function->Name()] = function;
311         }
312         private static UniquePtr<FunctionLibrary> instance;
313         private List<UniquePtr<Function>> functions;
314         private Map<stringFunction*> functionMap;
315     }
316 
317     [nodiscard]
318     public Result<Function*> GetFunction(FunctionKind kind)
319     {
320         return FunctionLibrary.Instance().GetFunction(kind);
321     }
322 
323     [nodiscard]
324     public Result<Function*> GetFunction(const string& name)
325     {
326         return FunctionLibrary.Instance().GetFunction(name);
327     }