Skip to content

Commit

Permalink
Script if expression support (#575)
Browse files Browse the repository at this point in the history
* script: Rename select intrinsic

* script: Lex if keyword

* script: Lex else keyword

* script: If expression support
  • Loading branch information
BastianBlokland authored Sep 8, 2023
1 parent a0640b7 commit 38df4fa
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 23 deletions.
3 changes: 3 additions & 0 deletions apps/utilities/repl.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ static TtyFgColor repl_token_color(const ScriptTokenType tokenType) {
case ScriptTokenType_QMarkQMark:
case ScriptTokenType_QMarkQMarkEq:
return TtyFgColor_Green;
case ScriptTokenType_If:
case ScriptTokenType_Else:
return TtyFgColor_Cyan;
case ScriptTokenType_ParenOpen:
case ScriptTokenType_ParenClose:
case ScriptTokenType_CurlyOpen:
Expand Down
1 change: 1 addition & 0 deletions libs/script/include/script_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ typedef enum {
ScriptError_UnterminatedArgumentList,
ScriptError_BlockSizeExceedsMaximum,
ScriptError_ArgumentCountExceedsMaximum,
ScriptError_InvalidConditionCountForIf,
ScriptError_MissingColonInSelectExpression,
ScriptError_UnexpectedTokenAfterExpression,

Expand Down
2 changes: 1 addition & 1 deletion libs/script/include/script_intrinsic.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ typedef enum {
ScriptIntrinsic_Angle,
ScriptIntrinsic_RandomBetween,
ScriptIntrinsic_ComposeVector3,
ScriptIntrinsic_Select,
ScriptIntrinsic_If,

ScriptIntrinsic_Count,
} ScriptIntrinsic;
Expand Down
2 changes: 2 additions & 0 deletions libs/script/include/script_lex.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ typedef enum {
ScriptTokenType_Number, // 42.1337
ScriptTokenType_Identifier, // foo
ScriptTokenType_Key, // $bar
ScriptTokenType_If, // if
ScriptTokenType_Else, // else
ScriptTokenType_Error, //
ScriptTokenType_End, // \0
} ScriptTokenType;
Expand Down
1 change: 1 addition & 0 deletions libs/script/src/error.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ static const String g_errorStrs[] = {
string_static("UnterminatedArgumentList"),
string_static("BlockSizeExceedsMaximum"),
string_static("ArgumentCountExceedsMaximum"),
string_static("InvalidConditionCountForIf"),
string_static("MissingColonInSelectExpression"),
string_static("UnexpectedTokenAfterExpression"),
};
Expand Down
6 changes: 2 additions & 4 deletions libs/script/src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ INLINE_HINT static ScriptVal eval_intr(ScriptEvalContext* ctx, const ScriptExprI
return script_val_random_between(eval(ctx, args[0]), eval(ctx, args[1]));
case ScriptIntrinsic_ComposeVector3:
return script_val_compose_vector3(eval(ctx, args[0]), eval(ctx, args[1]), eval(ctx, args[2]));
case ScriptIntrinsic_Select: {
const ScriptVal condition = eval(ctx, args[0]);
return script_truthy(condition) ? eval(ctx, args[1]) : eval(ctx, args[2]);
}
case ScriptIntrinsic_If:
return script_truthy(eval(ctx, args[0])) ? eval(ctx, args[1]) : eval(ctx, args[2]);
case ScriptIntrinsic_Count:
break;
}
Expand Down
4 changes: 2 additions & 2 deletions libs/script/src/intrinsic.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ u32 script_intrinsic_arg_count(const ScriptIntrinsic i) {
case ScriptIntrinsic_RandomBetween:
return 2;
case ScriptIntrinsic_ComposeVector3:
case ScriptIntrinsic_Select:
case ScriptIntrinsic_If:
return 3;
case ScriptIntrinsic_Count:
break;
Expand Down Expand Up @@ -77,7 +77,7 @@ String script_intrinsic_str(const ScriptIntrinsic i) {
string_static("angle"),
string_static("random-between"),
string_static("compose-vector3"),
string_static("select"),
string_static("if"),
};
ASSERT(array_elems(g_names) == ScriptIntrinsic_Count, "Incorrect number of names");
return g_names[i];
Expand Down
11 changes: 11 additions & 0 deletions libs/script/src/lex.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ static String script_lex_identifier(String str, ScriptToken* out) {
return str;
}

if (string_eq(identifier, string_lit("if"))) {
return out->type = ScriptTokenType_If, string_consume(str, end);
}
if (string_eq(identifier, string_lit("else"))) {
return out->type = ScriptTokenType_Else, string_consume(str, end);
}

out->type = ScriptTokenType_Identifier;
out->val_identifier = string_hash(identifier);
return string_consume(str, end);
Expand Down Expand Up @@ -313,6 +320,10 @@ String script_token_str_scratch(const ScriptToken* token) {
return fmt_write_scratch("${}", fmt_int(token->val_identifier, .base = 16));
case ScriptTokenType_Key:
return fmt_write_scratch("${}", fmt_int(token->val_key, .base = 16));
case ScriptTokenType_If:
return string_lit("if");
case ScriptTokenType_Else:
return string_lit("else");
case ScriptTokenType_Error:
return script_error_str(token->val_error);
case ScriptTokenType_End:
Expand Down
69 changes: 56 additions & 13 deletions libs/script/src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ static ScriptReadResult read_expr_constant(ScriptReadContext* ctx, const StringH
static ScriptReadResult read_expr_function(ScriptReadContext* ctx, const StringHash identifier) {
ScriptExpr args[script_args_max];
const ScriptArgsResult argsRes = read_args(ctx, args);
if (argsRes.type == ScriptResult_Fail) {
if (UNLIKELY(argsRes.type == ScriptResult_Fail)) {
return script_err(argsRes.error);
}

Expand All @@ -312,6 +312,45 @@ static ScriptReadResult read_expr_function(ScriptReadContext* ctx, const StringH
return script_err(ScriptError_NoFunctionFoundForIdentifier);
}

static ScriptReadResult read_expr_if(ScriptReadContext* ctx) {
ScriptToken token;
ctx->input = script_lex(ctx->input, g_stringtable, &token);
if (UNLIKELY(token.type != ScriptTokenType_ParenOpen)) {
return script_err(ScriptError_InvalidConditionCountForIf);
}

ScriptExpr conditions[script_args_max];
const ScriptArgsResult conditionRes = read_args(ctx, conditions);
if (UNLIKELY(conditionRes.type == ScriptResult_Fail)) {
return script_err(conditionRes.error);
}
if (UNLIKELY(conditionRes.argCount != 1)) {
return script_err(ScriptError_InvalidConditionCountForIf);
}

const ScriptReadResult b1 = read_expr(ctx, OpPrecedence_None);
if (UNLIKELY(b1.type == ScriptResult_Fail)) {
return b1;
}

ScriptExpr b2Expr;
const String remInput = script_lex(ctx->input, null, &token);
if (token.type == ScriptTokenType_Else) {
ctx->input = remInput; // Consume the else keyword.

const ScriptReadResult b2 = read_expr(ctx, OpPrecedence_None);
if (UNLIKELY(b2.type == ScriptResult_Fail)) {
return b2;
}
b2Expr = b2.expr;
} else {
b2Expr = script_add_value(ctx->doc, script_null());
}

const ScriptExpr intrArgs[] = {conditions[0], b1.expr, b2Expr};
return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_If, intrArgs));
}

static ScriptReadResult read_expr_select(ScriptReadContext* ctx, const ScriptExpr condition) {
const ScriptReadResult b1 = read_expr(ctx, OpPrecedence_None);
if (UNLIKELY(b1.type == ScriptResult_Fail)) {
Expand All @@ -329,29 +368,33 @@ static ScriptReadResult read_expr_select(ScriptReadContext* ctx, const ScriptExp
return b2;
}

const ScriptIntrinsic intr = ScriptIntrinsic_Select;
const ScriptExpr intrArgs[] = {condition, b1.expr, b2.expr};
return script_expr(script_add_intrinsic(ctx->doc, intr, intrArgs));
const ScriptExpr intrArgs[] = {condition, b1.expr, b2.expr};
return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_If, intrArgs));
}

static ScriptReadResult read_expr_primary(ScriptReadContext* ctx) {
ScriptToken token;
ctx->input = script_lex(ctx->input, g_stringtable, &token);

switch (token.type) {
/**
* Parenthesized expression.
*/
/**
* Parenthesized expression.
*/
case ScriptTokenType_ParenOpen:
return read_expr_paren(ctx);
/**
* Scope.
*/
/**
* Scope.
*/
case ScriptTokenType_CurlyOpen:
return read_expr_block(ctx, ScriptBlockType_Scope);
/**
* Identifiers.
*/
/**
* Keywords.
*/
case ScriptTokenType_If:
return read_expr_if(ctx);
/**
* Identifiers.
*/
case ScriptTokenType_Identifier: {
ScriptToken nextToken;
const String remInput = script_lex(ctx->input, null, &nextToken);
Expand Down
3 changes: 3 additions & 0 deletions libs/script/test/test_lex.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ spec(lex) {
{string_static(" \t $héllo"), tok_key_lit("héllo")},
{string_static("$"), tok_err(KeyEmpty)},

{string_static("if"), tok_simple(If)},
{string_static("else"), tok_simple(Else)},

{string_static("&"), tok_err(InvalidChar)},
{string_static("|"), tok_err(InvalidChar)},
{string_static("@"), tok_err(InvalidChar)},
Expand Down
36 changes: 34 additions & 2 deletions libs/script/test/test_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ spec(read) {
{string_static("((42.1337))"), string_static("[value: 42.1337]")},
{string_static("(($hello))"), string_static("[mem-load: $3944927369]")},

// If expressions.
{
string_static("if(true) 2"),
string_static("[intrinsic: if]\n"
" [value: true]\n"
" [value: 2]\n"
" [value: null]"),
},
{
string_static("if(true) 2 else 3"),
string_static("[intrinsic: if]\n"
" [value: true]\n"
" [value: 2]\n"
" [value: 3]"),
},
{
string_static("if(false) 2 else if(true) 3"),
string_static("[intrinsic: if]\n"
" [value: false]\n"
" [value: 2]\n"
" [intrinsic: if]\n"
" [value: true]\n"
" [value: 3]\n"
" [value: null]"),
},

// Unary expressions.
{
string_static("-42"),
Expand Down Expand Up @@ -201,14 +227,14 @@ spec(read) {
// Ternary expressions.
{
string_static("true ? 1 : 2"),
string_static("[intrinsic: select]\n"
string_static("[intrinsic: if]\n"
" [value: true]\n"
" [value: 1]\n"
" [value: 2]"),
},
{
string_static("1 > 2 ? 1 + 2 : 3 + 4"),
string_static("[intrinsic: select]\n"
string_static("[intrinsic: if]\n"
" [intrinsic: greater]\n"
" [value: 1]\n"
" [value: 2]\n"
Expand Down Expand Up @@ -478,6 +504,12 @@ spec(read) {
{string_static("{1;"), ScriptError_UnterminatedScope},
{string_static("{1;2"), ScriptError_UnterminatedScope},
{string_static("{1;2;"), ScriptError_UnterminatedScope},
{string_static("if"), ScriptError_InvalidConditionCountForIf},
{string_static("if("), ScriptError_UnterminatedArgumentList},
{string_static("if()"), ScriptError_InvalidConditionCountForIf},
{string_static("if(1,2)"), ScriptError_InvalidConditionCountForIf},
{string_static("if(1)"), ScriptError_MissingPrimaryExpression},
{string_static("if(1) 1 else"), ScriptError_MissingPrimaryExpression},
};

for (u32 i = 0; i != array_elems(g_testData); ++i) {
Expand Down
1 change: 0 additions & 1 deletion libs/script/test/utils_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// clang-format off

#define tok_simple(_TYPE_) (ScriptToken){.type = ScriptTokenType_##_TYPE_}
#define tok_null(void) (ScriptToken){.type = ScriptTokenType_Null}
#define tok_number(_VAL_) (ScriptToken){.type = ScriptTokenType_Number, .val_number = (_VAL_)}
#define tok_bool(_VAL_) (ScriptToken){.type = ScriptTokenType_Bool, .val_bool = (_VAL_)}
#define tok_id(_VAL_) (ScriptToken){.type = ScriptTokenType_Identifier, .val_identifier = string_hash(_VAL_)}
Expand Down

0 comments on commit 38df4fa

Please sign in to comment.