001package org.hl7.fhir.r4.fhirpath;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032import java.util.ArrayList;
033import java.util.List;
034
035import org.hl7.fhir.r4.model.Base;
036import org.hl7.fhir.r4.model.Quantity;
037import org.hl7.fhir.r4.model.StringType;
038import org.hl7.fhir.utilities.SourceLocation;
039import org.hl7.fhir.utilities.Utilities;
040
041public class ExpressionNode {
042
043  public enum Kind {
044                Name, Function, Constant, Group, Unary
045        }
046
047  public enum Function {
048    Custom, 
049    
050    Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single,
051    First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches, Contains, Replace, Length,  
052    Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue,
053    HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo,
054    Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, Power, Truncate, Sort,
055    
056    // R3 functions
057    Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
058    
059    // Local extensions to FHIRPath
060    HtmlChecks1, HtmlChecks2, Comparable, hasTemplateIdOf;
061
062    public static Function fromCode(String name) {
063      if (name.equals("empty")) return Function.Empty;
064      if (name.equals("not")) return Function.Not;
065      if (name.equals("exists")) return Function.Exists;
066      if (name.equals("subsetOf")) return Function.SubsetOf;
067      if (name.equals("supersetOf")) return Function.SupersetOf;
068      if (name.equals("isDistinct")) return Function.IsDistinct;
069      if (name.equals("distinct")) return Function.Distinct;
070      if (name.equals("count")) return Function.Count;
071      if (name.equals("where")) return Function.Where;
072      if (name.equals("select")) return Function.Select;
073      if (name.equals("all")) return Function.All;
074      if (name.equals("repeat")) return Function.Repeat;
075      if (name.equals("aggregate")) return Function.Aggregate;      
076      if (name.equals("item")) return Function.Item;
077      if (name.equals("as")) return Function.As;
078      if (name.equals("is")) return Function.Is;
079      if (name.equals("single")) return Function.Single;
080      if (name.equals("first")) return Function.First;
081      if (name.equals("last")) return Function.Last;
082      if (name.equals("tail")) return Function.Tail;
083      if (name.equals("skip")) return Function.Skip;
084      if (name.equals("take")) return Function.Take;
085      if (name.equals("union")) return Function.Union;
086      if (name.equals("combine")) return Function.Combine;
087      if (name.equals("intersect")) return Function.Intersect;
088      if (name.equals("exclude")) return Function.Exclude;
089      if (name.equals("iif")) return Function.Iif;
090      if (name.equals("lower")) return Function.Lower;
091      if (name.equals("upper")) return Function.Upper;
092      if (name.equals("toChars")) return Function.ToChars;
093      if (name.equals("indexOf")) return Function.IndexOf;
094      if (name.equals("substring")) return Function.Substring;
095      if (name.equals("startsWith")) return Function.StartsWith;
096      if (name.equals("endsWith")) return Function.EndsWith;
097      if (name.equals("matches")) return Function.Matches;
098      if (name.equals("matchesFull")) return Function.MatchesFull;
099      if (name.equals("replaceMatches")) return Function.ReplaceMatches;
100      if (name.equals("contains")) return Function.Contains;
101      if (name.equals("replace")) return Function.Replace;
102      if (name.equals("length")) return Function.Length;
103      if (name.equals("children")) return Function.Children;
104      if (name.equals("descendants")) return Function.Descendants;
105      if (name.equals("memberOf")) return Function.MemberOf;
106      if (name.equals("trace")) return Function.Trace;
107      if (name.equals("defineVariable")) return Function.DefineVariable;
108      if (name.equals("check")) return Function.Check;
109      if (name.equals("today")) return Function.Today;
110      if (name.equals("now")) return Function.Now;
111      if (name.equals("resolve")) return Function.Resolve;
112      if (name.equals("extension")) return Function.Extension;
113      if (name.equals("allFalse")) return Function.AllFalse;
114      if (name.equals("anyFalse")) return Function.AnyFalse;
115      if (name.equals("allTrue")) return Function.AllTrue;
116      if (name.equals("anyTrue")) return Function.AnyTrue;
117      if (name.equals("hasValue")) return Function.HasValue;
118      if (name.equals("htmlChecks")) return Function.HtmlChecks1;
119      if (name.equals("htmlchecks")) return Function.HtmlChecks1; // support change of care from R3
120      if (name.equals("htmlChecks2")) return Function.HtmlChecks2;
121      if (name.equals("comparable")) return Function.Comparable;
122      if (name.equals("encode")) return Function.Encode;
123      if (name.equals("decode")) return Function.Decode;      
124      if (name.equals("escape")) return Function.Escape;
125      if (name.equals("unescape")) return Function.Unescape;
126      if (name.equals("trim")) return Function.Trim;      
127      if (name.equals("split")) return Function.Split;
128      if (name.equals("join")) return Function.Join;            
129      if (name.equals("ofType")) return Function.OfType;      
130      if (name.equals("type")) return Function.Type;      
131      if (name.equals("toInteger")) return Function.ToInteger;
132      if (name.equals("toDecimal")) return Function.ToDecimal;
133      if (name.equals("toString")) return Function.ToString;
134      if (name.equals("toQuantity")) return Function.ToQuantity;
135      if (name.equals("toBoolean")) return Function.ToBoolean;
136      if (name.equals("toDateTime")) return Function.ToDateTime;
137      if (name.equals("toTime")) return Function.ToTime;
138      if (name.equals("convertsToInteger")) return Function.ConvertsToInteger;
139      if (name.equals("convertsToDecimal")) return Function.ConvertsToDecimal;
140      if (name.equals("convertsToString")) return Function.ConvertsToString;
141      if (name.equals("convertsToQuantity")) return Function.ConvertsToQuantity;
142      if (name.equals("convertsToBoolean")) return Function.ConvertsToBoolean;
143      if (name.equals("convertsToDateTime")) return Function.ConvertsToDateTime;
144      if (name.equals("convertsToDate")) return Function.ConvertsToDate;
145      if (name.equals("convertsToTime")) return Function.ConvertsToTime;
146      if (name.equals("conformsTo")) return Function.ConformsTo;
147      if (name.equals("round")) return Function.Round;
148      if (name.equals("sqrt")) return Function.Sqrt;
149      if (name.equals("abs")) return Function.Abs;
150      if (name.equals("ceiling")) return Function.Ceiling;
151      if (name.equals("exp")) return Function.Exp;
152      if (name.equals("floor")) return Function.Floor;
153      if (name.equals("ln")) return Function.Ln;
154      if (name.equals("log")) return Function.Log;
155      if (name.equals("power")) return Function.Power;
156      if (name.equals("truncate")) return Function.Truncate; 
157      if (name.equals("sort")) return Function.Sort;  
158      if (name.equals("lowBoundary")) return Function.LowBoundary;  
159      if (name.equals("highBoundary")) return Function.HighBoundary;  
160      if (name.equals("precision")) return Function.Precision;  
161      if (name.equals("hasTemplateIdOf")) return Function.hasTemplateIdOf;  
162
163      return null;
164    }
165    
166    public String toCode() {
167      switch (this) {
168      case Empty : return "empty";
169      case Not : return "not";
170      case Exists : return "exists";
171      case SubsetOf : return "subsetOf";
172      case SupersetOf : return "supersetOf";
173      case IsDistinct : return "isDistinct";
174      case Distinct : return "distinct";
175      case Count : return "count";
176      case Where : return "where";
177      case Select : return "select";
178      case All : return "all";
179      case Repeat : return "repeat";
180      case Aggregate : return "aggregate";
181      case Item : return "item";
182      case As : return "as";
183      case Is : return "is";
184      case Single : return "single";
185      case First : return "first";
186      case Last : return "last";
187      case Tail : return "tail";
188      case Skip : return "skip";
189      case Take : return "take";
190      case Union : return "union";
191      case Combine : return "combine";
192      case Intersect : return "intersect";
193      case Exclude : return "exclude";
194      case Iif : return "iif";
195      case ToChars : return "toChars";
196      case Lower : return "lower";
197      case Upper : return "upper";
198      case IndexOf : return "indexOf";
199      case Substring : return "substring";
200      case StartsWith : return "startsWith";
201      case EndsWith : return "endsWith";
202      case Matches : return "matches";
203      case MatchesFull : return "matchesFull";
204      case ReplaceMatches : return "replaceMatches";
205      case Contains : return "contains";
206      case Replace : return "replace";
207      case Length : return "length";
208      case Children : return "children";
209      case Descendants : return "descendants";
210      case MemberOf : return "memberOf";
211      case Trace : return "trace";
212      case DefineVariable : return "defineVariable";
213      case Check : return "check";
214      case Today : return "today";
215      case Now : return "now";
216      case Resolve : return "resolve";
217      case Extension : return "extension";
218      case AllFalse : return "allFalse";
219      case AnyFalse : return "anyFalse";
220      case AllTrue : return "allTrue";
221      case AnyTrue : return "anyTrue";
222      case HasValue : return "hasValue";
223      case Encode : return "encode";
224      case Decode : return "decode";
225      case Escape : return "escape";
226      case Unescape : return "unescape";
227      case Trim : return "trim";
228      case Split : return "split";
229      case Join : return "join";
230      case HtmlChecks1 : return "htmlChecks";
231      case HtmlChecks2 : return "htmlChecks2";
232      case Comparable : return "comparable";
233      case OfType : return "ofType";
234      case Type : return "type";
235      case ToInteger : return "toInteger";
236      case ToDecimal : return "toDecimal";
237      case ToString : return "toString";
238      case ToBoolean : return "toBoolean";
239      case ToQuantity : return "toQuantity";
240      case ToDateTime : return "toDateTime";
241      case ToTime : return "toTime";
242      case ConvertsToInteger : return "convertsToInteger";
243      case ConvertsToDecimal : return "convertsToDecimal";
244      case ConvertsToString : return "convertsToString";
245      case ConvertsToBoolean : return "convertsToBoolean";
246      case ConvertsToQuantity : return "convertsToQuantity";
247      case ConvertsToDateTime : return "convertsToDateTime";
248      case ConvertsToDate : return "convertsToDate";
249      case ConvertsToTime : return "isTime";
250      case ConformsTo : return "conformsTo";
251      case Round : return "round";
252      case Sqrt : return "sqrt";
253      case Abs : return "abs";
254      case Ceiling : return "ceiling";
255      case Exp : return "exp";
256      case Floor : return "floor";
257      case Ln : return "ln";
258      case Log : return "log";
259      case Power : return "power";
260      case Truncate: return "truncate";
261      case Sort: return "sort";
262      case LowBoundary: return "lowBoundary";
263      case HighBoundary: return "highBoundary";
264      case Precision: return "precision";
265      case hasTemplateIdOf: return "hasTemplateIdOf";
266      default: return "?custom?";
267      }
268    }
269  }
270
271        public enum Operation {
272                Equals, Equivalent, NotEquals, NotEquivalent, LessThan, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, And, Xor, Implies, 
273                Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains, MemberOf;
274
275                public static Operation fromCode(String name) {
276                        if (Utilities.noString(name))
277                                return null;
278                        if (name.equals("="))
279                                return Operation.Equals;
280                        if (name.equals("~"))
281                                return Operation.Equivalent;
282                        if (name.equals("!="))
283                                return Operation.NotEquals;
284                        if (name.equals("!~"))
285                                return Operation.NotEquivalent;
286                        if (name.equals(">"))
287                                return Operation.Greater;
288                        if (name.equals("<"))
289                                return Operation.LessThan;
290                        if (name.equals(">="))
291                                return Operation.GreaterOrEqual;
292                        if (name.equals("<="))
293                                return Operation.LessOrEqual;
294                        if (name.equals("|"))
295                                return Operation.Union;
296                        if (name.equals("or"))
297                                return Operation.Or;
298                        if (name.equals("and"))
299                                return Operation.And;
300                        if (name.equals("xor"))
301                                return Operation.Xor;
302      if (name.equals("is"))
303        return Operation.Is;
304      if (name.equals("as"))
305        return Operation.As;
306      if (name.equals("*"))
307        return Operation.Times;
308      if (name.equals("/"))
309        return Operation.DivideBy;
310                        if (name.equals("+"))
311                                return Operation.Plus;
312      if (name.equals("-"))
313        return Operation.Minus;
314      if (name.equals("&"))
315        return Operation.Concatenate;
316                        if (name.equals("implies"))
317                                return Operation.Implies;
318      if (name.equals("div"))
319        return Operation.Div;
320      if (name.equals("mod"))
321        return Operation.Mod;
322      if (name.equals("in"))
323        return Operation.In;
324      if (name.equals("contains"))
325        return Operation.Contains;
326      if (name.equals("memberOf"))
327        return Operation.MemberOf;      
328                        return null;
329
330                }
331                public String toCode() {
332            switch (this) {
333                        case Equals : return "=";
334                        case Equivalent : return "~";
335                        case NotEquals : return "!=";
336                        case NotEquivalent : return "!~";
337                        case Greater : return ">";
338                        case LessThan : return "<";
339                        case GreaterOrEqual : return ">=";
340                        case LessOrEqual : return "<=";
341                        case Union : return "|";
342                        case Or : return "or";
343                        case And : return "and";
344                        case Xor : return "xor";
345      case Times : return "*";
346      case DivideBy : return "/";
347      case Plus : return "+";
348      case Minus : return "-";
349      case Concatenate : return "&";
350                        case Implies : return "implies";
351      case Is : return "is";
352      case As : return "as";
353      case Div : return "div";
354      case Mod : return "mod";
355      case In : return "in";
356      case Contains : return "contains";
357      case MemberOf : return "memberOf";
358                        default: return "?custom?";
359                        }
360                }
361        }
362
363  public enum CollectionStatus {
364    SINGLETON, ORDERED, UNORDERED;
365
366    boolean isList() {
367      return this == ORDERED || this == UNORDERED;
368    }
369  }
370  
371  //the expression will have one of either name or constant
372        private String uniqueId;
373        private Kind kind;
374        private String name;
375        private Base constant;
376        private Function function;
377        private List<ExpressionNode> parameters; // will be created if there is a function
378        private ExpressionNode inner;
379        private ExpressionNode group;
380        private Operation operation;
381        private boolean proximal; // a proximal operation is the first in the sequence of operations. This is significant when evaluating the outcomes
382        private ExpressionNode opNext;
383        private SourceLocation start;
384        private SourceLocation end;
385        private SourceLocation opStart;
386        private SourceLocation opEnd;
387        private TypeDetails types;
388        private TypeDetails opTypes;
389
390
391        public ExpressionNode(int uniqueId) {
392                super();
393                this.uniqueId = Integer.toString(uniqueId);
394        }
395
396        public String toString() {
397                StringBuilder b = new StringBuilder();
398                switch (kind) {
399                case Name:
400                        b.append(name);
401                        break;
402                case Function:
403                        if (function == Function.Item) 
404                                b.append("[");
405                        else {
406                                b.append(name);
407                                b.append("(");
408                        }
409                        boolean first = true;
410                        for (ExpressionNode n : parameters) {
411                                if (first)
412                                        first = false;
413                                else
414                                        b.append(", ");
415                                b.append(n.toString());
416                        }
417                        if (function == Function.Item) {
418        b.append("]");
419      } else {
420                                b.append(")");
421                        }
422                        break;
423                case Constant:
424      if (constant == null) {
425        b.append("{}");
426      } else if (constant instanceof StringType) {
427        b.append("'" + Utilities.escapeJson(constant.primitiveValue()) + "'");
428      } else if (constant instanceof Quantity) {
429        Quantity q = (Quantity) constant;
430        b.append(Utilities.escapeJson(q.getValue().toPlainString()));
431        if (q.hasUnit() || q.hasCode()) {
432          b.append(" '");
433          if (q.hasUnit()) {
434            b.append(Utilities.escapeJson(q.getUnit()));
435          } else {
436            b.append(Utilities.escapeJson(q.getCode()));
437          }
438          b.append("'");
439        }
440      } else if (constant.primitiveValue() != null) {
441        b.append(Utilities.escapeJson(constant.primitiveValue()));
442      } else {
443        b.append(Utilities.escapeJson(constant.toString()));
444      }
445                        break;
446                case Group:
447                        b.append("(");
448                        b.append(group.toString());
449                        b.append(")");
450                }
451                if (inner != null) {
452                        if (!((ExpressionNode.Kind.Function == inner.getKind()) && (ExpressionNode.Function.Item == inner.getFunction()))) {
453                                b.append(".");
454                        }
455                        b.append(inner.toString());
456                }
457                if (operation != null) {
458                        b.append(" ");
459                        b.append(operation.toCode());
460                        b.append(" ");
461                        b.append(opNext.toString());
462                }
463                        
464                return b.toString();
465        }
466        
467        public String getName() {
468                return name;
469        }
470        public void setName(String name) {
471                this.name = name;
472        }
473        public Base getConstant() {
474                return constant;
475        }
476        public void setConstant(Base constant) {
477                this.constant = constant;
478        }
479        
480        public Function getFunction() {
481                return function;
482        }
483        public void setFunction(Function function) {
484                this.function = function;
485                if (parameters == null)
486                        parameters = new ArrayList<ExpressionNode>();
487        }
488
489        public boolean isProximal() {
490                return proximal;
491        }
492        public void setProximal(boolean proximal) {
493                this.proximal = proximal;
494        }
495        public Operation getOperation() {
496                return operation;
497        }
498        public void setOperation(Operation operation) {
499                this.operation = operation;
500        }
501        public ExpressionNode getInner() {
502                return inner;
503        }
504        public void setInner(ExpressionNode value) {
505                this.inner = value;
506        }
507        public ExpressionNode getOpNext() {
508                return opNext;
509        }
510        public void setOpNext(ExpressionNode value) {
511                this.opNext = value;
512        }
513        public List<ExpressionNode> getParameters() {
514                return parameters;
515        }
516        public boolean checkName() {
517                if (!name.startsWith("$"))
518                        return true;
519                else
520                        return Utilities.existsInList(name, "$this", "$total", "$index");  
521        }
522
523        public Kind getKind() {
524                return kind;
525        }
526
527        public void setKind(Kind kind) {
528                this.kind = kind;
529        }
530
531        public ExpressionNode getGroup() {
532                return group;
533        }
534
535        public void setGroup(ExpressionNode group) {
536                this.group = group;
537        }
538
539        public SourceLocation getStart() {
540                return start;
541        }
542
543        public void setStart(SourceLocation start) {
544                this.start = start;
545        }
546
547        public SourceLocation getEnd() {
548                return end;
549        }
550
551        public void setEnd(SourceLocation end) {
552                this.end = end;
553        }
554
555        public SourceLocation getOpStart() {
556                return opStart;
557        }
558
559        public void setOpStart(SourceLocation opStart) {
560                this.opStart = opStart;
561        }
562
563        public SourceLocation getOpEnd() {
564                return opEnd;
565        }
566
567        public void setOpEnd(SourceLocation opEnd) {
568                this.opEnd = opEnd;
569        }
570
571        public String getUniqueId() {
572                return uniqueId;
573        }
574
575
576        public int parameterCount() {
577                if (parameters == null)
578                        return 0;
579                else
580                        return parameters.size();
581        }
582
583        public String Canonical() {
584                StringBuilder b = new StringBuilder();
585                write(b);
586                return b.toString();
587        }
588
589        public String summary() {
590                switch (kind) {
591                case Name: return uniqueId+": "+name;
592                case Function: return uniqueId+": "+function.toString()+"()";
593                case Constant: return uniqueId+": "+constant;
594                case Group: return uniqueId+": (Group)";
595                }
596                return "?exp-kind?";
597        }
598
599        private void write(StringBuilder b) {
600
601                switch (kind) {
602                case Name:
603                        b.append(name);
604                        break;
605                case Constant:
606                        b.append(constant);
607                        break;
608                case Function:
609                        b.append(function.toCode());
610                        b.append('(');
611                        boolean f = true;
612                        for (ExpressionNode n : parameters) {
613                                if (f)
614                                        f = false;
615                                else
616                                        b.append(", ");
617                                n.write(b);
618                        }
619                        b.append(')');
620
621                        break;
622                case Group:
623                        b.append('(');
624                        group.write(b);
625                        b.append(')');
626                }
627
628                if (inner != null) {
629                        b.append('.');
630                        inner.write(b);
631                }
632                if (operation != null) {
633                        b.append(' ');
634                        b.append(operation.toCode());
635                        b.append(' ');
636                        opNext.write(b);
637                }
638        }
639
640        public String check() {
641
642          if (kind == null) {
643            return "Error in expression - node has no kind";
644          }
645                switch (kind) {
646                case Name:
647                        if (Utilities.noString(name)) 
648                                return "No Name provided @ "+location();
649                        break;
650
651                case Function:          
652                        if (function == null)
653                                return "No Function id provided @ "+location();
654                        for (ExpressionNode n : parameters) { 
655                                String msg = n.check();
656                                if (msg != null)
657                                        return msg;
658                        }
659
660                        break;
661
662                case Unary:
663                  break;
664                case Constant:
665                        if (constant == null) 
666                                return "No Constant provided @ "+location();
667                        break;
668
669                case Group:
670                        if (group == null)
671                                return "No Group provided @ "+location();
672                        else {
673                                String msg = group.check();
674                                if (msg != null)
675                                        return msg;
676                        }
677                }
678                if (inner != null) { 
679                        String msg = inner.check();
680                        if (msg != null)
681                                return msg;
682                }
683                if (operation == null) {
684
685                        if (opNext != null)
686                                return "Next provided when it shouldn't be @ "+location();
687                } 
688                else {
689                        if (opNext == null)
690                                return "No Next provided @ "+location();
691                        else
692                                opNext.check();
693                }
694                return null;
695
696        }
697
698        private String location() {
699                return Integer.toString(start.getLine())+", "+Integer.toString(start.getColumn());
700        }
701
702        public TypeDetails getTypes() {
703                return types;
704        }
705
706        public void setTypes(TypeDetails types) {
707                this.types = types;
708        }
709
710        public TypeDetails getOpTypes() {
711                return opTypes;
712        }
713
714        public void setOpTypes(TypeDetails opTypes) {
715                this.opTypes = opTypes;
716        }
717
718  public List<String> getDistalNames() {
719    List<String> names = new ArrayList<String>();
720    if (operation != null) {
721      names.add(null);
722    } else if (inner != null) {
723      names.addAll(inner.getDistalNames());
724    } else if (group != null) {
725      names.addAll(group.getDistalNames());
726    } else if (function != null) {
727      names.add(null);
728    } else if (constant != null) {
729      names.add(null);
730    } else {
731      names.add(name);
732    }    
733    return names;
734  }
735
736  public boolean isNullSet() {
737    return kind == Kind.Constant && constant == null;
738  }
739                
740}