• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc. All Rights Reserved.
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 
17 package com.google.turbine.parse;
18 
19 import static com.google.common.collect.Iterables.getOnlyElement;
20 
21 import com.google.common.collect.ImmutableList;
22 import com.google.errorprone.annotations.CheckReturnValue;
23 import com.google.turbine.diag.TurbineError;
24 import com.google.turbine.diag.TurbineError.ErrorKind;
25 import com.google.turbine.model.Const;
26 import com.google.turbine.model.TurbineConstantTypeKind;
27 import com.google.turbine.tree.Tree;
28 import com.google.turbine.tree.Tree.AnnoExpr;
29 import com.google.turbine.tree.Tree.ClassLiteral;
30 import com.google.turbine.tree.Tree.ClassTy;
31 import com.google.turbine.tree.Tree.Expression;
32 import com.google.turbine.tree.Tree.Ident;
33 import com.google.turbine.tree.TurbineOperatorKind;
34 import java.util.Optional;
35 import org.jspecify.nullness.Nullable;
36 
37 /** A parser for compile-time constant expressions. */
38 public class ConstExpressionParser {
39 
40   Token token;
41   private int position;
42   private final Lexer lexer;
43 
ConstExpressionParser(Lexer lexer, Token token, int position)44   public ConstExpressionParser(Lexer lexer, Token token, int position) {
45     this.lexer = lexer;
46     this.token = token;
47     this.position = position;
48   }
49 
operator(Token token)50   private static @Nullable TurbineOperatorKind operator(Token token) {
51     switch (token) {
52       case ASSIGN:
53         // TODO(cushon): only allow in annotations?
54         return TurbineOperatorKind.ASSIGN;
55       case MULT:
56         return TurbineOperatorKind.MULT;
57       case DIV:
58         return TurbineOperatorKind.DIVIDE;
59       case MOD:
60         return TurbineOperatorKind.MODULO;
61       case PLUS:
62         return TurbineOperatorKind.PLUS;
63       case MINUS:
64         return TurbineOperatorKind.MINUS;
65       case LTLT:
66         return TurbineOperatorKind.SHIFT_LEFT;
67       case GTGT:
68         return TurbineOperatorKind.SHIFT_RIGHT;
69       case GTGTGT:
70         return TurbineOperatorKind.UNSIGNED_SHIFT_RIGHT;
71       case LT:
72         return TurbineOperatorKind.LESS_THAN;
73       case GT:
74         return TurbineOperatorKind.GREATER_THAN;
75       case LTE:
76         return TurbineOperatorKind.LESS_THAN_EQ;
77       case GTE:
78         return TurbineOperatorKind.GREATER_THAN_EQ;
79       case EQ:
80         return TurbineOperatorKind.EQUAL;
81       case NOTEQ:
82         return TurbineOperatorKind.NOT_EQUAL;
83       case AND:
84         return TurbineOperatorKind.BITWISE_AND;
85       case OR:
86         return TurbineOperatorKind.BITWISE_OR;
87       case XOR:
88         return TurbineOperatorKind.BITWISE_XOR;
89       case ANDAND:
90         return TurbineOperatorKind.AND;
91       case OROR:
92         return TurbineOperatorKind.OR;
93       case COND:
94         return TurbineOperatorKind.TERNARY;
95       default:
96         return null;
97     }
98   }
99 
primary(boolean negate)100   private @Nullable Expression primary(boolean negate) {
101     switch (token) {
102       case INT_LITERAL:
103         return finishLiteral(TurbineConstantTypeKind.INT, negate);
104       case DOUBLE_LITERAL:
105         return finishLiteral(TurbineConstantTypeKind.DOUBLE, negate);
106       case LONG_LITERAL:
107         return finishLiteral(TurbineConstantTypeKind.LONG, negate);
108       case FLOAT_LITERAL:
109         return finishLiteral(TurbineConstantTypeKind.FLOAT, negate);
110       case TRUE:
111         {
112           int pos = position;
113           eat();
114           return new Tree.Literal(
115               pos, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(true));
116         }
117       case FALSE:
118         {
119           int pos = position;
120           eat();
121           return new Tree.Literal(
122               pos, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(false));
123         }
124       case CHAR_LITERAL:
125         return finishLiteral(TurbineConstantTypeKind.CHAR, negate);
126       case STRING_LITERAL:
127         return finishLiteral(TurbineConstantTypeKind.STRING, false);
128       case PLUS:
129         eat();
130         return unaryRest(TurbineOperatorKind.UNARY_PLUS);
131       case MINUS:
132         eat();
133         return unaryRest(TurbineOperatorKind.NEG);
134       case NOT:
135         eat();
136         return unaryRest(TurbineOperatorKind.NOT);
137       case TILDE:
138         eat();
139         return unaryRest(TurbineOperatorKind.BITWISE_COMP);
140       case LPAREN:
141         return maybeCast();
142       case LBRACE:
143         int pos = position;
144         eat();
145         return arrayInitializer(pos);
146       case IDENT:
147         return qualIdent();
148       case BYTE:
149         return primitiveClassLiteral(TurbineConstantTypeKind.BYTE);
150       case CHAR:
151         return primitiveClassLiteral(TurbineConstantTypeKind.CHAR);
152       case DOUBLE:
153         return primitiveClassLiteral(TurbineConstantTypeKind.DOUBLE);
154       case FLOAT:
155         return primitiveClassLiteral(TurbineConstantTypeKind.FLOAT);
156       case INT:
157         return primitiveClassLiteral(TurbineConstantTypeKind.INT);
158       case LONG:
159         return primitiveClassLiteral(TurbineConstantTypeKind.LONG);
160       case SHORT:
161         return primitiveClassLiteral(TurbineConstantTypeKind.SHORT);
162       case BOOLEAN:
163         return primitiveClassLiteral(TurbineConstantTypeKind.BOOLEAN);
164       case VOID:
165         eat();
166         return finishClassLiteral(position, new Tree.VoidTy(position));
167       case AT:
168         return annotation();
169       default:
170         return null;
171     }
172   }
173 
primitiveClassLiteral(TurbineConstantTypeKind type)174   private Expression primitiveClassLiteral(TurbineConstantTypeKind type) {
175     eat();
176     return finishClassLiteral(position, new Tree.PrimTy(position, ImmutableList.of(), type));
177   }
178 
maybeCast()179   private Expression maybeCast() {
180     eat();
181     switch (token) {
182       case BOOLEAN:
183         eat();
184         return castTail(TurbineConstantTypeKind.BOOLEAN);
185       case BYTE:
186         eat();
187         return castTail(TurbineConstantTypeKind.BYTE);
188       case SHORT:
189         eat();
190         return castTail(TurbineConstantTypeKind.SHORT);
191       case INT:
192         eat();
193         return castTail(TurbineConstantTypeKind.INT);
194       case LONG:
195         eat();
196         return castTail(TurbineConstantTypeKind.LONG);
197       case CHAR:
198         eat();
199         return castTail(TurbineConstantTypeKind.CHAR);
200       case DOUBLE:
201         eat();
202         return castTail(TurbineConstantTypeKind.DOUBLE);
203       case FLOAT:
204         eat();
205         return castTail(TurbineConstantTypeKind.FLOAT);
206       default:
207         return notCast();
208     }
209   }
210 
notCast()211   private @Nullable Expression notCast() {
212     Expression expr = expression(null);
213     if (expr == null) {
214       return null;
215     }
216     if (token != Token.RPAREN) {
217       return null;
218     }
219     eat();
220     if (expr.kind() == Tree.Kind.CONST_VAR_NAME) {
221       Tree.ConstVarName cvar = (Tree.ConstVarName) expr;
222       switch (token) {
223         case INT_LITERAL:
224         case FLOAT_LITERAL:
225         case TRUE:
226         case FALSE:
227         case CHAR_LITERAL:
228         case STRING_LITERAL:
229         case NOT:
230         case TILDE:
231         case IDENT:
232           Expression expression = primary(false);
233           if (expression == null) {
234             throw error(ErrorKind.EXPRESSION_ERROR);
235           }
236           return new Tree.TypeCast(position, asClassTy(cvar.position(), cvar.name()), expression);
237         default:
238           return new Tree.Paren(position, expr);
239       }
240     } else {
241       return new Tree.Paren(position, expr);
242     }
243   }
244 
asClassTy(int pos, ImmutableList<Tree.Ident> names)245   private static ClassTy asClassTy(int pos, ImmutableList<Tree.Ident> names) {
246     ClassTy cty = null;
247     for (Tree.Ident bit : names) {
248       cty = new ClassTy(pos, Optional.ofNullable(cty), bit, ImmutableList.of(), ImmutableList.of());
249     }
250     return cty;
251   }
252 
eat()253   private void eat() {
254     token = lexer.next();
255     position = lexer.position();
256   }
257 
arrayInitializer(int pos)258   private @Nullable Expression arrayInitializer(int pos) {
259     if (token == Token.RBRACE) {
260       eat();
261       return new Tree.ArrayInit(pos, ImmutableList.<Tree.Expression>of());
262     }
263 
264     ImmutableList.Builder<Tree.Expression> exprs = ImmutableList.builder();
265     OUTER:
266     while (true) {
267       if (token == Token.RBRACE) {
268         eat();
269         break OUTER;
270       }
271       Expression item = expression(null);
272       if (item == null) {
273         return null;
274       }
275       exprs.add(item);
276       switch (token) {
277         case COMMA:
278           eat();
279           break;
280         case RBRACE:
281           eat();
282           break OUTER;
283         default:
284           return null;
285       }
286     }
287     return new Tree.ArrayInit(pos, exprs.build());
288   }
289 
290   /** Finish hex, decimal, octal, and binary integer literals (see JLS 3.10.1). */
finishLiteral(TurbineConstantTypeKind kind, boolean negate)291   private Expression finishLiteral(TurbineConstantTypeKind kind, boolean negate) {
292     int pos = position;
293     String text = ident().value();
294     Const.Value value;
295     switch (kind) {
296       case INT:
297         {
298           int radix = 10;
299           if (text.startsWith("0x") || text.startsWith("0X")) {
300             text = text.substring(2);
301             radix = 0x10;
302           } else if (isOctal(text)) {
303             radix = 010;
304           } else if (text.startsWith("0b") || text.startsWith("0B")) {
305             text = text.substring(2);
306             radix = 0b10;
307           }
308           if (negate) {
309             text = "-" + text;
310           }
311           long longValue = parseLong(text, radix);
312           if (radix == 10) {
313             if (longValue != (int) longValue) {
314               throw error(ErrorKind.INVALID_LITERAL, text);
315             }
316           } else {
317             if (Math.abs(longValue) >> 32 != 0) {
318               throw error(ErrorKind.INVALID_LITERAL, text);
319             }
320           }
321           value = new Const.IntValue((int) longValue);
322           break;
323         }
324       case LONG:
325         {
326           int radix = 10;
327           if (text.startsWith("0x") || text.startsWith("0X")) {
328             text = text.substring(2);
329             radix = 0x10;
330           } else if (isOctal(text)) {
331             radix = 010;
332           } else if (text.startsWith("0b") || text.startsWith("0B")) {
333             text = text.substring(2);
334             radix = 0b10;
335           }
336           if (negate) {
337             text = "-" + text;
338           }
339           if (text.endsWith("L") || text.endsWith("l")) {
340             text = text.substring(0, text.length() - 1);
341           }
342           value = new Const.LongValue(parseLong(text, radix));
343           break;
344         }
345       case CHAR:
346         value = new Const.CharValue(text.charAt(0));
347         break;
348       case FLOAT:
349         try {
350           value = new Const.FloatValue(Float.parseFloat(text.replace("_", "")));
351         } catch (NumberFormatException e) {
352           throw error(ErrorKind.INVALID_LITERAL, text);
353         }
354         break;
355       case DOUBLE:
356         try {
357           value = new Const.DoubleValue(Double.parseDouble(text.replace("_", "")));
358         } catch (NumberFormatException e) {
359           throw error(ErrorKind.INVALID_LITERAL, text);
360         }
361         break;
362       case STRING:
363         value = new Const.StringValue(text);
364         break;
365       default:
366         throw new AssertionError(kind);
367     }
368     eat();
369     return new Tree.Literal(pos, kind, value);
370   }
371 
isOctal(String text)372   static boolean isOctal(String text) {
373     if (!text.startsWith("0")) {
374       return false;
375     }
376     if (text.length() <= 1) {
377       return false;
378     }
379     char next = text.charAt(1);
380     return Character.isDigit(next) || next == '_';
381   }
382 
383   /**
384    * Parse the string as a signed long.
385    *
386    * <p>{@link Long#parseLong} doesn't accept {@link Long#MIN_VALUE}.
387    */
parseLong(String text, int radix)388   private long parseLong(String text, int radix) {
389     long r = 0;
390     boolean neg = text.startsWith("-");
391     if (neg) {
392       text = text.substring(1);
393     }
394     for (int i = 0; i < text.length(); i++) {
395       char c = text.charAt(i);
396       int digit;
397       if ('0' <= c && c <= '9') {
398         digit = c - '0';
399       } else if ('a' <= c && c <= 'f') {
400         digit = 10 + (c - 'a');
401       } else if ('A' <= c && c <= 'F') {
402         digit = 10 + (c - 'A');
403       } else if (c == '_') {
404         continue;
405       } else {
406         throw error(ErrorKind.INVALID_LITERAL, text);
407       }
408       r = (r * radix) + digit;
409     }
410     if (neg) {
411       r = -r;
412     }
413     return r;
414   }
415 
unaryRest(TurbineOperatorKind op)416   private @Nullable Expression unaryRest(TurbineOperatorKind op) {
417     boolean negate = op == TurbineOperatorKind.NEG;
418     Expression expr = primary(negate);
419     if (expr == null) {
420       return null;
421     }
422     if (negate && expr.kind() == Tree.Kind.LITERAL) {
423       Tree.Literal lit = (Tree.Literal) expr;
424       switch (lit.tykind()) {
425         case INT:
426         case LONG:
427           return expr;
428         default:
429           break;
430       }
431     }
432     return new Tree.Unary(position, expr, op);
433   }
434 
qualIdent()435   private @Nullable Expression qualIdent() {
436     int pos = position;
437     ImmutableList.Builder<Ident> bits = ImmutableList.builder();
438     bits.add(ident());
439     eat();
440     while (token == Token.DOT) {
441       eat();
442       switch (token) {
443         case IDENT:
444           bits.add(ident());
445           break;
446         case CLASS:
447           // TODO(cushon): only allow in annotations?
448           eat();
449           return new Tree.ClassLiteral(pos, asClassTy(pos, bits.build()));
450         default:
451           return null;
452       }
453       eat();
454     }
455     if (token == Token.LBRACK) {
456       return finishClassLiteral(pos, asClassTy(pos, bits.build()));
457     }
458     return new Tree.ConstVarName(pos, bits.build());
459   }
460 
ident()461   private Ident ident() {
462     return new Ident(lexer.position(), lexer.stringValue());
463   }
464 
finishClassLiteral(int pos, Tree.Type type)465   private @Nullable Expression finishClassLiteral(int pos, Tree.Type type) {
466     while (token == Token.LBRACK) {
467       eat();
468       if (token != Token.RBRACK) {
469         return null;
470       }
471       eat();
472       type = new Tree.ArrTy(position, ImmutableList.of(), type);
473     }
474     if (token != Token.DOT) {
475       return null;
476     }
477     eat();
478     if (token != Token.CLASS) {
479       return null;
480     }
481     eat();
482     return new ClassLiteral(pos, type);
483   }
484 
expression()485   public @Nullable Expression expression() {
486     Expression result = expression(null);
487     switch (token) {
488       case EOF:
489       case SEMI:
490         // TODO(cushon): only allow in annotations?
491       case COMMA:
492       case RPAREN:
493         return result;
494       default:
495         return null;
496     }
497   }
498 
expression(TurbineOperatorKind.Precedence prec)499   private @Nullable Expression expression(TurbineOperatorKind.Precedence prec) {
500     Expression term1 = primary(false);
501     if (term1 == null) {
502       return null;
503     }
504     return expression(term1, prec);
505   }
506 
expression(Expression term1, TurbineOperatorKind.Precedence prec)507   private @Nullable Expression expression(Expression term1, TurbineOperatorKind.Precedence prec) {
508     while (true) {
509       if (token == Token.EOF) {
510         return term1;
511       }
512       TurbineOperatorKind op = operator(token);
513       if (op == null) {
514         return term1;
515       }
516       if (prec != null && op.prec().rank() <= prec.rank()) {
517         return term1;
518       }
519       eat();
520       switch (op) {
521         case TERNARY:
522           term1 = ternary(term1);
523           break;
524         case ASSIGN:
525           term1 = assign(term1, op);
526           break;
527         default:
528           int pos = position;
529           Expression term2 = expression(op.prec());
530           if (term2 == null) {
531             return null;
532           }
533           term1 = new Tree.Binary(pos, term1, term2, op);
534       }
535       if (term1 == null) {
536         return null;
537       }
538     }
539   }
540 
assign(Expression term1, TurbineOperatorKind op)541   private @Nullable Expression assign(Expression term1, TurbineOperatorKind op) {
542     if (!(term1 instanceof Tree.ConstVarName)) {
543       return null;
544     }
545     ImmutableList<Ident> names = ((Tree.ConstVarName) term1).name();
546     if (names.size() > 1) {
547       return null;
548     }
549     Ident name = getOnlyElement(names);
550     Expression rhs = expression(op.prec());
551     if (rhs == null) {
552       return null;
553     }
554     return new Tree.Assign(term1.position(), name, rhs);
555   }
556 
ternary(Expression term1)557   private @Nullable Expression ternary(Expression term1) {
558     Expression thenExpr = expression(TurbineOperatorKind.Precedence.TERNARY);
559     if (thenExpr == null) {
560       return null;
561     }
562     if (token != Token.COLON) {
563       return null;
564     }
565     eat();
566     Expression elseExpr = expression();
567     if (elseExpr == null) {
568       return null;
569     }
570     return new Tree.Conditional(position, term1, thenExpr, elseExpr);
571   }
572 
castTail(TurbineConstantTypeKind ty)573   private @Nullable Expression castTail(TurbineConstantTypeKind ty) {
574     if (token != Token.RPAREN) {
575       return null;
576     }
577     eat();
578     Expression rhs = primary(false);
579     if (rhs == null) {
580       return null;
581     }
582     return new Tree.TypeCast(position, new Tree.PrimTy(position, ImmutableList.of(), ty), rhs);
583   }
584 
annotation()585   private @Nullable AnnoExpr annotation() {
586     if (token != Token.AT) {
587       throw new AssertionError();
588     }
589     eat();
590     int pos = position;
591     Expression constVarName = qualIdent();
592     if (!(constVarName instanceof Tree.ConstVarName)) {
593       return null;
594     }
595     ImmutableList<Ident> name = ((Tree.ConstVarName) constVarName).name();
596     ImmutableList.Builder<Tree.Expression> args = ImmutableList.builder();
597     if (token == Token.LPAREN) {
598       eat();
599       while (token != Token.RPAREN) {
600         int argPos = position;
601         Expression expression = expression();
602         if (expression == null) {
603           throw TurbineError.format(lexer.source(), argPos, ErrorKind.INVALID_ANNOTATION_ARGUMENT);
604         }
605         args.add(expression);
606         if (token != Token.COMMA) {
607           break;
608         }
609         eat();
610       }
611       if (token == Token.RPAREN) {
612         eat();
613       }
614     }
615     return new Tree.AnnoExpr(pos, new Tree.Anno(pos, name, args.build()));
616   }
617 
618   @CheckReturnValue
error(ErrorKind kind, Object... args)619   private TurbineError error(ErrorKind kind, Object... args) {
620     return TurbineError.format(lexer.source(), lexer.position(), kind, args);
621   }
622 
f()623   public int f() {
624     return helper(1, 2);
625   }
626 
helper(int x, int y)627   private int helper(int x, int y) {
628     return x + y;
629   }
630 }
631