Skip to content

Commit

Permalink
Add ternary operator (#287)
Browse files Browse the repository at this point in the history
  • Loading branch information
vtereshkov authored Jun 12, 2023
1 parent 5d0cdf5 commit 0dd6a25
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 12 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Umka is a statically typed embeddable scripting language. It combines the simpli

* [Playground](https://vtereshkov.github.io/umka-lang)
* [Downloads](https://github.com/vtereshkov/umka-lang/releases)
* Documentation:
* Documentation
* [Language](https://github.com/vtereshkov/umka-lang/blob/master/doc/lang.md)
* [Standard library](https://github.com/vtereshkov/umka-lang/blob/master/doc/lib.md)
* [Embedding API](https://github.com/vtereshkov/umka-lang/blob/master/doc/api.md)
Expand Down Expand Up @@ -197,4 +197,4 @@ While Go is a compiled systems programming language with a complex runtime libra
Umka is very similar to Go syntactically. However, in some aspects it's different. It has shorter keywords: `fn` for `func`, `str` for `string`, `in` for `range`. For better readability, it requires a `:` between variable names and types in declarations. It doesn't follow the [unfortunate C tradition](https://blog.golang.org/declaration-syntax) of pointer dereferencing. Instead of `*p`, it uses the Pascal syntax `p^`. As the `*` character is no longer used for pointers, it becomes the export mark, like in Oberon, so that a programmer can freely use upper/lower case letters in identifiers according to his/her own style. Type assertions don't have any special syntax; they look like pointer type casts.

### Semantics
Umka allows implicit type casts and supports default parameters in function declarations. It doesn't have slices as separate data types. Instead, it supports dynamic arrays, which are declared like Go's slices and initialized by calling `make()`. Method receivers must be pointers. The multithreading model in Umka is inspired by Lua and Wren rather than Go. It offers lightweight threads called fibers instead of goroutines and channels. The garbage collection mechanism is based on reference counting, so Umka needs to support `weak` pointers. Closures and Unicode support are under development.
Umka allows implicit type casts and supports default parameters in function declarations. It features the ternary conditional operator deliberately omitted from Go. It doesn't have slices as separate data types. Instead, it supports dynamic arrays, which are declared like Go's slices and initialized by calling `make()`. Method receivers must be pointers. The multithreading model in Umka is inspired by Lua and Wren rather than Go. It offers lightweight threads called fibers instead of goroutines and channels. The garbage collection mechanism is based on reference counting, so Umka needs to support `weak` pointers. Closures and Unicode support are under development.
2 changes: 1 addition & 1 deletion Umka.sublime-syntax
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ contexts:
scope: keyword.control.umka

# Operators
- match: '\+|\-|\*|/|%|&|\||~|<<|>>|\+=|\-=|\*=|/=|%=|&=|\|=|~=|<<=|>>=|&&|\|\||!|\++|\--|==|<|>|!=|<=|>=|=|:='
- match: '\+|\-|\*|/|%|&|\||~|<<|>>|\+=|\-=|\*=|/=|%=|&=|\|=|~=|<<=|>>=|&&|\|\||\?|!|\++|\--|==|<|>|!=|<=|>=|=|:='
scope: keyword.operator.umka

# Punctuation
Expand Down
23 changes: 17 additions & 6 deletions doc/lang.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Umka has a set of arithmetical, bitwise, logical, relation operators, as well as
```
+ - * / % & | ~ << >>
+= -= *= /= %= &= |= ~= <<= >>=
&& || ! ++ --
&& || ? ! ++ --
== < > != <= >=
= := ( ) [ ] { }
^ ; : . ..
Expand Down Expand Up @@ -958,7 +958,8 @@ Syntax:

```
exprOrLit = expr | untypedLiteral.
expr = logicalTerm {"||" logicalTerm}.
expr = logicalExpr ["?" expr ":" expr].
logicalExpr = logicalTerm {"||" logicalTerm}.
logicalTerm = relation {"&&" relation}.
relation = relationTerm [("==" | "!=" | "<" | "<=" | ">" | ">=") relationTerm].
relationTerm = term {("+" | "-" | "|" | "~") term}.
Expand All @@ -975,7 +976,7 @@ Examples:
"Hello, " + person.name + '\n'
p != null && p[i] > 0
&Vec{2, 5}
{"Hello", "World"} // Literal type omitted
x < 3 ? 42.5 : 60
```

#### Unary operators
Expand Down Expand Up @@ -1024,14 +1025,23 @@ Operand types are implicitly converted in two steps:
* The right operand type is converted to the left operand type
* The left operand type is converted to the right operand type

##### Operator precedence
#### Ternary operator

```
a ? b : c
```

The `? :` operator performs the conditional evaluation. For any expressions `a`, `b` and `c`, if `a` is `true`, it evaluates to `b` and skips `c`, otherwise it evaluates to `c` and skips `b`. The type of `a` must be compatible with `bool`. The operator result has the same type as `b`. The type of `c` is implicitly converted to the type of `b`. The expression `a` cannot be a ternary operator itself.

#### Operator precedence

```
* / % << >> & Highest
+ - | ~
== != < <= > >=
&&
|| Lowest
||
? : Lowest
```

## Statements
Expand Down Expand Up @@ -1406,7 +1416,8 @@ continueStmt = "continue".
returnStmt = "return" [exprOrLitList].
exprOrLitList = exprOrLit {"," exprOrLit}.
exprOrLit = expr | untypedLiteral.
expr = logicalTerm {"||" logicalTerm}.
expr = logicalExpr ["?" expr ":" expr].
logicalExpr = logicalTerm {"||" logicalTerm}.
logicalTerm = relation {"&&" relation}.
relation = relationTerm
[("==" | "!=" | "<" | "<=" | ">" | ">=") relationTerm].
Expand Down
2 changes: 1 addition & 1 deletion playground/umka.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/umka_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ void blocksLeave(Blocks *blocks)
}


void blocksReenter(Blocks *blocks)
{
blocks->top++;
}


int blocksCurrent(Blocks *blocks)
{
return blocks->item[blocks->top].block;
Expand Down
1 change: 1 addition & 0 deletions src/umka_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ void moduleAssertRegularizePath(Modules *modules, const char *path, const char
void blocksInit (Blocks *blocks, Error *error);
void blocksFree (Blocks *blocks);
void blocksEnter (Blocks *blocks, struct tagIdent *fn);
void blocksReenter(Blocks *blocks);
void blocksLeave (Blocks *blocks);
int blocksCurrent(Blocks *blocks);

Expand Down
96 changes: 94 additions & 2 deletions src/umka_expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -2406,8 +2406,8 @@ static void parseLogicalTerm(Compiler *comp, Type **type, Const *constant)
}


// expr = logicalTerm {"||" logicalTerm}.
void parseExpr(Compiler *comp, Type **type, Const *constant)
// logicalExpr = logicalTerm {"||" logicalTerm}.
static void parseLogicalExpr(Compiler *comp, Type **type, Const *constant)
{
parseLogicalTerm(comp, type, constant);

Expand Down Expand Up @@ -2450,6 +2450,98 @@ void parseExpr(Compiler *comp, Type **type, Const *constant)
}


// expr = logicalExpr ["?" expr ":" expr].
void parseExpr(Compiler *comp, Type **type, Const *constant)
{
parseLogicalExpr(comp, type, constant);

// "?"
if (comp->lex.tok.kind == TOK_QUESTION)
{
typeAssertCompatible(&comp->types, comp->boolType, *type, false);
lexNext(&comp->lex);

Type *leftType = NULL, *rightType = NULL;

if (constant)
{
Const leftConstantBuf, *leftConstant = &leftConstantBuf;
parseExpr(comp, &leftType, leftConstant);

// ":"
lexEat(&comp->lex, TOK_COLON);

Const rightConstantBuf, *rightConstant = &rightConstantBuf;
parseExpr(comp, &rightType, rightConstant);

// Convert to left-hand side's type
doImplicitTypeConv(comp, leftType, &rightType, rightConstant, false);
typeAssertCompatible(&comp->types, leftType, rightType, false);

*constant = constant->intVal ? (*leftConstant) : (*rightConstant);
}
else
{
genIfCondEpilog(&comp->gen);

// Left-hand side expression
blocksEnter(&comp->blocks, NULL);

parseExpr(comp, &leftType, NULL);

Ident *result = NULL;
if (typeGarbageCollected(leftType))
{
// Create a temporary result variable in the outer block, so that it could outlive both left- and right-hand side expression blocks
blocksLeave(&comp->blocks);
result = identAllocTempVar(&comp->idents, &comp->types, &comp->modules, &comp->blocks, leftType, false);
blocksReenter(&comp->blocks);

// Copy result to temporary variable
genDup(&comp->gen);
genChangeRefCnt(&comp->gen, TOK_PLUSPLUS, leftType);
doPushVarPtr(comp, result);
genSwapAssign(&comp->gen, result->type->kind, typeSize(&comp->types, result->type));
}

doGarbageCollection(comp, blocksCurrent(&comp->blocks));
identWarnIfUnusedAll(&comp->idents, blocksCurrent(&comp->blocks));
blocksLeave(&comp->blocks);

// ":"
lexEat(&comp->lex, TOK_COLON);
genElseProlog(&comp->gen);

// Right-hand side expression
blocksEnter(&comp->blocks, NULL);

parseExpr(comp, &rightType, NULL);

// Convert to left-hand side's type
doImplicitTypeConv(comp, leftType, &rightType, NULL, false);
typeAssertCompatible(&comp->types, leftType, rightType, false);

if (typeGarbageCollected(leftType))
{
// Copy result to temporary variable
genDup(&comp->gen);
genChangeRefCnt(&comp->gen, TOK_PLUSPLUS, leftType);
doPushVarPtr(comp, result);
genSwapAssign(&comp->gen, result->type->kind, typeSize(&comp->types, result->type));
}

doGarbageCollection(comp, blocksCurrent(&comp->blocks));
identWarnIfUnusedAll(&comp->idents, blocksCurrent(&comp->blocks));
blocksLeave(&comp->blocks);

genIfElseEpilog(&comp->gen);
}

*type = leftType;
}
}


// exprOrLit = expr | untypedLiteral.
void parseExprOrUntypedLiteral(Compiler *comp, Type **type, Type *untypedLiteralType, Const *constant)
{
Expand Down
8 changes: 8 additions & 0 deletions src/umka_lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ static const char *spelling [] =
"<",
">",
"=",
"?",
"!",
"!=",
"<=",
Expand Down Expand Up @@ -532,6 +533,13 @@ static void lexOperator(Lexer *lex)
break;
}

case '?':
{
lex->tok.kind = TOK_QUESTION;
ch = lexChar(lex);
break;
}

case '!':
{
ch = lexChar(lex);
Expand Down
1 change: 1 addition & 0 deletions src/umka_lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ typedef enum
TOK_LESS,
TOK_GREATER,
TOK_EQ,
TOK_QUESTION,
TOK_NOT,
TOK_NOTEQ,
TOK_LESSEQ,
Expand Down
2 changes: 2 additions & 0 deletions tests/all.um
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"defparam.um"
"datetime.um"
"untypedlit.um"
"ternary.um"
)

fn main() {
Expand Down Expand Up @@ -70,4 +71,5 @@ fn main() {
printf("\n\n>>> Default parameters\n\n"); defparam.test()
printf("\n\n>>> Date/time\n\n"); datetime.test()
printf("\n\n>>> Untyped literals\n\n"); untypedlit.test()
printf("\n\n>>> Ternary operator\n\n"); ternary.test()
}
27 changes: 27 additions & 0 deletions tests/expected.log
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,30 @@ true
{w: 300 h: 400}
[42 9 11]
[[3 6] [5 7]]


>>> Ternary operator

42 17
42 17
0: 5 6 7.000000 8.000000 9 10
1: 15 16 and nothing more
2: 5 6 7.000000 8.000000 9 10
3: 15 16 and nothing more
4: 5 6 7.000000 8.000000 9 10
5: 15 16 and nothing more
6: 5 6 7.000000 8.000000 9 10
7: 15 16 and nothing more
8: 5 6 7.000000 8.000000 9 10
9: 15 16 and nothing more
0: [-4 12]
1: [-4 12]
2: [-4 12]
3: [-4 12]
4: [3 5.5 7]
5: [3 5.5 7]
6: [3 5.5 7]
7: [-4 12]
8: [-4 12]
9: [-4 12]
train! train!
49 changes: 49 additions & 0 deletions tests/ternary.um
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
fn f(x: int): real {
return x < 3 ? 42.0 : 17
}

g1 := 2 < 3 ? 42.0 : 17
g2 := 4 < 3 ? 42.0 : 17

const (
gexc = '!'
garg = 'T'
gvehicle = (garg == 'B') ? "bus" + gexc :
(garg == 'A') ? "airplane" + gexc :
(garg == 'T') ? "train" + gexc :
(garg == 'C') ? "car" + gexc :
(garg == 'H') ? "horse" + gexc :
"feet" + gexc
)

fn test*() {
printf("%v %v\n", f(2), f(4))
printf("%v %v\n", g1, g2)

for i := 0; i < 10; i++ {
s := "Hello" + "World"
s = i % 2 == 0 ? sprintf("%d %d ", 5, 6) + sprintf("%f %f ", 7.0, 8.0) + sprintf("%v %v ", 9, 10) : sprintf("%d %d ", 15, 16) + "and nothing more"
printf("%d: %s\n", i, s)
}

for i := 0; i < 10; i++ {
p := i > 3 && i < 7 ? []real{3, 5.5, 7} : []real([]int{-4, 12})
printf("%d: %v\n", i, p)
}


exc := '!'
arg := 'T'
vehicle := (arg == 'B') ? "bus" + exc :
(arg == 'A') ? "airplane" + exc :
(arg == 'T') ? "train" + exc :
(arg == 'C') ? "car" + exc :
(arg == 'H') ? "horse" + exc :
"feet" + exc

printf("%s %s\n", vehicle, gvehicle)
}

fn main() {
test()
}

0 comments on commit 0dd6a25

Please sign in to comment.