From: Jürg Billeter Date: Sat, 10 Oct 2009 11:40:04 +0000 (+0200) Subject: Add support for string templates X-Git-Tag: 0.7.8~68 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=15e7c259c4e2b1c54f2aea90c6304d40a1447389;p=thirdparty%2Fvala.git Add support for string templates --- diff --git a/vala/Makefile.am b/vala/Makefile.am index 016d92414..db4d5c242 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 4c2d39734..2fa695b11 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 e1b00f014..7cdd68cd6 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 000000000..e1b4085c0 --- /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 + */ + +using Gee; + +public class Vala.Template : Expression { + private Gee.List expression_list = new ArrayList (); + + public Template () { + } + + public void add_expression (Expression expr) { + expression_list.add (expr); + } + + public Gee.List 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 d22c07bdf..bfacde16d 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 a0e15272e..eaea83359 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")]