]> git.ipfire.org Git - thirdparty/vala.git/commitdiff
Add support for string templates
authorJürg Billeter <j@bitron.ch>
Sat, 10 Oct 2009 11:40:04 +0000 (13:40 +0200)
committerJürg Billeter <j@bitron.ch>
Sat, 10 Oct 2009 12:46:01 +0000 (14:46 +0200)
vala/Makefile.am
vala/valaparser.vala
vala/valascanner.vala
vala/valatemplate.vala [new file with mode: 0644]
vala/valatokentype.vala
vapi/glib-2.0.vapi

index 016d9241494d1b8c2fcd9627cb3d891e07584b9b..db4d5c24277fbf0b84fe82d41db61f5ab8ace87f 100644 (file)
@@ -136,6 +136,7 @@ libvalacore_la_VALASOURCES = \
        valaswitchstatement.vala \
        valasymbol.vala \
        valasymbolresolver.vala \
+       valatemplate.vala \
        valathrowstatement.vala \
        valatokentype.vala \
        valatrystatement.vala \
index 4c2d39734b176d3b5990d8b1fba5ea4a569ec1c5..2fa695b11d50de00d44b733c7126c51dbbaa1caa 100644 (file)
@@ -286,6 +286,9 @@ public class Vala.Parser : CodeVisitor {
                case TokenType.STRING_LITERAL:
                        next ();
                        return new StringLiteral (get_last_string (), get_src (begin));
+               case TokenType.TEMPLATE_STRING_LITERAL:
+                       next ();
+                       return new StringLiteral ("\"%s\"".printf (get_last_string ()), get_src (begin));
                case TokenType.VERBATIM_STRING_LITERAL:
                        next ();
                        string raw_string = get_last_string ();
@@ -534,6 +537,7 @@ public class Vala.Parser : CodeVisitor {
                case TokenType.REAL_LITERAL:
                case TokenType.CHARACTER_LITERAL:
                case TokenType.STRING_LITERAL:
+               case TokenType.TEMPLATE_STRING_LITERAL:
                case TokenType.VERBATIM_STRING_LITERAL:
                case TokenType.NULL:
                        expr = parse_literal ();
@@ -544,6 +548,9 @@ public class Vala.Parser : CodeVisitor {
                case TokenType.OPEN_PARENS:
                        expr = parse_tuple ();
                        break;
+               case TokenType.OPEN_TEMPLATE:
+                       expr = parse_template ();
+                       break;
                case TokenType.THIS:
                        expr = parse_this_access ();
                        break;
@@ -636,6 +643,21 @@ public class Vala.Parser : CodeVisitor {
                return expr_list.get (0);
        }
 
+       Expression parse_template () throws ParseError {
+               var begin = get_location ();
+               var template = new Template ();
+
+               expect (TokenType.OPEN_TEMPLATE);
+               while (current () != TokenType.CLOSE_TEMPLATE) {
+                       template.add_expression (parse_expression ());
+                       expect (TokenType.COMMA);
+               }
+               expect (TokenType.CLOSE_TEMPLATE);
+
+               template.source_reference = get_src (begin);
+               return template;
+       }
+
        Expression parse_member_access (SourceLocation begin, Expression inner) throws ParseError {
                expect (TokenType.DOT);
                string id = parse_identifier ();
@@ -906,6 +928,7 @@ public class Vala.Parser : CodeVisitor {
                                        case TokenType.REAL_LITERAL:
                                        case TokenType.CHARACTER_LITERAL:
                                        case TokenType.STRING_LITERAL:
+                                       case TokenType.TEMPLATE_STRING_LITERAL:
                                        case TokenType.VERBATIM_STRING_LITERAL:
                                        case TokenType.NULL:
                                        case TokenType.THIS:
index e1b00f014e250ba7888d779f939be8c6fc9ec138..7cdd68cd68112869d0d02e0e8837306de398bd21 100644 (file)
@@ -45,6 +45,16 @@ public class Vala.Scanner {
                public bool skip_section;
        }
 
+       State[] state_stack;
+
+       enum State {
+               PARENS,
+               BRACE,
+               BRACKET,
+               TEMPLATE,
+               TEMPLATE_PART
+       }
+
        public Scanner (SourceFile source_file) {
                this.source_file = source_file;
 
@@ -57,6 +67,14 @@ public class Vala.Scanner {
                column = 1;
        }
 
+       bool in_template () {
+               return (state_stack.length > 0 && state_stack[state_stack.length - 1] == State.TEMPLATE);
+       }
+
+       bool in_template_part () {
+               return (state_stack.length > 0 && state_stack[state_stack.length - 1] == State.TEMPLATE_PART);
+       }
+
        bool is_ident_char (char c) {
                return (c.isalnum () || c == '_');
        }
@@ -428,7 +446,139 @@ public class Vala.Scanner {
                return type;
        }
 
+       public TokenType read_template_token (out SourceLocation token_begin, out SourceLocation token_end) {
+               TokenType type;
+               char* begin = current;
+               token_begin.pos = begin;
+               token_begin.line = line;
+               token_begin.column = column;
+
+               int token_length_in_chars = -1;
+
+               if (current >= end) {
+                       type = TokenType.EOF;
+               } else {
+                       switch (current[0]) {
+                       case '"':
+                               type = TokenType.CLOSE_TEMPLATE;
+                               current++;
+                               state_stack.length--;
+                               break;
+                       case '$':
+                               token_begin.pos++; // $ is not part of following token
+                               current++;
+                               if (current[0].isalpha () || current[0] == '_') {
+                                       int len = 0;
+                                       while (current < end && is_ident_char (current[0])) {
+                                               current++;
+                                               len++;
+                                       }
+                                       type = TokenType.IDENTIFIER;
+                                       state_stack += State.TEMPLATE_PART;
+                               } else if (current[0] == '(') {
+                                       current++;
+                                       column += 2;
+                                       state_stack += State.PARENS;
+                                       return read_token (out token_begin, out token_end);
+                               } else if (current[0] == '$') {
+                                       type = TokenType.TEMPLATE_STRING_LITERAL;
+                                       current++;
+                                       state_stack += State.TEMPLATE_PART;
+                               } else {
+                                       Report.error (new SourceReference (source_file, line, column + 1, line, column + 1), "unexpected character");
+                                       return read_template_token (out token_begin, out token_end);
+                               }
+                               break;
+                       default:
+                               type = TokenType.TEMPLATE_STRING_LITERAL;
+                               token_length_in_chars = 0;
+                               while (current < end && current[0] != '"' && current[0] != '$') {
+                                       if (current[0] == '\\') {
+                                               current++;
+                                               token_length_in_chars++;
+                                               if (current >= end) {
+                                                       break;
+                                               }
+
+                                               switch (current[0]) {
+                                               case '\'':
+                                               case '"':
+                                               case '\\':
+                                               case '0':
+                                               case 'b':
+                                               case 'f':
+                                               case 'n':
+                                               case 'r':
+                                               case 't':
+                                                       current++;
+                                                       token_length_in_chars++;
+                                                       break;
+                                               case 'x':
+                                                       // hexadecimal escape character
+                                                       current++;
+                                                       token_length_in_chars++;
+                                                       while (current < end && current[0].isxdigit ()) {
+                                                               current++;
+                                                               token_length_in_chars++;
+                                                       }
+                                                       break;
+                                               default:
+                                                       Report.error (new SourceReference (source_file, line, column + token_length_in_chars, line, column + token_length_in_chars), "invalid escape sequence");
+                                                       break;
+                                               }
+                                       } else if (current[0] == '\n') {
+                                               break;
+                                       } else {
+                                               unichar u = ((string) current).get_char_validated ((long) (end - current));
+                                               if (u != (unichar) (-1)) {
+                                                       current += u.to_utf8 (null);
+                                                       token_length_in_chars++;
+                                               } else {
+                                                       current++;
+                                                       Report.error (new SourceReference (source_file, line, column + token_length_in_chars, line, column + token_length_in_chars), "invalid UTF-8 character");
+                                               }
+                                       }
+                               }
+                               if (current >= end || current[0] == '\n') {
+                                       Report.error (new SourceReference (source_file, line, column + token_length_in_chars, line, column + token_length_in_chars), "syntax error, expected \"");
+                                       state_stack.length--;
+                                       return read_token (out token_begin, out token_end);
+                               }
+                               state_stack += State.TEMPLATE_PART;
+                               break;
+                       }
+               }
+
+               if (token_length_in_chars < 0) {
+                       column += (int) (current - begin);
+               } else {
+                       column += token_length_in_chars;
+               }
+
+               token_end.pos = current;
+               token_end.line = line;
+               token_end.column = column - 1;
+
+               return type;
+       }
+
        public TokenType read_token (out SourceLocation token_begin, out SourceLocation token_end) {
+               if (in_template ()) {
+                       return read_template_token (out token_begin, out token_end);
+               } else if (in_template_part ()) {
+                       state_stack.length--;
+
+                       token_begin.pos = current;
+                       token_begin.line = line;
+                       token_begin.column = column;
+
+                       token_end.pos = current;
+                       token_end.line = line;
+                       token_end.column = column - 1;
+
+                       return TokenType.COMMA;
+               }
+
                space ();
 
                TokenType type;
@@ -449,14 +599,20 @@ public class Vala.Scanner {
                        }
                        type = get_identifier_or_keyword (begin, len);
                } else if (current[0] == '@') {
-                       token_begin.pos++; // @ is not part of the identifier
-                       current++;
-                       int len = 0;
-                       while (current < end && is_ident_char (current[0])) {
+                       if (current < end - 1 && current[1] == '"') {
+                               type = TokenType.OPEN_TEMPLATE;
+                               current += 2;
+                               state_stack += State.TEMPLATE;
+                       } else {
+                               token_begin.pos++; // @ is not part of the identifier
                                current++;
-                               len++;
+                               int len = 0;
+                               while (current < end && is_ident_char (current[0])) {
+                                       current++;
+                                       len++;
+                               }
+                               type = TokenType.IDENTIFIER;
                        }
-                       type = TokenType.IDENTIFIER;
                } else if (current[0].isdigit ()) {
                        type = read_number ();
                } else {
@@ -464,26 +620,35 @@ public class Vala.Scanner {
                        case '{':
                                type = TokenType.OPEN_BRACE;
                                current++;
+                               state_stack += State.BRACE;
                                break;
                        case '}':
                                type = TokenType.CLOSE_BRACE;
                                current++;
+                               state_stack.length--;
                                break;
                        case '(':
                                type = TokenType.OPEN_PARENS;
                                current++;
+                               state_stack += State.PARENS;
                                break;
                        case ')':
                                type = TokenType.CLOSE_PARENS;
                                current++;
+                               state_stack.length--;
+                               if (in_template ()) {
+                                       type = TokenType.COMMA;
+                               }
                                break;
                        case '[':
                                type = TokenType.OPEN_BRACKET;
                                current++;
+                               state_stack += State.BRACKET;
                                break;
                        case ']':
                                type = TokenType.CLOSE_BRACKET;
                                current++;
+                               state_stack.length--;
                                break;
                        case '.':
                                type = TokenType.DOT;
diff --git a/vala/valatemplate.vala b/vala/valatemplate.vala
new file mode 100644 (file)
index 0000000..e1b4085
--- /dev/null
@@ -0,0 +1,78 @@
+/* valatemplate.vala
+ *
+ * Copyright (C) 2009  Jürg Billeter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ *     Jürg Billeter <j@bitron.ch>
+ */
+
+using Gee;
+
+public class Vala.Template : Expression {
+       private Gee.List<Expression> expression_list = new ArrayList<Expression> ();
+
+       public Template () {
+       }
+
+       public void add_expression (Expression expr) {
+               expression_list.add (expr);
+       }
+
+       public Gee.List<Expression> get_expressions () {
+               return expression_list;
+       }
+
+       public override bool is_pure () {
+               return false;
+       }
+
+       Expression stringify (Expression expr) {
+               if (expr is StringLiteral) {
+                       return expr;
+               } else {
+                       return new MethodCall (new MemberAccess (expr, "to_string", expr.source_reference), expr.source_reference);
+               }
+       }
+
+       public override bool check (SemanticAnalyzer analyzer) {
+               if (checked) {
+                       return !error;
+               }
+
+               checked = true;
+
+               Expression expr;
+
+               if (expression_list.size == 0) {
+                       expr = new StringLiteral ("\"\"", source_reference);
+               } else {
+                       expr = stringify (expression_list[0]);
+                       if (expression_list.size > 1) {
+                               var concat = new MethodCall (new MemberAccess (expr, "concat", source_reference), source_reference);
+                               for (int i = 1; i < expression_list.size; i++) {
+                                       concat.add_argument (stringify (expression_list[i]));
+                               }
+                               expr = concat;
+                       }
+               }
+
+               analyzer.replaced_nodes.add (this);
+               parent_node.replace_expression (this, expr);
+               return expr.check (analyzer);
+       }
+}
+
index d22c07bdfd39ac838f5bea58bec5cad51246452f..bfacde16dda1edf5ad5d13a50575e7ff51073e1a 100644 (file)
@@ -49,6 +49,7 @@ public enum Vala.TokenType {
        CLOSE_BRACE,
        CLOSE_BRACKET,
        CLOSE_PARENS,
+       CLOSE_TEMPLATE,
        COLON,
        COMMA,
        CONST,
@@ -107,6 +108,7 @@ public enum Vala.TokenType {
        OPEN_BRACE,
        OPEN_BRACKET,
        OPEN_PARENS,
+       OPEN_TEMPLATE,
        OVERRIDE,
        OWNED,
        PARAMS,
@@ -128,6 +130,7 @@ public enum Vala.TokenType {
        STRING_LITERAL,
        STRUCT,
        SWITCH,
+       TEMPLATE_STRING_LITERAL,
        THIS,
        THROW,
        THROWS,
index a0e15272ee88c06ba3585df8dd0c9d7c37e26ed8..eaea833590bedd6ac826fe4abdeab2129ddb66d5 100644 (file)
@@ -989,6 +989,10 @@ public class string {
                GLib.Memory.copy (result, this, this.size ());
                return result;
        }
+
+       public unowned string to_string () {
+               return this;
+       }
 }
 
 [CCode (cprefix = "G", lower_case_cprefix = "g_", cheader_filename = "glib.h")]