[vala] Add support for string templates
- From: Jürg Billeter <juergbi src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [vala] Add support for string templates
- Date: Sat, 10 Oct 2009 12:47:33 +0000 (UTC)
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]