Structured programming with expressions
For your convenience, app.ducx expression language supports all common language constructs that you already know from most imperative programming languages.
Conditions
Syntax |
if (expression) { ... } else if (expression) { ... } else { ... } switch (expression) { case constant: ... break; case constant: ... break; default: ... break; } |
You can use if statements in app.ducx expression language. The if keyword must be followed by parentheses enclosing a conditional expression, and non-optional curly braces. An if block can be followed by multiple else if blocks and an optional else block.
Example |
@orderstate = @order.orderstate; if (@orderstate == OrderState(OS_PENDING)) { @order.ProcessPendingOrder(); } else if (@orderstate == OrderState(OS_SHIPPED)) { @order.ProcessShippedOrder(); } else { throw coort.SetError(#OrderAlreadyProcessed, null); } // lists as conditional expression are evaluated true, if the list contains at least // one not null element if (["", "", "a"]) { true; } |
Note: It is not necessary that OS_PENDING is explicitly casted (e.g. @orderstate == OS_PENDING works, too).
The switch - case - default statement can be used to evaluate the switch expression and execute the appropriate case.
Example |
OrderState @orderstate; switch (@orderstate){ case OS_PENDING: @state = 1; break; case OS_SHIPPED: @state = 2; break; default: @state = 0; break; } |
Note: Enumeration items like OS_PENDING are determined by @orderstate in the switch statement.
Loops
Syntax |
for (expression) { ... } while (expression) { ... } do { ... } while (expression); |
The app.ducx expression language contains loops with both a constant and a non-constant number of iterations. The statements for, while, and do-while are supported. All loops have a mandatory block.
The break statement can be used to exit a loop.
The continue statement can be used to skip the remainder of the loop body and continue with the next iteration of the loop.
Note: continue inside a catch block does not apply to any enclosing loop.
Example |
currency totalvalue = 0; // Iteration with index (inefficient) for (integer idx = 0; idx < count(orderpositions); idx++) { Product product = positions[idx].product; totalvalue += product.unitprice * positions[idx].quantity; } currency total = 0; // Iteration with iterator (efficient) for (OrderPosition position : orderpositions) { Product product = position.product; if (product != null) { total += product.unitprice * position.quantity; } } integer stock = product.itemsinstock; integer threshold = product.productionthreshold; while (stock <= threshold) { product.ProduceItem(); stock++; } OrderState orderstate; do { order.ProcessOrder(&orderstate); } while (@orderstate != OS_COMPLETED); |
Note: Avoid iterating through lists using a for loop with an index, since this is very inefficient in large lists.
Raising an error
Syntax |
// Raising a custom error throw coort.SetError(errormessage, argument); // Rethrowing an exception throw errorcode; |
Using the throw keyword, the app.ducx expression language allows you to raise an error:
- to rethrow an exception in an error handler (e.g. in a catch block), you just need to specify the error code of the exception after the throw keyword.
- to raise a custom error, you need to issue a call to the SetError function of the Fabasoft Folio Runtime.
- instead of using the SetError function you can also use the COOSYSTEM@1.1:RaiseError action, which allows you to pass in additional arguments for filling a formatting string. The throw keyword is not required when using COOSYSTEM@1.1:RaiseError.
Example |
// Raising a custom error using a "throw" statement throw coort.SetError(#InvalidInvoice, null); // Raising a custom error using the COOSYSTEM@1.1:RaiseError action, assuming that the // error message APPDUCXSAMPLE@200.200:InvalidInvoice contains a formatting pattern like // "Invoice '%s' (no. %d) is not valid!" cooobj.RaiseError(#InvalidInvoice, cooobj.objname, cooobj.invoicenumber); |
Error handling
Syntax |
try { ... } catch (condition) { ... } finally { ... } |
app.ducx expression language supports try-catch-finally statement constructs for handling exceptions. A try block must be followed by at least one catch block, followed by an optional finally block.
If an exception occurs when processing the try block, the Fabasoft Folio Kernel tries to locate a catch block with a condition matching the error message object of the exception.
A catch block can have three distinct types of conditions:
- An error message object can be specified to handle only matching exceptions.
- A variable can be specified. If an exception occurs, the error code is stored in the specified variable, and the corresponding catch block is executed. The variable can be used for reading the error code and the corresponding error message, using coort.GetErrorText(errorcode) or coort.GetErrorMessage(errorcode)
- The ... operator can be specified to handle all exceptions without taking into account the error code.
To continue the execution directly after the statement raising the error the keyword continue can be used inside the catch block.
The optional finally block is executed after the try and catch blocks have been processed, whether an exception occurred or not.
Example |
try { User usr = #User.ObjectCreate(,,newaddress); } catch(#COOERR_INVADDR) { /* * handle an invalid object address format */ } catch(@ex) { /* * handle any other error raised during object creation */ string errortext = coort.GetErrorText(@ex); ErrorMessage errorobject = coort.GetErrorMessage(@ex); } finally { ... } |
Creating new transactions or opening a transaction scope
Similar to the try statement for error handling it is possible to execute a block using a separate transaction context:
Example |
try new transaction { // The statements in this block are executed in a new transaction context. // This new transaction context is also available in the cootx built-in variable. Invoice @invoice = #Invoice.ObjectCreate(); @invoice.APPDUCXSAMPLE_200_300_InitializeInvoice(); } // After the try block an implicit Commit() or Abort() is executed on the new // transaction context created for the try block. // If the code in the try block throws an exception, Abort() is called, else // Commit(). // If the implicit Commit() itself throws an exception this can be handeled by // the catch block below. catch (...) { // Here you can catch exceptions that occurred during the try block or during the // implicit Commit() after the try block. // This code is executed in the original transaction context so the // changes made during the try block are not available any more. } finally { // Here you can perform some additional cleanup. // This code is executed in the original transaction context. } |
If the new keyword is omitted, a transaction scope is opened. A transaction scope is a sub transaction of the current transaction. When a transaction scope is commited, the changes of that scope are propagated to the surrounding transaction. These changes are only persisted if the surrounding transaction context is commited.
Example |
try transaction { // The statements in this block are executed in a new transaction scope. // This new transaction scope is also available in the cootx built-in variable. Invoice @invoice = #Invoice.ObjectCreate(); @invoice.APPDUCXSAMPLE_200_300_InitializeInvoice(); } // After the try block an implicit Commit() or Abort() is executed on the new // transaction scope created for the try block. // If the code in the try block throws an exception, Abort() is called, else // Commit(). // Afterwards, the transaction scope is closed. // If the implicit Commit() itself throws an exception this can be handeled by // the catch block below. catch (...) { // Here you can catch exceptions that occurred during the try block or during the // implicit Commit() after the try block. // This code is executed in the original transaction scope so the // changes made during the try block are not available any more. } finally { // Here you can perform some additional cleanup. // This code is executed in the origina transaction scope. } |
Returning values
The return statement can be used to stop the evaluation of an expression at any time. Each expression has a return value, which is calculated by the expression following the return keyword.
Example |
if (@order != null) { return @order; } |
Directives
A directive is a special statement that does not influence the semantic of the expression.
Syntax |
%%NAME(parameters); |
%%TRACE
The %%TRACE directive can be used to conditionally write trace messages to the Fabasoft app.ducx Tracer (see chapter “Tracing in Fabasoft app.ducx projects”).
Syntax |
%%TRACE(message); %%TRACE(value); %%TRACE(message, value); |
Example |
%%TRACE("Hello World!"); %%TRACE(cooobj); %%TRACE("Current Object", cooobj); %%TRACE(cooobj.objname + " locked?", cooobj.objlock.objlocked); |
%%FAIL
The %%FAIL directive can be used to generate a failure. The message is written to the Fabasoft app.ducx Tracer and an error (EXPRERR_FAIL) is raised.
Note: Like the %%TRACE directive, the %%FAIL directive is only evaluated if trace mode is activated for your software component (see chapter “Tracing in Fabasoft app.ducx projects”).
Syntax |
%%FAIL; %%FAIL(message); |
Example |
%%FAIL; %%FAIL("Unexpected!"); |
%%ASSERT
The %%ASSERT directive can be used to check conditions. In case the condition returns false, a message is written to the Fabasoft app.ducx Tracer and an error (EXPRERR_ASSERT) is raised.
Note: Like the %%TRACE directive, the %%ASSERT directive is only evaluated if trace mode is activated for your software component (see chapter “Tracing in Fabasoft app.ducx projects”).
Syntax |
%%ASSERT(condition); %%ASSERT(message, condition); %%ASSERT(message, expectedvalue, actualvalue); |
Example |
%%ASSERT(cooobj.objlock.objlocked); %%ASSERT("'cooobj' should not be locked.", cooobj.objlock.objlocked); @expect = "Test"; @actual = cooobj.objname; %%ASSERT(@expected != @actual); %%ASSERT("Expecting " + @expect + ", but actual value is '" + @actual + "'.", @expect, @actual); |
%%DEBUGGER
The %%DEBUGGER directive can be used to set a breakpoint in a Fabasoft app.ducx Expression.
%%LOG
The %%LOG directive can be used to log messages to Fabasoft app.telemetry. app.telemetry provides the log level LOG, IPC, NORMAL, DETAIL, and DEBUG.
Syntax |
%%LOG(message); %%LOG(level, message); |
Example |
%%LOG("Object created by " + cooobj.objcreatedby.objname); // Detail level %%LOG("DEBUG", "Object created by " + cooobj.objcreatedby.objname); // Debug level |