Commit 08c26be7 authored by Jürg Billeter's avatar Jürg Billeter Committed by Jürg Billeter

implement simple exception handling for expression and declaration

2007-07-12  Juerg Billeter  <j@bitron.ch>

	* vala/parser.y, vala/valacodenode.vala, vala/valaenum.vala,
	  vala/valaexpression.vala, vala/valamethod.vala,
	  vala/valasemanticanalyzer.vala, vala/valatrystatement.vala,
	  ccode/valaccodegotostatement.vala, ccode/valaccodelabel.vala:
	* gobject/valacodegenerator.vala,
	  gobject/valacodegeneratorinvocationexpression.vala,
	  gobject/valacodegeneratormethod.vala: implement simple exception
	  handling for expression and declaration statements
	* tests/test-033.vala, tests/test-033.out: test exception handling
	* README, ccode/Makefile.am, tests/Makefile.am: update

svn path=/trunk/; revision=348
parent 85a9aab7
2007-07-12 Jürg Billeter <j@bitron.ch>
* vala/parser.y, vala/valacodenode.vala, vala/valaenum.vala,
vala/valaexpression.vala, vala/valamethod.vala,
vala/valasemanticanalyzer.vala, vala/valatrystatement.vala,
ccode/valaccodegotostatement.vala, ccode/valaccodelabel.vala:
* gobject/valacodegenerator.vala,
gobject/valacodegeneratorinvocationexpression.vala,
gobject/valacodegeneratormethod.vala: implement simple exception
handling for expression and declaration statements
* tests/test-033.vala, tests/test-033.out: test exception handling
* README, ccode/Makefile.am, tests/Makefile.am: update
2007-07-11 Jürg Billeter <j@bitron.ch>
* ccode/valaccodefunctioncall.vala, gobject/valacodegenerator.vala,
......
......@@ -21,7 +21,7 @@ type system. Vala supports modern language features as the following:
* Generics
* Non-null types
* Assisted memory management
* Exception handling [PLANNED]
* Exception handling
Vala is designed to allow access to existing C libraries, especially
GObject-based libraries, without the need for runtime bindings. Each to
......
......@@ -87,6 +87,9 @@ libvalaccode_la_SOURCES = \
valaccodefunctiondeclarator.c \
valaccodefunctiondeclarator.h \
valaccodefunctiondeclarator.vala \
valaccodegotostatement.c \
valaccodegotostatement.h \
valaccodegotostatement.vala \
valaccodeidentifier.c \
valaccodeidentifier.h \
valaccodeidentifier.vala \
......@@ -99,6 +102,9 @@ libvalaccode_la_SOURCES = \
valaccodeinitializerlist.c \
valaccodeinitializerlist.h \
valaccodeinitializerlist.vala \
valaccodelabel.c \
valaccodelabel.h \
valaccodelabel.vala \
valaccodelinedirective.c \
valaccodelinedirective.h \
valaccodelinedirective.vala \
......@@ -182,10 +188,12 @@ ccodeinclude_HEADERS = \
valaccodefunction.h \
valaccodefunctioncall.h \
valaccodefunctiondeclarator.h \
valaccodegotostatement.h \
valaccodeidentifier.h \
valaccodeifstatement.h \
valaccodeincludedirective.h \
valaccodeinitializerlist.h \
valaccodelabel.h \
valaccodelinedirective.h \
valaccodemacroreplacement.h \
valaccodememberaccess.h \
......
/* valaccodegotostatement.vala
*
* Copyright (C) 2007 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 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 GLib;
/**
* Represents a goto statement in the C code.
*/
public class Vala.CCodeGotoStatement : CCodeStatement {
/**
* The name of the target label.
*/
public string! name { get; set construct; }
public CCodeGotoStatement (construct string! name) {
}
public override void write (CCodeWriter! writer) {
writer.write_indent ();
writer.write_string ("goto ");
writer.write_string (name);
writer.write_string (";");
writer.write_newline ();
}
}
/* valaccodelabel.vala
*
* Copyright (C) 2007 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 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 GLib;
/**
* Represents a label declaration in the C code.
*/
public class Vala.CCodeLabel : CCodeStatement {
/**
* The name of this label.
*/
public string! name { get; set construct; }
public CCodeLabel (construct string! name) {
}
public override void write (CCodeWriter! writer) {
writer.write_indent ();
writer.write_string (name);
writer.write_string (":");
writer.write_newline ();
}
}
......@@ -40,6 +40,7 @@ public class Vala.CodeGenerator : CodeVisitor {
Class current_class;
Method current_method;
TypeReference current_return_type;
TryStatement current_try;
CCodeFragment header_begin;
CCodeFragment header_type_declaration;
......@@ -75,7 +76,9 @@ public class Vala.CodeGenerator : CodeVisitor {
HashTable<string,bool> c_keywords;
private int next_temp_var_id = 0;
private int next_try_id = 0;
private bool in_creation_method = false;
private bool current_method_inner_error = false;
TypeReference bool_type;
TypeReference char_type;
......@@ -271,6 +274,26 @@ public class Vala.CodeGenerator : CodeVisitor {
header_type_definition.append (cenum);
en.accept_children (this);
if (en.error_domain) {
string quark_fun_name = en.get_lower_case_cprefix () + "quark";
var error_domain_define = new CCodeMacroReplacement (en.get_upper_case_cname (), quark_fun_name + " ()");
header_type_definition.append (error_domain_define);
var cquark_fun = new CCodeFunction (quark_fun_name, "GQuark");
var cquark_block = new CCodeBlock ();
var cquark_call = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
cquark_call.add_argument (new CCodeConstant ("\"" + en.get_lower_case_cname () + "-quark\""));
cquark_block.add_statement (new CCodeReturnStatement (cquark_call));
header_type_member_declaration.append (cquark_fun.copy ());
cquark_fun.block = cquark_block;
source_type_member_definition.append (cquark_fun);
}
}
public override void visit_enum_value (EnumValue! ev) {
......@@ -717,7 +740,11 @@ public class Vala.CodeGenerator : CodeVisitor {
cdecl.add_declarator ((CCodeVariableDeclarator) decl.ccodenode);
cfrag.append (cdecl);
if (decl.initializer != null && decl.initializer.can_fail) {
add_simple_check (decl.initializer, cfrag);
}
/* try to initialize uninitialized variables */
if (decl.initializer == null && decl.type_reference.data_type is Struct) {
if (decl.type_reference.data_type.is_reference_type ()) {
......@@ -990,9 +1017,82 @@ public class Vala.CodeGenerator : CodeVisitor {
}
}
private void add_simple_check (CodeNode! node, CCodeFragment! cfrag) {
var cprint_frag = new CCodeFragment ();
var ccritical = new CCodeFunctionCall (new CCodeIdentifier ("g_critical"));
ccritical.add_argument (new CCodeConstant ("\"file %s: line %d: uncaught error: %s\""));
ccritical.add_argument (new CCodeConstant ("__FILE__"));
ccritical.add_argument (new CCodeConstant ("__LINE__"));
ccritical.add_argument (new CCodeMemberAccess.pointer (new CCodeIdentifier ("inner_error"), "message"));
cprint_frag.append (new CCodeExpressionStatement (ccritical));
if (current_return_type != null && current_return_type.data_type != null) {
cprint_frag.append (new CCodeReturnStatement (default_value_for_type (current_return_type.data_type)));
} else {
cprint_frag.append (new CCodeReturnStatement ());
}
if (current_try != null) {
// surrounding try found
// TODO might be the wrong one when using nested try statements
var cerror_block = new CCodeBlock ();
foreach (CatchClause clause in current_try.get_catch_clauses ()) {
// go to catch clause if error domain matches
var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.EQUALITY, new CCodeMemberAccess.pointer (new CCodeIdentifier ("inner_error"), "domain"), new CCodeIdentifier (clause.type_reference.data_type.get_upper_case_cname ()));
var cgoto_block = new CCodeBlock ();
cgoto_block.add_statement (new CCodeGotoStatement ("__catch%d_%s".printf (next_try_id, clause.type_reference.data_type.get_lower_case_cname ())));
cerror_block.add_statement (new CCodeIfStatement (ccond, cgoto_block));
}
// print critical message and return if no catch clause matches
cerror_block.add_statement (cprint_frag);
// check error domain if expression failed
var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.INEQUALITY, new CCodeIdentifier ("inner_error"), new CCodeConstant ("NULL"));
cfrag.append (new CCodeIfStatement (ccond, cerror_block));
} else if (current_method != null && current_method.get_error_domains ().length () > 0) {
// current method can fail, propagate error
// TODO ensure one of the error domains matches
var cpropagate = new CCodeFunctionCall (new CCodeIdentifier ("g_propagate_error"));
cpropagate.add_argument (new CCodeIdentifier ("error"));
cpropagate.add_argument (new CCodeIdentifier ("inner_error"));
var cerror_block = new CCodeBlock ();
cerror_block.add_statement (new CCodeExpressionStatement (cpropagate));
if (current_return_type != null && current_return_type.data_type != null) {
cerror_block.add_statement (new CCodeReturnStatement (default_value_for_type (current_return_type.data_type)));
} else {
cerror_block.add_statement (new CCodeReturnStatement ());
}
var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.INEQUALITY, new CCodeIdentifier ("inner_error"), new CCodeConstant ("NULL"));
cfrag.append (new CCodeIfStatement (ccond, cerror_block));
} else {
Report.warning (node.source_reference, "unhandled error");
cfrag.append (cprint_frag);
}
}
public override void visit_expression_statement (ExpressionStatement! stmt) {
stmt.ccodenode = new CCodeExpressionStatement ((CCodeExpression) stmt.expression.ccodenode);
if (stmt.tree_can_fail && stmt.expression.can_fail) {
// simple case, no node breakdown necessary
var cfrag = new CCodeFragment ();
cfrag.append (stmt.ccodenode);
add_simple_check (stmt.expression, cfrag);
stmt.ccodenode = cfrag;
}
/* free temporary objects */
if (!memory_management) {
temp_vars = null;
......@@ -1443,7 +1543,65 @@ public class Vala.CodeGenerator : CodeVisitor {
}
}
}
public override void visit_end_throw_statement (ThrowStatement! stmt) {
var cfrag = new CCodeFragment ();
cfrag.append (new CCodeExpressionStatement ((CCodeExpression) stmt.error_expression.ccodenode));
if (current_return_type != null && current_return_type.data_type != null) {
cfrag.append (new CCodeReturnStatement (default_value_for_type (current_return_type.data_type)));
} else {
cfrag.append (new CCodeReturnStatement ());
}
stmt.ccodenode = cfrag;
}
public override void visit_begin_try_statement (TryStatement! stmt) {
current_try = stmt;
}
public override void visit_end_try_statement (TryStatement! stmt) {
current_try = null;
var cfrag = new CCodeFragment ();
cfrag.append (stmt.body.ccodenode);
cfrag.append (new CCodeGotoStatement ("__finally%d".printf (next_try_id)));
foreach (CatchClause clause in stmt.get_catch_clauses ()) {
cfrag.append (clause.ccodenode);
}
cfrag.append (new CCodeLabel ("__finally%d".printf (next_try_id)));
if (stmt.finally_body != null) {
cfrag.append (stmt.finally_body.ccodenode);
}
stmt.ccodenode = cfrag;
next_try_id++;
}
public override void visit_end_catch_clause (CatchClause! clause) {
var cfrag = new CCodeFragment ();
cfrag.append (new CCodeLabel ("__catch%d_%s".printf (next_try_id, clause.type_reference.data_type.get_lower_case_cname ())));
var cblock = new CCodeBlock ();
var cdecl = new CCodeDeclaration ("GError *");
cdecl.add_declarator (new CCodeVariableDeclarator.with_initializer (clause.variable_name, new CCodeIdentifier ("inner_error")));
cblock.add_statement (cdecl);
cblock.add_statement (new CCodeExpressionStatement (new CCodeAssignment (new CCodeIdentifier ("inner_error"), new CCodeConstant ("NULL"))));
cblock.add_statement (clause.body.ccodenode);
cfrag.append (cblock);
clause.ccodenode = cfrag;
}
private string get_symbol_lock_name (Symbol! sym) {
return "__lock_%s".printf (sym.name);
}
......@@ -1881,7 +2039,7 @@ public class Vala.CodeGenerator : CodeVisitor {
expr.ccodenode = ccall;
}
} else {
} else if (expr.symbol_reference.node is Method) {
// use creation method
var m = (Method) expr.symbol_reference.node;
var params = m.get_parameters ();
......@@ -1959,6 +2117,23 @@ public class Vala.CodeGenerator : CodeVisitor {
}
expr.ccodenode = ccall;
} else if (expr.symbol_reference.node is EnumValue) {
// error code
var ev = (EnumValue) expr.symbol_reference.node;
var en = (Enum) ev.symbol.parent_symbol.node;
var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_set_error"));
ccall.add_argument (new CCodeIdentifier ("error"));
ccall.add_argument (new CCodeIdentifier (en.get_upper_case_cname ()));
ccall.add_argument (new CCodeIdentifier (ev.get_cname ()));
foreach (Expression arg in expr.get_argument_list ()) {
ccall.add_argument ((CCodeExpression) arg.ccodenode);
}
expr.ccodenode = ccall;
} else {
assert (false);
}
visit_expression (expr);
......
......@@ -195,6 +195,12 @@ public class Vala.CodeGenerator {
}
}
if (expr.can_fail) {
// method can fail
current_method_inner_error = true;
ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("inner_error")));
}
if (m != null && m.instance && m.instance_last) {
ccall.add_argument (instance);
} else if (ellipsis) {
......
......@@ -27,9 +27,11 @@ public class Vala.CodeGenerator {
public override void visit_method (Method! m) {
Method old_method = current_method;
TypeReference old_return_type = current_return_type;
bool old_method_inner_error = current_method_inner_error;
current_symbol = m.symbol;
current_method = m;
current_return_type = m.return_type;
current_method_inner_error = false;
if (m is CreationMethod) {
in_creation_method = true;
......@@ -45,9 +47,12 @@ public class Vala.CodeGenerator {
in_creation_method = false;
}
bool inner_error = current_method_inner_error;
current_symbol = current_symbol.parent_symbol;
current_method = current_method;
current_return_type = old_return_type;
current_method_inner_error = old_method_inner_error;
if (current_type_symbol != null && current_type_symbol.node is Interface) {
var iface = (Interface) current_type_symbol.node;
......@@ -143,6 +148,14 @@ public class Vala.CodeGenerator {
function.add_parameter (instance_param);
}
if (m.get_error_domains ().length () > 0) {
var cparam = new CCodeFormalParameter ("error", "GError**");
function.add_parameter (cparam);
if (vdeclarator != null) {
vdeclarator.add_parameter (cparam);
}
}
/* real function declaration and definition not needed
* for abstract methods */
if (!m.is_abstract) {
......@@ -189,6 +202,15 @@ public class Vala.CodeGenerator {
}
}
if (inner_error) {
/* always separate error parameter and inner_error local variable
* as error may be set to NULL but we're always interested in inner errors
*/
var cdecl = new CCodeDeclaration ("GError *");
cdecl.add_declarator (new CCodeVariableDeclarator.with_initializer ("inner_error", new CCodeConstant ("NULL")));
cinit.append (cdecl);
}
if (m.source_reference != null && m.source_reference.comment != null) {
source_type_member_definition.append (new CCodeComment (m.source_reference.comment));
}
......@@ -384,11 +406,10 @@ public class Vala.CodeGenerator {
ccheck.call = new CCodeIdentifier ("g_return_if_fail");
} else {
ccheck.call = new CCodeIdentifier ("g_return_val_if_fail");
if (ret_type.is_reference_type () || ret_type is Pointer) {
ccheck.add_argument (new CCodeConstant ("NULL"));
} else if (ret_type.get_default_value () != null) {
ccheck.add_argument (new CCodeConstant (ret_type.get_default_value ()));
var cdefault = default_value_for_type (ret_type);
if (cdefault != null) {
ccheck.add_argument (cdefault);
} else {
Report.warning (method_node.source_reference, "not supported return type for runtime type checks");
return new CCodeExpressionStatement (new CCodeConstant ("0"));
......@@ -398,6 +419,15 @@ public class Vala.CodeGenerator {
return new CCodeExpressionStatement (ccheck);
}
private CCodeExpression default_value_for_type (DataType! type) {
if (type.is_reference_type () || type is Pointer) {
return new CCodeConstant ("NULL");
} else if (type.get_default_value () != null) {
return new CCodeConstant (type.get_default_value ());
}
return null;
}
private DataType find_parent_type (CodeNode node) {
var sym = node.symbol;
while (sym != null) {
......
......@@ -35,6 +35,7 @@ TESTS = \
test-030.vala \
test-031.vala \
test-032.vala \
test-033.vala \
$(NULL)
EXTRA_DIST = \
......@@ -70,6 +71,8 @@ EXTRA_DIST = \
test-029.vala \
test-030.vala \
test-031.vala \
test-032.vala \
test-033.vala \
test-001.out \
test-002.out \
test-003.out \
......@@ -102,4 +105,5 @@ EXTRA_DIST = \
test-030.out \
test-031.out \
test-032.out \
test-033.out \
$(NULL)
Exception Test: 1 2 3 4 5 6 7 8 9 10 11
using GLib;
[ErrorDomain]
enum Maman.BarError {
FOO,
BAR
}
class Maman.Bar {
public void foo () throws BarError {
stdout.printf (" 6");
throw new BarError.FOO (" 8");
stdout.printf (" BAD");
}
public int bad () throws BarError {
stdout.printf (" 5");
foo ();
stdout.printf (" BAD");
}
public void good () throws BarError {
stdout.printf (" 4");
}
public void run () {
stdout.printf (" 2");
try {
stdout.printf (" 3");
good ();
int i = bad ();
good ();
stdout.printf (" BAD");
} catch (BarError e) {
stdout.printf (" 7");
stdout.printf ("%s", e.message);
stdout.printf (" 9");
}
stdout.printf (" 10");
}
static int main (string[] args) {
stdout.printf ("Exception Test: 1");
var bar = new Bar ();
bar.run ();
stdout.printf (" 11\n");
return 0;
}
}
......@@ -2629,6 +2629,14 @@ method_header
g_list_free ($8);
}
for (l = $10; l != NULL; l = l->next) {
vala_method_add_error_domain ($$, l->data);
g_object_unref (l->data);
}
if ($10 != NULL) {
g_list_free ($10);
}
g_object_unref ($5);
g_free ($6);
}
......
......@@ -73,6 +73,11 @@ public abstract class Vala.CodeNode {
*/
public bool error { get; set; }
/**
* Specifies that this node or a child node may throw an exception.
*/
public bool tree_can_fail { get; set; }
/**
* Visits this code node with the specified CodeVisitor.
*
......
......@@ -26,6 +26,11 @@ using GLib;
* Represents an enum declaration in the source code.
*/
public class Vala.Enum : DataType {
/**
* Specifies whether this enum represents an error domain.
*/
public bool error_domain { get; set; }
private List<EnumValue> values;
private List<Method> methods;
private string cname;
......@@ -169,6 +174,8 @@ public class Vala.Enum : DataType {
foreach (Attribute a in attributes) {
if (a.name == "CCode") {
process_ccode_attribute (a);
} else if (a.name == "ErrorDomain") {
error_domain = true;
}
}
}
......
......@@ -65,7 +65,12 @@ public abstract class Vala.Expression : CodeNode {
* Specifies that this expression successfully transfers ownership.
*/
public bool ref_sink { get; set; }
/**
* Specifies that this expression may throw an exception.
*/
public bool can_fail { get; set; }
/**
* Contains all temporary variables this expression requires for
* execution.
......
......@@ -140,6 +140,7 @@ public class Vala.Method : Member, Invokable {
private List<FormalParameter> parameters;
private string cname;
private bool _no_array_length;
private List<TypeReference> error_domains;
/**
* Creates a new method.
......@@ -154,7 +155,7 @@ public class Vala.Method : Member, Invokable {
return_type = _return_type;
source_reference = source;
}
/**
* Appends parameter to this method.
*
......@@ -188,11 +189,15 @@ public class Vala.Method : Member, Invokable {
if (return_type != null) {
return_type.accept (visitor);
}
foreach (FormalParameter param in parameters) {
param.accept (visitor);
}
foreach (TypeReference error_domain in error_domains) {
error_domain.accept (visitor);
}
if (body != null) {
body.accept (visitor);
}
......@@ -319,4 +324,22 @@ public class Vala.Method : Member, Invokable {
return true;
}
/**
* Adds an error domain to this method.
*
* @param error_domain an error domain
*/
public void add_error_domain (TypeReference! error_domain) {
error_domains.append (error_domain);
}
/**
* Returns a copy of the list of error domains of this method.
*
* @return list of error domains
*/
public List<weak TypeReference> get_error_domains () {
return error_domains.copy ();
}
}
......@@ -52,6 +52,7 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
DataType initially_unowned_type;
DataType glist_type;
DataType gslist_type;
DataType gerror_type;
private int next_lambda_id = 0;
......@@ -97,6 +98,8 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
glist_type = (DataType) glib_ns.lookup ("List").node;
gslist_type = (DataType) glib_ns.lookup ("SList").node;
gerror_type = (DataType) glib_ns.lookup ("Error").node;
}
current_symbol = root_symbol;
......@@ -788,8 +791,9 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
if (stmt.expression.static_type != null &&
stmt.expression.static_type.transfers_ownership) {
Report.warning (stmt.source_reference, "Short-living reference");
return;
}
stmt.tree_can_fail = stmt.expression.tree_can_fail;
}
public override void visit_if_statement (IfStatement! stmt) {
......@@ -886,6 +890,19 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
}
}
public override void visit_begin_catch_clause (CatchClause! clause) {
if (clause.type_reference.data_type != null) {
current_source_file.add_symbol_dependency (clause.type_reference.data_type.symbol, SourceFileDependencyType.SOURCE);
}
clause.variable_declarator = new VariableDeclarator (clause.variable_name);
clause.variable_declarator.type_reference = new TypeReference ();
clause.variable_declarator.type_reference.data_type = gerror_type;