StringTerm.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  15.  *
  16.  * Alternatively, the contents of this file may be used under the terms of
  17.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  18.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  19.  * in which case the provisions of the GPL or the LGPL are applicable instead
  20.  * of those above. If you wish to allow use of your version of this file only
  21.  * under the terms of either the GPL or the LGPL, and not to allow others to
  22.  * use your version of this file under the terms of the EPL, indicate your
  23.  * decision by deleting the provisions above and replace them with the notice
  24.  * and other provisions required by the GPL or the LGPL. If you do not delete
  25.  * the provisions above, a recipient may use your version of this file under
  26.  * the terms of any one of the EPL, the GPL or the LGPL.
  27.  ***** END LICENSE BLOCK *****/
  28. package org.jruby.lexer.yacc;

  29. import java.io.IOException;
  30. import org.jcodings.Encoding;
  31. import org.jruby.ast.RegexpNode;
  32. import org.jruby.lexer.yacc.SyntaxException.PID;
  33. import org.jruby.parser.Tokens;
  34. import org.jruby.util.ByteList;
  35. import org.jruby.util.KCode;
  36. import org.jruby.util.RegexpOptions;

  37. public class StringTerm extends StrTerm {
  38.     // Expand variables, Indentation of final marker
  39.     private int flags;

  40.     // Start of string ([, (, {, <, ', ", \n)
  41.     private final char begin;

  42.     // End of string (], ), }, >, ', ", \0)
  43.     private final char end;

  44.     // How many strings are nested in the current string term
  45.     private int nest;

  46.     public StringTerm(int flags, int begin, int end) {
  47.         this.flags = flags;
  48.         this.begin = (char) begin;
  49.         this.end   = (char) end;
  50.         this.nest  = 0;
  51.     }

  52.     public int getNest() {
  53.         return nest;
  54.     }

  55.     protected ByteList createByteList(RubyLexer lexer) {
  56.         return new ByteList(new byte[]{}, lexer.getEncoding());
  57.     }

  58.     private int endFound(RubyLexer lexer, LexerSource src) throws IOException {
  59.             if ((flags & RubyLexer.STR_FUNC_QWORDS) != 0) {
  60.                 flags = -1;
  61.                 lexer.getPosition();
  62.                 return ' ';
  63.             }

  64.             if ((flags & RubyLexer.STR_FUNC_REGEXP) != 0) {
  65.                 RegexpOptions options = parseRegexpFlags(lexer, src);
  66.                 ByteList regexpBytelist = ByteList.create("");

  67.                 lexer.setValue(new RegexpNode(src.getPosition(), regexpBytelist, options));
  68.                 return Tokens.tREGEXP_END;
  69.             }

  70.             lexer.setValue("" + end);
  71.             return Tokens.tSTRING_END;
  72.     }

  73.     // Return of 0 means failed to find anything.  Non-zero means return that from lexer.
  74.     private int parsePeekVariableName(RubyLexer lexer, LexerSource src) throws IOException {
  75.         int c = src.read(); // byte right after #
  76.         int significant = -1;
  77.         switch (c) {
  78.             case '$': {  // we unread back to before the $ so next lex can read $foo
  79.                 int c2 = src.read();

  80.                 if (c2 == '-') {
  81.                     int c3 = src.read();

  82.                     if (c3 == RubyLexer.EOF) {
  83.                         src.unread(c3); src.unread(c2);
  84.                         return 0;
  85.                     }

  86.                     significant = c3;                              // $-0 potentially
  87.                     src.unread(c3); src.unread(c2);
  88.                     break;
  89.                 } else if (lexer.isGlobalCharPunct(c2)) {          // $_ potentially
  90.                     lexer.setValue("#" + (char) c2);

  91.                     src.unread(c2); src.unread(c);
  92.                     return Tokens.tSTRING_DVAR;
  93.                 }

  94.                 significant = c2;                                  // $FOO potentially
  95.                 src.unread(c2);
  96.                 break;
  97.             }
  98.             case '@': {  // we unread back to before the @ so next lex can read @foo
  99.                 int c2 = src.read();

  100.                 if (c2 == '@') {
  101.                     int c3 = src.read();

  102.                     if (c3 == RubyLexer.EOF) {
  103.                         src.unread(c3); src.unread(c2);
  104.                         return 0;
  105.                     }

  106.                     significant = c3;                                // #@@foo potentially
  107.                     src.unread(c3); src.unread(c2);
  108.                     break;
  109.                 }

  110.                 significant = c2;                                    // #@foo potentially
  111.                 src.unread(c2);
  112.                 break;
  113.             }
  114.             case '{':
  115.                 lexer.setValue("#" + (char) c);
  116.                 return Tokens.tSTRING_DBEG;
  117.             default:
  118.                 // We did not find significant char after # so push it back to
  119.                 // be processed as an ordinary string.
  120.                 src.unread(c);
  121.                 return 0;
  122.         }

  123.         if (significant != -1 && Character.isAlphabetic(significant) || significant == '_') {
  124.             src.unread(c);
  125.             lexer.setValue("#" + significant);
  126.             return Tokens.tSTRING_DVAR;
  127.         }

  128.         return 0;
  129.     }

  130.     public int parseString(RubyLexer lexer, LexerSource src) throws IOException {
  131.         boolean spaceSeen = false;
  132.         int c;

  133.         // FIXME: How much more obtuse can this be?
  134.         // Heredoc already parsed this and saved string...Do not parse..just return
  135.         if (flags == -1) {
  136.             lexer.setValue("" + end);
  137.             return Tokens.tSTRING_END;
  138.         }

  139.         c = src.read();
  140.         if ((flags & RubyLexer.STR_FUNC_QWORDS) != 0 && Character.isWhitespace(c)) {
  141.             do { c = src.read(); } while (Character.isWhitespace(c));
  142.             spaceSeen = true;
  143.         }

  144.         if (c == end && nest == 0) return endFound(lexer, src);
  145.        
  146.         if (spaceSeen) {
  147.             src.unread(c);
  148.             lexer.getPosition();
  149.             return ' ';
  150.         }
  151.        
  152.         ByteList buffer = createByteList(lexer);
  153.         lexer.newtok();
  154.         if ((flags & RubyLexer.STR_FUNC_EXPAND) != 0 && c == '#') {
  155.             int token = parsePeekVariableName(lexer, src);

  156.             if (token != 0) return token;
  157.         }
  158.         src.unread(c);
  159.        
  160.         if (parseStringIntoBuffer(lexer, src, buffer) == RubyLexer.EOF) {
  161.             throw new SyntaxException(PID.STRING_HITS_EOF, src.getPosition(),
  162.                     src.getCurrentLine(), "unterminated string meets end of file");
  163.         }

  164.         lexer.setValue(lexer.createStrNode(lexer.getPosition(), buffer, flags));
  165.         return Tokens.tSTRING_CONTENT;
  166.     }

  167.     private RegexpOptions parseRegexpFlags(RubyLexer lexer, LexerSource src) throws IOException {
  168.         RegexpOptions options = new RegexpOptions();
  169.         int c;
  170.         StringBuilder unknownFlags = new StringBuilder(10);

  171.         lexer.newtok();
  172.         for (c = src.read(); c != RubyLexer.EOF
  173.                 && Character.isLetter(c); c = src.read()) {
  174.             switch (c) {
  175.             case 'i':
  176.                 options.setIgnorecase(true);
  177.                 break;
  178.             case 'x':
  179.                 options.setExtended(true);
  180.                 break;
  181.             case 'm':
  182.                 options.setMultiline(true);
  183.                 break;
  184.             case 'o':
  185.                 options.setOnce(true);
  186.                 break;
  187.             case 'n':
  188.                 options.setExplicitKCode(KCode.NONE);
  189.                 break;
  190.             case 'e':
  191.                 options.setExplicitKCode(KCode.EUC);
  192.                 break;
  193.             case 's':
  194.                 options.setExplicitKCode(KCode.SJIS);
  195.                 break;
  196.             case 'u':
  197.                 options.setExplicitKCode(KCode.UTF8);
  198.                 break;
  199.             case 'j':
  200.                 options.setJava(true);
  201.                 break;
  202.             default:
  203.                 unknownFlags.append((char) c);
  204.                 break;
  205.             }
  206.         }
  207.         src.unread(c);
  208.         if (unknownFlags.length() != 0) {
  209.             throw new SyntaxException(PID.REGEXP_UNKNOWN_OPTION, src.getPosition(), "unknown regexp option"
  210.                     + (unknownFlags.length() > 1 ? "s" : "") + " - "
  211.                     + unknownFlags.toString(), unknownFlags.toString());
  212.         }
  213.         return options;
  214.     }

  215.     private void mixedEscape(RubyLexer lexer, Encoding foundEncoding, Encoding parserEncoding) {
  216.         throw new SyntaxException(PID.MIXED_ENCODING,lexer.getPosition(), "",
  217.                 foundEncoding + " mixed within " + parserEncoding);
  218.     }

  219.     // mri: parser_tokadd_string
  220.     public int parseStringIntoBuffer(RubyLexer lexer, LexerSource src, ByteList buffer) throws IOException {
  221.         boolean qwords = (flags & RubyLexer.STR_FUNC_QWORDS) != 0;
  222.         boolean expand = (flags & RubyLexer.STR_FUNC_EXPAND) != 0;
  223.         boolean escape = (flags & RubyLexer.STR_FUNC_ESCAPE) != 0;
  224.         boolean regexp = (flags & RubyLexer.STR_FUNC_REGEXP) != 0;
  225.         boolean symbol = (flags & RubyLexer.STR_FUNC_SYMBOL) != 0;
  226.         boolean hasNonAscii = false;
  227.         int c;
  228.         Encoding encoding = lexer.getEncoding();

  229.         while ((c = src.read()) != RubyLexer.EOF) {
  230.             if (begin != '\0' && c == begin) {
  231.                 nest++;
  232.             } else if (c == end) {
  233.                 if (nest == 0) {
  234.                     src.unread(c);
  235.                     break;
  236.                 }
  237.                 nest--;
  238.             } else if (expand && c == '#' && !src.peek('\n')) {
  239.                 int c2 = src.read();

  240.                 if (c2 == '$' || c2 == '@' || c2 == '{') {
  241.                     src.unread(c2);
  242.                     src.unread(c);
  243.                     break;
  244.                 }
  245.                 src.unread(c2);
  246.             } else if (c == '\\') {
  247.                 c = src.read();
  248.                 switch (c) {
  249.                 case '\n':
  250.                     if (qwords) break;
  251.                     if (expand) continue;
  252.                     buffer.append('\\');
  253.                     break;

  254.                 case '\\':
  255.                     if (escape) buffer.append(c);
  256.                     break;

  257.                 case 'u':
  258.                     if (!expand) {
  259.                         buffer.append('\\');
  260.                         break;
  261.                     }

  262.                     if (regexp) {
  263.                         lexer.readUTFEscapeRegexpLiteral(buffer);
  264.                     } else {
  265.                         lexer.readUTFEscape(buffer, true, symbol);
  266.                     }

  267.                     if (hasNonAscii && buffer.getEncoding() != encoding) {
  268.                         mixedEscape(lexer, buffer.getEncoding(), encoding);
  269.                     }

  270.                     continue;
  271.                 default:
  272.                     if (regexp) {
  273.                         src.unread(c);
  274.                         parseEscapeIntoBuffer(lexer, encoding, src, buffer);

  275.                         if (hasNonAscii && buffer.getEncoding() != encoding) {
  276.                             mixedEscape(lexer, buffer.getEncoding(), encoding);
  277.                         }
  278.                        
  279.                         continue;
  280.                     } else if (expand) {
  281.                         src.unread(c);
  282.                         if (escape) buffer.append('\\');
  283.                         c = lexer.readEscape();
  284.                     } else if (qwords && Character.isWhitespace(c)) {
  285.                         /* ignore backslashed spaces in %w */
  286.                     } else if (c != end && !(begin != '\0' && c == begin)) {
  287.                         buffer.append('\\');
  288.                     }
  289.                 }
  290.             } else if (!Encoding.isAscii((byte) c)) {
  291.                 if (buffer.getEncoding() != encoding) {
  292.                     mixedEscape(lexer, buffer.getEncoding(), encoding);
  293.                 }
  294.                
  295.                 if (addNonAsciiToBuffer(c, src, encoding, lexer, buffer) == RubyLexer.EOF) return RubyLexer.EOF;

  296.                 continue;
  297.             } else if (qwords && Character.isWhitespace(c)) {
  298.                 src.unread(c);
  299.                 break;
  300.             }

  301.             // Hmm did they change this?
  302. /*          if (c == '\0' && symbol) {
  303.                 throw new SyntaxException(PID.NUL_IN_SYMBOL, lexer.getPosition(),
  304.                         src.getCurrentLine(), "symbol cannot contain '\\0'");
  305.             } else*/ if ((c & 0x80) != 0) {
  306.                 hasNonAscii = true;
  307.                 if (buffer.getEncoding() != encoding) {
  308.                     mixedEscape(lexer, buffer.getEncoding(), encoding);
  309.                 }
  310.             }
  311.             buffer.append(c);
  312.         }
  313.        
  314.         return c;
  315.     }

  316.     // Was a goto in original ruby lexer
  317.     private void escaped(RubyLexer lexer, Encoding encoding, LexerSource src, ByteList buffer) throws java.io.IOException {
  318.         int c;

  319.         switch (c = src.read()) {
  320.         case '\\':
  321.             parseEscapeIntoBuffer(lexer, encoding, src, buffer);
  322.             break;
  323.         case RubyLexer.EOF:
  324.             throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  325.                     src.getCurrentLine(), "Invalid escape character syntax");
  326.         default:
  327.             buffer.append(c);
  328.         }
  329.     }

  330.     private void parseEscapeIntoBuffer(RubyLexer lexer, Encoding encoding, LexerSource src, ByteList buffer) throws java.io.IOException {
  331.         int c;

  332.         switch (c = src.read()) {
  333.         case '\n':
  334.             break; /* just ignore */
  335.         case '0':
  336.         case '1':
  337.         case '2':
  338.         case '3': /* octal constant */
  339.         case '4':
  340.         case '5':
  341.         case '6':
  342.         case '7':
  343.             buffer.append('\\');
  344.             buffer.append(c);
  345.             for (int i = 0; i < 2; i++) {
  346.                 c = src.read();
  347.                 if (c == RubyLexer.EOF) {
  348.                     throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  349.                             src.getCurrentLine(), "Invalid escape character syntax");
  350.                 }
  351.                 if (!RubyLexer.isOctChar(c)) {
  352.                     src.unread(c);
  353.                     break;
  354.                 }
  355.                 buffer.append(c);
  356.             }
  357.             break;
  358.         case 'x': /* hex constant */
  359.             buffer.append('\\');
  360.             buffer.append(c);
  361.             c = src.read();
  362.             if (!RubyLexer.isHexChar(c)) {
  363.                 throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  364.                         src.getCurrentLine(), "Invalid escape character syntax");
  365.             }
  366.             buffer.append(c);
  367.             c = src.read();
  368.             if (RubyLexer.isHexChar(c)) {
  369.                 buffer.append(c);
  370.             } else {
  371.                 src.unread(c);
  372.             }
  373.             break;
  374.         case 'M':
  375.             if ((c = src.read()) != '-') {
  376.                 throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  377.                         src.getCurrentLine(), "Invalid escape character syntax");
  378.             }
  379.             buffer.append(new byte[] { '\\', 'M', '-' });
  380.             escaped(lexer, encoding, src, buffer);
  381.             break;
  382.         case 'C':
  383.             if ((c = src.read()) != '-') {
  384.                 throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  385.                         src.getCurrentLine(), "Invalid escape character syntax");
  386.             }
  387.             buffer.append(new byte[] { '\\', 'C', '-' });
  388.             escaped(lexer, encoding, src, buffer);
  389.             break;
  390.         case 'c':
  391.             buffer.append(new byte[] { '\\', 'c' });
  392.             escaped(lexer, encoding, src, buffer);
  393.             break;
  394.         case RubyLexer.EOF:
  395.             throw new SyntaxException(PID.INVALID_ESCAPE_SYNTAX, src.getPosition(),
  396.                     src.getCurrentLine(), "Invalid escape character syntax");
  397.         default:
  398.             if (!Encoding.isAscii((byte) c)) {
  399.                 addNonAsciiToBuffer(c, src, encoding, lexer, buffer);
  400.             } else {
  401.                 if (c != '\\' || c != end) buffer.append('\\');

  402.                 buffer.append(c);
  403.             }
  404.         }
  405.     }

  406.     private int addNonAsciiToBuffer(int c, LexerSource src, Encoding encoding, RubyLexer lexer, ByteList buffer) throws SyntaxException, IOException {
  407.         c = src.readCodepoint(c, encoding);

  408.         if (c == -2) { // FIXME: Hack
  409.             throw new SyntaxException(PID.INVALID_MULTIBYTE_CHAR, lexer.getPosition(),
  410.                     null, "invalid multibyte char (" + encoding + ")");
  411.         }

  412.         // FIXME: We basically go from bytes to codepoint back to bytes to append them...fix this
  413.         return lexer.tokenAddMBC(c, buffer);
  414.     }
  415. }