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