001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jasper.compiler;
019:
020: /**
021: * This class implements a parser for EL expressions.
022: *
023: * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a
024: * ELNode.Nodes.
025: *
026: * Currently, it only handles text outside ${..} and functions in ${ ..}.
027: *
028: * @author Kin-man Chung
029: */
030:
031: public class ELParser {
032:
033: private Token curToken; // current token
034:
035: private ELNode.Nodes expr;
036:
037: private ELNode.Nodes ELexpr;
038:
039: private int index; // Current index of the expression
040:
041: private String expression; // The EL expression
042:
043: private char type;
044:
045: private boolean escapeBS; // is '\' an escape char in text outside EL?
046:
047: private static final String reservedWords[] = { "and", "div",
048: "empty", "eq", "false", "ge", "gt", "instanceof", "le",
049: "lt", "mod", "ne", "not", "null", "or", "true" };
050:
051: public ELParser(String expression) {
052: index = 0;
053: this .expression = expression;
054: expr = new ELNode.Nodes();
055: }
056:
057: /**
058: * Parse an EL expression
059: *
060: * @param expression
061: * The input expression string of the form Char* ('${' Char*
062: * '}')* Char*
063: * @return Parsed EL expression in ELNode.Nodes
064: */
065: public static ELNode.Nodes parse(String expression) {
066: ELParser parser = new ELParser(expression);
067: while (parser.hasNextChar()) {
068: String text = parser.skipUntilEL();
069: if (text.length() > 0) {
070: parser.expr.add(new ELNode.Text(text));
071: }
072: ELNode.Nodes elexpr = parser.parseEL();
073: if (!elexpr.isEmpty()) {
074: parser.expr.add(new ELNode.Root(elexpr, parser.type));
075: }
076: }
077: return parser.expr;
078: }
079:
080: /**
081: * Parse an EL expression string '${...}'
082: *
083: * @return An ELNode.Nodes representing the EL expression TODO: Currently
084: * only parsed into functions and text strings. This should be
085: * rewritten for a full parser.
086: */
087: private ELNode.Nodes parseEL() {
088:
089: StringBuffer buf = new StringBuffer();
090: ELexpr = new ELNode.Nodes();
091: while (hasNext()) {
092: curToken = nextToken();
093: if (curToken instanceof Char) {
094: if (curToken.toChar() == '}') {
095: break;
096: }
097: buf.append(curToken.toChar());
098: } else {
099: // Output whatever is in buffer
100: if (buf.length() > 0) {
101: ELexpr.add(new ELNode.ELText(buf.toString()));
102: }
103: if (!parseFunction()) {
104: ELexpr.add(new ELNode.ELText(curToken.toString()));
105: }
106: }
107: }
108: if (buf.length() > 0) {
109: ELexpr.add(new ELNode.ELText(buf.toString()));
110: }
111:
112: return ELexpr;
113: }
114:
115: /**
116: * Parse for a function FunctionInvokation ::= (identifier ':')? identifier
117: * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse
118: * arguments
119: */
120: private boolean parseFunction() {
121: if (!(curToken instanceof Id)
122: || isELReserved(curToken.toString())) {
123: return false;
124: }
125: String s1 = null; // Function prefix
126: String s2 = curToken.toString(); // Function name
127: int mark = getIndex();
128: if (hasNext()) {
129: Token t = nextToken();
130: if (t.toChar() == ':') {
131: if (hasNext()) {
132: Token t2 = nextToken();
133: if (t2 instanceof Id) {
134: s1 = s2;
135: s2 = t2.toString();
136: if (hasNext()) {
137: t = nextToken();
138: }
139: }
140: }
141: }
142: if (t.toChar() == '(') {
143: ELexpr.add(new ELNode.Function(s1, s2));
144: return true;
145: }
146: }
147: setIndex(mark);
148: return false;
149: }
150:
151: /**
152: * Test if an id is a reserved word in EL
153: */
154: private boolean isELReserved(String id) {
155: int i = 0;
156: int j = reservedWords.length;
157: while (i < j) {
158: int k = (i + j) / 2;
159: int result = reservedWords[k].compareTo(id);
160: if (result == 0) {
161: return true;
162: }
163: if (result < 0) {
164: i = k + 1;
165: } else {
166: j = k;
167: }
168: }
169: return false;
170: }
171:
172: /**
173: * Skip until an EL expression ('${' || '#{') is reached, allowing escape
174: * sequences '\\' and '\$' and '\#'.
175: *
176: * @return The text string up to the EL expression
177: */
178: private String skipUntilEL() {
179: char prev = 0;
180: StringBuffer buf = new StringBuffer();
181: while (hasNextChar()) {
182: char ch = nextChar();
183: if (prev == '\\') {
184: prev = 0;
185: if (ch == '\\') {
186: buf.append('\\');
187: if (!escapeBS)
188: prev = '\\';
189: } else if (ch == '$' || ch == '#') {
190: buf.append(ch);
191: }
192: // else error!
193: } else if (prev == '$' || prev == '#') {
194: if (ch == '{') {
195: this .type = prev;
196: prev = 0;
197: break;
198: }
199: buf.append(prev);
200: prev = 0;
201: }
202: if (ch == '\\' || ch == '$' || ch == '#') {
203: prev = ch;
204: } else {
205: buf.append(ch);
206: }
207: }
208: if (prev != 0) {
209: buf.append(prev);
210: }
211: return buf.toString();
212: }
213:
214: /*
215: * @return true if there is something left in EL expression buffer other
216: * than white spaces.
217: */
218: private boolean hasNext() {
219: skipSpaces();
220: return hasNextChar();
221: }
222:
223: /*
224: * @return The next token in the EL expression buffer.
225: */
226: private Token nextToken() {
227: skipSpaces();
228: if (hasNextChar()) {
229: char ch = nextChar();
230: if (Character.isJavaIdentifierStart(ch)) {
231: StringBuffer buf = new StringBuffer();
232: buf.append(ch);
233: while ((ch = peekChar()) != -1
234: && Character.isJavaIdentifierPart(ch)) {
235: buf.append(ch);
236: nextChar();
237: }
238: return new Id(buf.toString());
239: }
240:
241: if (ch == '\'' || ch == '"') {
242: return parseQuotedChars(ch);
243: } else {
244: // For now...
245: return new Char(ch);
246: }
247: }
248: return null;
249: }
250:
251: /*
252: * Parse a string in single or double quotes, allowing for escape sequences
253: * '\\', and ('\"', or "\'")
254: */
255: private Token parseQuotedChars(char quote) {
256: StringBuffer buf = new StringBuffer();
257: buf.append(quote);
258: while (hasNextChar()) {
259: char ch = nextChar();
260: if (ch == '\\') {
261: ch = nextChar();
262: if (ch == '\\' || ch == quote) {
263: buf.append(ch);
264: }
265: // else error!
266: } else if (ch == quote) {
267: buf.append(ch);
268: break;
269: } else {
270: buf.append(ch);
271: }
272: }
273: return new QuotedString(buf.toString());
274: }
275:
276: /*
277: * A collection of low level parse methods dealing with character in the EL
278: * expression buffer.
279: */
280:
281: private void skipSpaces() {
282: while (hasNextChar()) {
283: if (expression.charAt(index) > ' ')
284: break;
285: index++;
286: }
287: }
288:
289: private boolean hasNextChar() {
290: return index < expression.length();
291: }
292:
293: private char nextChar() {
294: if (index >= expression.length()) {
295: return (char) -1;
296: }
297: return expression.charAt(index++);
298: }
299:
300: private char peekChar() {
301: if (index >= expression.length()) {
302: return (char) -1;
303: }
304: return expression.charAt(index);
305: }
306:
307: private int getIndex() {
308: return index;
309: }
310:
311: private void setIndex(int i) {
312: index = i;
313: }
314:
315: /*
316: * Represents a token in EL expression string
317: */
318: private static class Token {
319:
320: char toChar() {
321: return 0;
322: }
323:
324: public String toString() {
325: return "";
326: }
327: }
328:
329: /*
330: * Represents an ID token in EL
331: */
332: private static class Id extends Token {
333: String id;
334:
335: Id(String id) {
336: this .id = id;
337: }
338:
339: public String toString() {
340: return id;
341: }
342: }
343:
344: /*
345: * Represents a character token in EL
346: */
347: private static class Char extends Token {
348:
349: private char ch;
350:
351: Char(char ch) {
352: this .ch = ch;
353: }
354:
355: char toChar() {
356: return ch;
357: }
358:
359: public String toString() {
360: return (new Character(ch)).toString();
361: }
362: }
363:
364: /*
365: * Represents a quoted (single or double) string token in EL
366: */
367: private static class QuotedString extends Token {
368:
369: private String value;
370:
371: QuotedString(String v) {
372: this .value = v;
373: }
374:
375: public String toString() {
376: return value;
377: }
378: }
379:
380: public char getType() {
381: return type;
382: }
383: }
|