1 /* 2 * Copyright (C) 2018 Google, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.escapevelocity; 17 18 import com.google.common.collect.ImmutableList; 19 20 /** 21 * A node in the parse tree. 22 * 23 * @author emcmanus@google.com (Éamonn McManus) 24 */ 25 abstract class Node { 26 final String resourceName; 27 final int lineNumber; 28 Node(String resourceName, int lineNumber)29 Node(String resourceName, int lineNumber) { 30 this.resourceName = resourceName; 31 this.lineNumber = lineNumber; 32 } 33 34 /** 35 * Returns the result of evaluating this node in the given context. This result may be used as 36 * part of a further operation, for example evaluating {@code 2 + 3} to 5 in order to set 37 * {@code $x} to 5 in {@code #set ($x = 2 + 3)}. Or it may be used directly as part of the 38 * template output, for example evaluating replacing {@code name} by {@code Fred} in 39 * {@code My name is $name.}. 40 */ evaluate(EvaluationContext context)41 abstract Object evaluate(EvaluationContext context); 42 where()43 private String where() { 44 String where = "In expression on line " + lineNumber; 45 if (resourceName != null) { 46 where += " of " + resourceName; 47 } 48 return where; 49 } 50 evaluationException(String message)51 EvaluationException evaluationException(String message) { 52 return new EvaluationException(where() + ": " + message); 53 } 54 evaluationException(Throwable cause)55 EvaluationException evaluationException(Throwable cause) { 56 return new EvaluationException(where() + ": " + cause, cause); 57 } 58 59 /** 60 * Returns an empty node in the parse tree. This is used for example to represent the trivial 61 * "else" part of an {@code #if} that does not have an explicit {@code #else}. 62 */ emptyNode(String resourceName, int lineNumber)63 static Node emptyNode(String resourceName, int lineNumber) { 64 return new Cons(resourceName, lineNumber, ImmutableList.<Node>of()); 65 } 66 67 /** 68 * Create a new parse tree node that is the concatenation of the given ones. Evaluating the 69 * new node produces the same string as evaluating each of the given nodes and concatenating the 70 * result. 71 */ cons(String resourceName, int lineNumber, ImmutableList<Node> nodes)72 static Node cons(String resourceName, int lineNumber, ImmutableList<Node> nodes) { 73 return new Cons(resourceName, lineNumber, nodes); 74 } 75 76 private static final class Cons extends Node { 77 private final ImmutableList<Node> nodes; 78 Cons(String resourceName, int lineNumber, ImmutableList<Node> nodes)79 Cons(String resourceName, int lineNumber, ImmutableList<Node> nodes) { 80 super(resourceName, lineNumber); 81 this.nodes = nodes; 82 } 83 evaluate(EvaluationContext context)84 @Override Object evaluate(EvaluationContext context) { 85 StringBuilder sb = new StringBuilder(); 86 for (Node node : nodes) { 87 sb.append(node.evaluate(context)); 88 } 89 return sb.toString(); 90 } 91 } 92 } 93