Skip to content

Instantly share code, notes, and snippets.

@RedHatter
Last active December 30, 2015 20:29
Show Gist options
  • Save RedHatter/7881160 to your computer and use it in GitHub Desktop.
Save RedHatter/7881160 to your computer and use it in GitHub Desktop.
A patch to the Vala and Genie compilers to add support for cascading member access. Includes vala unit tests. Recursive cascading now supported. Similar to described here: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html
diff --git a/tests/Makefile.am b/tests/Makefile.am
index bcf82e1..eb158d7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -112,6 +112,7 @@ TESTS = \
delegates/bug639751.vala \
delegates/bug659778.vala \
delegates/bug703804.vala \
+ objects/cascade.vala \
objects/chainup.vala \
objects/classes.vala \
objects/fields.vala \
diff --git a/tests/objects/cascade.vala b/tests/objects/cascade.vala
new file mode 100644
index 0000000..45a985f
--- /dev/null
+++ b/tests/objects/cascade.vala
@@ -0,0 +1,53 @@
+using GLib;
+
+class Test : Object {
+ public bool method_called;
+ public bool arguments_called;
+
+ public int variable;
+
+ public string another;
+
+ public void method () {
+ method_called = true;
+ }
+
+ public void arguments (Arg arg) {
+ arguments_called = true;
+ }
+}
+
+class Arg : Object {
+ public bool method_called;
+
+ public int variable;
+
+ public string another;
+
+ public void method () {
+ method_called = true;
+ }
+}
+
+static int main () {
+ Arg arg = new Arg ();
+ Test test = new Test ()
+ ..variable = 0
+ ..method ()
+ ..arguments (arg
+ ..variable = 1
+ ..another = "2"
+ ..method ())
+ ..another = "3";
+
+ assert (test.variable == 0);
+ assert (test.another == "3");
+ assert (test.method_called);
+ assert (test.arguments_called);
+ assert (arg.variable == 1);
+ assert (arg.another == "2");
+ assert (arg.method_called);
+
+
+ return 0;
+}
\ No newline at end of file
diff --git a/vala/Makefile.am b/vala/Makefile.am
index ee8d410..300c56c 100644
--- a/vala/Makefile.am
+++ b/vala/Makefile.am
@@ -29,6 +29,7 @@ libvalacore_la_VALASOURCES = \
valabooleanliteral.vala \
valabooleantype.vala \
valabreakstatement.vala \
+ valacascadevariable.vala \
valacastexpression.vala \
valacatchclause.vala \
valacharacterliteral.vala \
diff --git a/vala/valacascadevariable.vala b/vala/valacascadevariable.vala
new file mode 100644
index 0000000..77cb45e
--- /dev/null
+++ b/vala/valacascadevariable.vala
@@ -0,0 +1,39 @@
+/* valacascadevariable.vala
+ *
+ * Copyright (C) 2006-2010 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 <[email protected]>
+ */
+
+using GLib;
+
+/**
+ * Represents a variable generated for a cascade.
+ */
+public class Vala.CascadeVariable : LocalVariable {
+ private static int count = 0;
+
+ public CascadeVariable (Expression initializer, SourceReference? source_reference = null) {
+ base (null, "cascade_"+count.to_string (), initializer, source_reference);
+ count++;
+ }
+
+ public MemberAccess get_access () {
+ return new MemberAccess (null, name, null);
+ }
+}
\ No newline at end of file
diff --git a/vala/valagenieparser.vala b/vala/valagenieparser.vala
index 7abd943..0df41b1 100644
--- a/vala/valagenieparser.vala
+++ b/vala/valagenieparser.vala
@@ -46,6 +46,8 @@ public class Vala.Genie.Parser : CodeVisitor {
/* hack needed to know if any part of an expression is a lambda one */
bool current_expr_is_lambda;
+ Block current_block;
+
const int BUFFER_SIZE = 32;
static List<TypeParameter> _empty_type_parameter_list;
@@ -414,7 +416,7 @@ public class Vala.Genie.Parser : CodeVisitor {
void skip_symbol_name () throws ParseError {
do {
skip_identifier ();
- } while (accept (TokenType.DOT));
+ } while (accept (TokenType.DOT) || accept (TokenType.CASCADE));
}
UnresolvedSymbol parse_symbol_name () throws ParseError {
@@ -423,7 +425,7 @@ public class Vala.Genie.Parser : CodeVisitor {
do {
string name = parse_identifier ();
sym = new UnresolvedSymbol (sym, name, get_src (begin));
- } while (accept (TokenType.DOT));
+ } while (accept (TokenType.DOT) || accept (TokenType.CASCADE));
return sym;
}
@@ -656,7 +658,6 @@ public class Vala.Genie.Parser : CodeVisitor {
}
Expression parse_primary_expression () throws ParseError {
- var begin = get_location ();
Expression expr;
@@ -712,6 +713,13 @@ public class Vala.Genie.Parser : CodeVisitor {
break;
}
+ return parse_inner_primary_expression (expr);
+ }
+
+ Expression parse_inner_primary_expression (Expression inner) throws ParseError {
+ var expr = inner;
+ var begin = get_location ();
+
// process primary expressions that start with an inner primary expression
bool found = true;
while (found) {
@@ -734,7 +742,7 @@ public class Vala.Genie.Parser : CodeVisitor {
case TokenType.OP_DEC:
expr = parse_post_decrement_expression (begin, expr);
break;
-
+ case TokenType.CASCADE:
default:
found = false;
break;
@@ -804,6 +812,19 @@ public class Vala.Genie.Parser : CodeVisitor {
return expr;
}
+ Expression parse_cascade_member_access (SourceLocation begin, Expression inner) throws ParseError {
+ expect (TokenType.CASCADE);
+ string id = parse_identifier ();
+ List<DataType> type_arg_list = parse_type_argument_list (true);
+ var expr = new MemberAccess (inner, id, get_src (begin));
+ if (type_arg_list != null) {
+ foreach (DataType type_arg in type_arg_list) {
+ expr.add_type_argument (type_arg);
+ }
+ }
+ return expr;
+ }
+
Expression parse_pointer_member_access (SourceLocation begin, Expression inner) throws ParseError {
expect (TokenType.OP_PTR);
string id = parse_identifier ();
@@ -1607,6 +1628,101 @@ public class Vala.Genie.Parser : CodeVisitor {
}
Expression parse_expression () throws ParseError {
+ var expr = parse_before_cascade ();
+
+ CascadeVariable variable = null;
+ if (current () == TokenType.CASCADE) {
+ variable = new CascadeVariable (expr, get_src (get_location()));
+ var declaration = new DeclarationStatement (variable, get_src (get_location ()));
+ current_block.add_statement (declaration);
+ expr = parse_cascade_expression (expr, variable);
+ }
+
+ return parse_assignment (expr, variable);
+ }
+
+ Expression parse_assignment (Expression lhs, CascadeVariable? variable) throws ParseError {
+ var expr = lhs;
+ var begin = get_location ();
+ var operator = get_assignment_operator (current ());
+ if (operator != AssignmentOperator.NONE) {
+ next ();
+ var rhs = parse_before_cascade ();
+ expr = new Assignment (expr, rhs, operator, get_src (begin));
+ if (current () == TokenType.CASCADE) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = parse_cascade_expression (expr, variable);
+ } else if (variable != null) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = variable.get_access ();
+ }
+
+ expr = parse_assignment (expr, variable);
+ } else if (current () == TokenType.OP_GT) { // >>=
+ char* first_gt_pos = tokens[index].begin.pos;
+ next ();
+ // only accept >>= when there is no space between the two > signs
+ if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) {
+ next ();
+ var rhs = parse_before_cascade ();
+ expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin));
+ if (current () == TokenType.CASCADE) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = parse_cascade_expression (expr, variable);
+ } else if (variable != null) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = variable.get_access ();
+ }
+
+ expr = parse_assignment (expr, variable);
+ } else {
+ prev ();
+ }
+ }
+
+ return expr;
+ }
+
+ Expression parse_cascade_expression (Expression inner, CascadeVariable? variable) throws ParseError {
+ Expression prv = null;
+ Expression return_val = null;
+ Expression expr = inner;
+ if (current () == TokenType.CASCADE) {
+ expr = parse_cascade_member_access (get_location(), variable.get_access ());
+ }
+ var ma = expr as MemberAccess;
+ while (expr != prv && ma != null) {
+ prv = expr;
+
+ expr = parse_inner_primary_expression (ma);
+
+ if (current () == TokenType.CASCADE) {
+ ma = parse_cascade_member_access (get_location(), variable.get_access ()) as MemberAccess;
+ } else {
+ ma = null;
+ }
+
+ if (expr == prv || ma == null) {
+ if (get_assignment_operator (current ()) != AssignmentOperator.NONE) {
+ return_val = expr;
+ break;
+ } else {
+ return_val = variable.get_access ();
+ }
+ }
+
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ }
+
+ return return_val;
+ }
+
+ Expression parse_before_cascade () throws ParseError {
if (current () == TokenType.DEF) {
var lambda = parse_lambda_expression ();
current_expr_is_lambda = true;
@@ -1615,36 +1731,15 @@ public class Vala.Genie.Parser : CodeVisitor {
current_expr_is_lambda = false;
}
- var begin = get_location ();
Expression expr = parse_conditional_expression ();
- while (true) {
- var operator = get_assignment_operator (current ());
- if (operator != AssignmentOperator.NONE) {
- next ();
- var rhs = parse_expression ();
- expr = new Assignment (expr, rhs, operator, get_src (begin));
- } else if (current () == TokenType.OP_GT) { // >>=
- char* first_gt_pos = tokens[index].begin.pos;
- next ();
- // only accept >>= when there is no space between the two > signs
- if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) {
- next ();
- var rhs = parse_expression ();
- expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin));
- } else {
- prev ();
- break;
- }
- } else {
- break;
- }
+ if (current () != TokenType.CASCADE) {
+ expr = parse_assignment (expr, null);
}
return expr;
}
-
Statement get_for_statement_type () throws ParseError {
var begin = get_location ();
@@ -1816,6 +1911,8 @@ public class Vala.Genie.Parser : CodeVisitor {
case TokenType.OP_GT: // >>=
// member access
case TokenType.DOT:
+ // cascade member access
+ case TokenType.CASCADE:
// pointer member access
case TokenType.OP_PTR:
rollback (begin);
@@ -1881,6 +1978,7 @@ public class Vala.Genie.Parser : CodeVisitor {
var begin = get_location ();
expect (TokenType.INDENT);
var block = new Block (get_src (begin));
+ current_block = block;
parse_statements (block);
if (!accept (TokenType.DEDENT)) {
// only report error if it's not a secondary error
@@ -3860,7 +3958,7 @@ public class Vala.Genie.Parser : CodeVisitor {
expr.add_type_argument (type_arg);
}
}
- } while (accept (TokenType.DOT));
+ } while (accept (TokenType.DOT) || accept (TokenType.CASCADE));
return expr;
}
}
diff --git a/vala/valageniescanner.vala b/vala/valageniescanner.vala
index e2c5466..cb3f0cb 100644
--- a/vala/valageniescanner.vala
+++ b/vala/valageniescanner.vala
@@ -1013,6 +1013,9 @@ public class Vala.Genie.Scanner {
if (current[0] == '.' && current[1] == '.') {
type = TokenType.ELLIPSIS;
current += 2;
+ } else if (current[0] == '.') {
+ type = TokenType.CASCADE;
+ current++;
}
}
break;
diff --git a/vala/valagenietokentype.vala b/vala/valagenietokentype.vala
index 920a96a..fdb1920 100644
--- a/vala/valagenietokentype.vala
+++ b/vala/valagenietokentype.vala
@@ -44,6 +44,7 @@ public enum Vala.Genie.TokenType {
BITWISE_OR,
BREAK,
CARRET,
+ CASCADE,
CASE,
CHARACTER_LITERAL,
CLASS,
@@ -193,6 +194,7 @@ public enum Vala.Genie.TokenType {
case BITWISE_OR: return "`|'";
case BREAK: return "`break'";
case CARRET: return "`^'";
+ case CASCADE: return "`..";
case CASE: return "`case'";
case CHARACTER_LITERAL: return "character literal";
case CLASS: return "`class'";
diff --git a/vala/valaparser.vala b/vala/valaparser.vala
index c465a8e..0c6c5ea 100644
--- a/vala/valaparser.vala
+++ b/vala/valaparser.vala
@@ -37,6 +37,8 @@ public class Vala.Parser : CodeVisitor {
// number of tokens in buffer
int size;
+ Block current_block;
+
Comment comment;
const int BUFFER_SIZE = 32;
@@ -353,7 +355,7 @@ public class Vala.Parser : CodeVisitor {
void skip_symbol_name () throws ParseError {
do {
skip_identifier ();
- } while (accept (TokenType.DOT) || accept (TokenType.DOUBLE_COLON));
+ } while (accept (TokenType.CASCADE) || accept (TokenType.DOT) || accept (TokenType.DOUBLE_COLON));
}
UnresolvedSymbol parse_symbol_name () throws ParseError {
@@ -370,7 +372,7 @@ public class Vala.Parser : CodeVisitor {
continue;
}
sym = new UnresolvedSymbol (sym, name, get_src (begin));
- } while (accept (TokenType.DOT));
+ } while (accept (TokenType.CASCADE) || accept (TokenType.DOT));
return sym;
}
@@ -585,8 +587,6 @@ public class Vala.Parser : CodeVisitor {
}
Expression parse_primary_expression () throws ParseError {
- var begin = get_location ();
-
Expression expr;
switch (current ()) {
@@ -640,6 +640,13 @@ public class Vala.Parser : CodeVisitor {
break;
}
+ return parse_inner_primary_expression (expr);
+ }
+
+ Expression parse_inner_primary_expression (Expression inner) throws ParseError {
+ var expr = inner;
+ var begin = get_location ();
+
// process primary expressions that start with an inner primary expression
bool found = true;
while (found) {
@@ -662,6 +669,7 @@ public class Vala.Parser : CodeVisitor {
case TokenType.OP_DEC:
expr = parse_post_decrement_expression (begin, expr);
break;
+ case TokenType.CASCADE:
default:
found = false;
break;
@@ -747,6 +755,19 @@ public class Vala.Parser : CodeVisitor {
return expr;
}
+ Expression parse_cascade_member_access (SourceLocation begin, Expression inner) throws ParseError {
+ expect (TokenType.CASCADE);
+ string id = parse_identifier ();
+ List<DataType> type_arg_list = parse_type_argument_list (true);
+ var expr = new MemberAccess (inner, id, get_src (begin));
+ if (type_arg_list != null) {
+ foreach (DataType type_arg in type_arg_list) {
+ expr.add_type_argument (type_arg);
+ }
+ }
+ return expr;
+ }
+
Expression parse_pointer_member_access (SourceLocation begin, Expression inner) throws ParseError {
expect (TokenType.OP_PTR);
string id = parse_identifier ();
@@ -1456,35 +1477,109 @@ public class Vala.Parser : CodeVisitor {
}
Expression parse_expression () throws ParseError {
- if (is_lambda_expression ()) {
- return parse_lambda_expression ();
+ var expr = parse_before_cascade ();
+
+ CascadeVariable variable = null;
+ if (current () == TokenType.CASCADE) {
+ variable = new CascadeVariable (expr, get_src (get_location()));
+ var declaration = new DeclarationStatement (variable, get_src (get_location ()));
+ current_block.add_statement (declaration);
+ expr = parse_cascade_expression (expr, variable);
}
+ return parse_assignment (expr, variable);
+ }
+
+ Expression parse_assignment (Expression lhs, CascadeVariable? variable) throws ParseError {
+ var expr = lhs;
var begin = get_location ();
+ var operator = get_assignment_operator (current ());
+ if (operator != AssignmentOperator.NONE) {
+ next ();
+ var rhs = parse_before_cascade ();
+ expr = new Assignment (expr, rhs, operator, get_src (begin));
+ if (current () == TokenType.CASCADE) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = parse_cascade_expression (expr, variable);
+ } else if (variable != null) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = variable.get_access ();
+ }
+
+ expr = parse_assignment (expr, variable);
+ } else if (current () == TokenType.OP_GT) { // >>=
+ char* first_gt_pos = tokens[index].begin.pos;
+ next ();
+ // only accept >>= when there is no space between the two > signs
+ if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) {
+ next ();
+ var rhs = parse_before_cascade ();
+ expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin));
+ if (current () == TokenType.CASCADE) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = parse_cascade_expression (expr, variable);
+ } else if (variable != null) {
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ expr = variable.get_access ();
+ }
- Expression expr = parse_conditional_expression ();
+ expr = parse_assignment (expr, variable);
+ } else {
+ prev ();
+ }
+ }
- while (true) {
- var operator = get_assignment_operator (current ());
- if (operator != AssignmentOperator.NONE) {
- next ();
- var rhs = parse_expression ();
- expr = new Assignment (expr, rhs, operator, get_src (begin));
- } else if (current () == TokenType.OP_GT) { // >>=
- char* first_gt_pos = tokens[index].begin.pos;
- next ();
- // only accept >>= when there is no space between the two > signs
- if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) {
- next ();
- var rhs = parse_expression ();
- expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin));
- } else {
- prev ();
+ return expr;
+ }
+
+ Expression parse_cascade_expression (Expression inner, CascadeVariable? variable) throws ParseError {
+ Expression prv = null;
+ Expression return_val = null;
+ Expression expr = inner;
+ if (current () == TokenType.CASCADE) {
+ expr = parse_cascade_member_access (get_location(), variable.get_access ());
+ }
+ var ma = expr as MemberAccess;
+ while (expr != prv && ma != null) {
+ prv = expr;
+
+ expr = parse_inner_primary_expression (ma);
+
+ if (current () == TokenType.CASCADE) {
+ ma = parse_cascade_member_access (get_location(), variable.get_access ()) as MemberAccess;
+ } else {
+ ma = null;
+ }
+
+ if (expr == prv || ma == null) {
+ if (get_assignment_operator (current ()) != AssignmentOperator.NONE) {
+ return_val = expr;
break;
+ } else {
+ return_val = variable.get_access ();
}
- } else {
- break;
}
+
+ var stmt = new ExpressionStatement (expr, get_src (get_location ()));
+ current_block.add_statement (stmt);
+ }
+
+ return return_val;
+ }
+
+ Expression parse_before_cascade () throws ParseError {
+ if (is_lambda_expression ()) {
+ return parse_lambda_expression ();
+ }
+
+ Expression expr = parse_conditional_expression ();
+
+ if (current () != TokenType.CASCADE) {
+ expr = parse_assignment (expr, null);
}
return expr;
@@ -1619,6 +1714,8 @@ public class Vala.Parser : CodeVisitor {
case TokenType.OP_GT: // >>=
// member access
case TokenType.DOT:
+ // cascade member access
+ case TokenType.CASCADE:
// pointer member access
case TokenType.OP_PTR:
rollback (begin);
@@ -1730,6 +1827,7 @@ public class Vala.Parser : CodeVisitor {
var begin = get_location ();
expect (TokenType.OPEN_BRACE);
var block = new Block (get_src (begin));
+ current_block = block;
parse_statements (block);
if (!accept (TokenType.CLOSE_BRACE)) {
// only report error if it's not a secondary error
@@ -3420,6 +3518,7 @@ public class Vala.Parser : CodeVisitor {
case TokenType.SEMICOLON:
case TokenType.COMMA:
case TokenType.DOT:
+ case TokenType.CASCADE:
case TokenType.INTERR:
case TokenType.OP_EQ:
case TokenType.OP_NE:
@@ -3460,7 +3559,7 @@ public class Vala.Parser : CodeVisitor {
}
first = false;
- } while (accept (TokenType.DOT));
+ } while (accept (TokenType.DOT) || accept (TokenType.CASCADE));
return expr;
}
diff --git a/vala/valascanner.vala b/vala/valascanner.vala
index 19eb5c4..7b909f4 100644
--- a/vala/valascanner.vala
+++ b/vala/valascanner.vala
@@ -873,6 +873,9 @@ public class Vala.Scanner {
if (current[0] == '.' && current[1] == '.') {
type = TokenType.ELLIPSIS;
current += 2;
+ } else if (current[0] == '.') {
+ type = TokenType.CASCADE;
+ current++;
}
}
break;
diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala
index 91b50b9..1461020 100644
--- a/vala/valatokentype.vala
+++ b/vala/valatokentype.vala
@@ -43,6 +43,7 @@ public enum Vala.TokenType {
BREAK,
CARRET,
CASE,
+ CASCADE,
CATCH,
CHARACTER_LITERAL,
CLASS,
@@ -175,6 +176,7 @@ public enum Vala.TokenType {
case BREAK: return "`break'";
case CARRET: return "`^'";
case CASE: return "`case'";
+ case CASCADE: return "..";
case CATCH: return "`catch'";
case CHARACTER_LITERAL: return "character literal";
case CLASS: return "`class'";
--
1.9.3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment