Skip to content

Commit

Permalink
feat: give type casts the lowest precedence (#1157)
Browse files Browse the repository at this point in the history
Closes #1150

### Summary of Changes

Type casts now have the lowest precedence. In order to use them inside
other expressions, they **must be enclosed in parentheses**.
  • Loading branch information
lars-reimann authored May 5, 2024
1 parent 11c81b3 commit 7549fa1
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 41 deletions.
12 changes: 6 additions & 6 deletions docs/api/safeds/data/tabular/containers/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ pipeline example {
* pipeline example {
* val table = Table({"a": [1, 2], "b": [3, 4]});
* val filteredTable = table.filterRows((row) ->
* row.getValue("a") as Int > 1
* (row.getValue("a") as Int) > 1
* );
* // Table({"a": [2], "b": [4]})
* }
Expand All @@ -440,7 +440,7 @@ pipeline example {
* pipeline example {
* val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]});
* val tablesByKey = table.groupRows((row) ->
* row.getValue("a") as Int <= 2
* (row.getValue("a") as Int) <= 2
* );
* // {
* // true: Table({"a": [1, 2], "b": [4, 5]}),
Expand Down Expand Up @@ -838,7 +838,7 @@ pipeline example {
* "price": [ 100, 2, 4],
* });
* val discountedPrices = prices.transformColumn("price", (row) ->
* row.getValue("price") as Int * 0.5
* (row.getValue("price") as Int) * 0.5
* );
* // Table({
* // "product": ["apple", "banana", "cherry"],
Expand Down Expand Up @@ -1428,7 +1428,7 @@ The original table is not modified.
pipeline example {
val table = Table({"a": [1, 2], "b": [3, 4]});
val filteredTable = table.filterRows((row) ->
row.getValue("a") as Int > 1
(row.getValue("a") as Int) > 1
);
// Table({"a": [2], "b": [4]})
}
Expand Down Expand Up @@ -1579,7 +1579,7 @@ The original table is not modified.
pipeline example {
val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]});
val tablesByKey = table.groupRows((row) ->
row.getValue("a") as Int <= 2
(row.getValue("a") as Int) <= 2
);
// {
// true: Table({"a": [1, 2], "b": [4, 5]}),
Expand Down Expand Up @@ -2727,7 +2727,7 @@ pipeline example {
"price": [ 100, 2, 4],
});
val discountedPrices = prices.transformColumn("price", (row) ->
row.getValue("price") as Int * 0.5
(row.getValue("price") as Int) * 0.5
);
// Table({
// "product": ["apple", "banana", "cherry"],
Expand Down
3 changes: 1 addition & 2 deletions docs/pipeline-language/expressions/precedence.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ We all know that `#!sds 2 + 3 * 7` is `#!sds 23` and not `#!sds 35`. The reason
- `#!sds 1` ([integer literals][int-literals]), `#!sds 1.0` ([float literals][float-literals]), `#!sds "a"` ([string literals][string-literals]), `#!sds true`/`false` ([boolean literals][boolean-literals]), `#!sds null` ([null literal][null-literal]), `#!sds someName` ([references][references]), `#!sds "age: {{ age }}"` ([template strings][template-strings])
- `#!sds ()` ([calls][calls]), `#!sds ?()` ([null-safe calls][null-safe-calls]), `#!sds .` ([member accesses][member-accesses]), `#!sds ?.` ([null-safe member accesses][null-safe-member-accesses]), `#!sds []` ([indexed accesses][indexed-accesses]), `#!sds ?[]` ([null-safe indexed accesses][null-safe-indexed-accesses])
- `#!sds -` (unary, [arithmetic negations][operations-on-numbers])
- `#!sds as` ([type casts][type-casts])
- `#!sds ?:` ([Elvis operators][elvis-operator])
- `#!sds *`, `#!sds /` ([multiplicative operators][operations-on-numbers])
- `#!sds +`, `#!sds -` (binary, [additive operators][operations-on-numbers])
Expand All @@ -16,7 +15,7 @@ We all know that `#!sds 2 + 3 * 7` is `#!sds 23` and not `#!sds 35`. The reason
- `#!sds not` ([logical negations][logical-operations])
- `#!sds and` ([conjunctions][logical-operations])
- `#!sds or` ([disjunctions][logical-operations])
- `#!sds () -> 1` ([expression lambdas][expression-lambdas]), `#!sds () {}` ([block lambdas][block-lambdas])
- `#!sds () -> 1` ([expression lambdas][expression-lambdas]), `#!sds () {}` ([block lambdas][block-lambdas]), `#!sds as` ([type casts][type-casts])
- **LOWER PRECEDENCE**

If the default precedence of operators is not sufficient, parentheses can be used to force a part of an expression to be evaluated first.
Expand Down
25 changes: 18 additions & 7 deletions docs/pipeline-language/expressions/type-casts.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
## Type Casts
# Type Casts

The compiler can _infer_ the [type][types] of an expression in almost all cases. However, sometimes its [type][types]
has to be specified explicitly. This is called a _type cast_. Here is an example:
The compiler can _infer_ the [type][types] of an expression in almost all cases. However, sometimes its type must be
specified explicitly. This is called a _type cast_. Here is an example:

```sds
dataset.getColumn("age") as Column<Int>
table.getColumn("age") as Column<Int>
```

A type cast is written as follows:

- The expression to cast.
- The keyword `#!sds as`.
- The [type][types] to cast to.
- The type to cast to.

Type casts are only allowed if the type of the expression is unknown. They cannot be used to override the inferred type
of an expression.
Afterward, the compiler will treat the expression as if it had the specified type. If the expression's actual type is
not compatible with the specified type, the compiler will raise an error.

!!! warning "Precedence"
Type casts have the lowest precedence of all operators. If you want to use a type cast in an expression, you must
enclose it in parentheses:

```sds
(row.getValue("age") as Int) < 18
```

This is necessary, because the less than operator (`<`) looks the same as the opening angle bracket of a type
argument list (`Column<Int>`). We could remove this ambiguity by using different syntax for the less than operator
or for type argument lists, but both are established conventions in other languages.

[types]: ../types.md
44 changes: 22 additions & 22 deletions packages/safe-ds-lang/src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ SdsExpressionStatement returns SdsExpressionStatement:
interface SdsExpression extends SdsObject {}

SdsExpression returns SdsExpression:
SdsLambda | SdsOrExpression
SdsLambda | SdsTypeCast
;

interface SdsLambda extends SdsCallable, SdsExpression {}
Expand Down Expand Up @@ -581,17 +581,26 @@ SdsBlockLambdaAssignee returns SdsAssignee:
| {SdsBlockLambdaResult} 'yield' name=ID
;

interface SdsTypeCast extends SdsExpression {
expression: SdsExpression
^type: SdsType
}

SdsTypeCast returns SdsExpression:
SdsOrExpression
(
{SdsTypeCast.expression=current}
'as'
^type=SdsType
)?
;

interface SdsInfixOperation extends SdsExpression {
leftOperand: SdsExpression
operator: string
rightOperand: SdsExpression
}

interface SdsPrefixOperation extends SdsExpression {
operand: SdsExpression
operator: string
}

SdsOrExpression returns SdsExpression:
SdsAndExpression
(
Expand All @@ -610,6 +619,11 @@ SdsAndExpression returns SdsExpression:
)*
;

interface SdsPrefixOperation extends SdsExpression {
operand: SdsExpression
operator: string
}

SdsNotExpression returns SdsExpression:
{SdsPrefixOperation} operator='not' operand=SdsNotExpression
| SdsEqualityExpression
Expand Down Expand Up @@ -668,25 +682,11 @@ SdsMultiplicativeOperator returns string:
;

SdsElvisExpression returns SdsExpression:
SdsTypeCast
SdsUnaryOperation
(
{SdsInfixOperation.leftOperand=current}
operator='?:'
rightOperand=SdsTypeCast
)*
;

interface SdsTypeCast extends SdsExpression {
expression: SdsExpression
^type: SdsType
}

SdsTypeCast returns SdsExpression:
SdsUnaryOperation
(
{SdsTypeCast.expression=current}
'as'
^type=SdsType
rightOperand=SdsUnaryOperation
)*
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ class Table(
* pipeline example {
* val table = Table({"a": [1, 2], "b": [3, 4]});
* val filteredTable = table.filterRows((row) ->
* row.getValue("a") as Int > 1
* (row.getValue("a") as Int) > 1
* );
* // Table({"a": [2], "b": [4]})
* }
Expand All @@ -432,7 +432,7 @@ class Table(
* pipeline example {
* val table = Table({"a": [1, 2, 3], "b": [4, 5, 6]});
* val tablesByKey = table.groupRows((row) ->
* row.getValue("a") as Int <= 2
* (row.getValue("a") as Int) <= 2
* );
* // {
* // true: Table({"a": [1, 2], "b": [4, 5]}),
Expand Down Expand Up @@ -830,7 +830,7 @@ class Table(
* "price": [ 100, 2, 4],
* });
* val discountedPrices = prices.transformColumn("price", (row) ->
* row.getValue("price") as Int * 0.5
* (row.getValue("price") as Int) * 0.5
* );
* // Table({
* // "product": ["apple", "banana", "cherry"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ syntax_error

pipeline myPipeline {
1 as Int + 1;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// $TEST$ no_syntax_error

pipeline myPipeline {
1 as Int as String;
(1 as Int) as String;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ no_syntax_error

pipeline myPipeline {
(1 as Int) < 2;
}

0 comments on commit 7549fa1

Please sign in to comment.