[vala] Add support for string templates



commit 15e7c259c4e2b1c54f2aea90c6304d40a1447389
Author: Jürg Billeter <j bitron ch>
Date:   Sat Oct 10 13:40:04 2009 +0200

    Add support for string templates

 vala/Makefile.am        |    1 +
 vala/valaparser.vala    |   23 ++++++
 vala/valascanner.vala   |  177 +++++++++++++++++++++++++++++++++++++++++++++--
 vala/valatemplate.vala  |   78 +++++++++++++++++++++
 vala/valatokentype.vala |    3 +
 vapi/glib-2.0.vapi      |    4 +
 6 files changed, 280 insertions(+), 6 deletions(-)
---
diff --git a/vala/Makefile.am b/vala/Makefile.am
index 016d924..db4d5c2 100644
--- a/vala/Makefile.am
+++ b/vala/Makefile.am
@@ -136,6 +136,7 @@ libvalacore_la_VALASOURCES = \
 	valaswitchstatement.vala \
 	valasymbol.vala \
 	valasymbolresolver.vala \
+	valatemplate.vala \
 	valathrowstatement.vala \
 	valatokentype.vala \
 	valatrystatement.vala \
diff --git a/vala/valaparser.vala b/vala/valaparser.vala
index 4c2d397..2fa695b 100644
--- a/vala/valaparser.vala
+++ b/vala/valaparser.vala
@@ -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:
diff --git a/vala/valascanner.vala b/vala/valascanner.vala
index e1b00f0..7cdd68c 100644
--- a/vala/valascanner.vala
+++ b/vala/valascanner.vala
@@ -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
index 0000000..e1b4085
--- /dev/null
+++ b/vala/valatemplate.vala
@@ -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);
+	}
+}
+
diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala
index d22c07b..bfacde1 100644
--- a/vala/valatokentype.vala
+++ b/vala/valatokentype.vala
@@ -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,
diff --git a/vapi/glib-2.0.vapi b/vapi/glib-2.0.vapi
index a0e1527..eaea833 100644
--- a/vapi/glib-2.0.vapi
+++ b/vapi/glib-2.0.vapi
@@ -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")]



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]