From 0827c61b619437c841f9ddd83a6f46468be6ecc6 Mon Sep 17 00:00:00 2001 From: Bastian Blokland Date: Fri, 8 Sep 2023 23:03:29 +0300 Subject: [PATCH 1/4] script: Rename select intrinsic --- libs/script/include/script_intrinsic.h | 2 +- libs/script/src/eval.c | 6 ++---- libs/script/src/intrinsic.c | 4 ++-- libs/script/src/read.c | 5 ++--- libs/script/test/test_read.c | 4 ++-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/libs/script/include/script_intrinsic.h b/libs/script/include/script_intrinsic.h index af8984078..1d8c2f597 100644 --- a/libs/script/include/script_intrinsic.h +++ b/libs/script/include/script_intrinsic.h @@ -31,7 +31,7 @@ typedef enum { ScriptIntrinsic_Angle, ScriptIntrinsic_RandomBetween, ScriptIntrinsic_ComposeVector3, - ScriptIntrinsic_Select, + ScriptIntrinsic_If, ScriptIntrinsic_Count, } ScriptIntrinsic; diff --git a/libs/script/src/eval.c b/libs/script/src/eval.c index 5331c7384..0db95eed5 100644 --- a/libs/script/src/eval.c +++ b/libs/script/src/eval.c @@ -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; } diff --git a/libs/script/src/intrinsic.c b/libs/script/src/intrinsic.c index 3eb8cef1d..640401345 100644 --- a/libs/script/src/intrinsic.c +++ b/libs/script/src/intrinsic.c @@ -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; @@ -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]; diff --git a/libs/script/src/read.c b/libs/script/src/read.c index 1af73f00c..8a03f32b4 100644 --- a/libs/script/src/read.c +++ b/libs/script/src/read.c @@ -329,9 +329,8 @@ 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) { diff --git a/libs/script/test/test_read.c b/libs/script/test/test_read.c index 3f31e8f13..a263faf20 100644 --- a/libs/script/test/test_read.c +++ b/libs/script/test/test_read.c @@ -201,14 +201,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" From 69959c647dd1d37690852bbddb23315059ffa345 Mon Sep 17 00:00:00 2001 From: Bastian Blokland Date: Fri, 8 Sep 2023 23:22:42 +0300 Subject: [PATCH 2/4] script: Lex if keyword --- apps/utilities/repl.c | 2 ++ libs/script/include/script_lex.h | 1 + libs/script/src/lex.c | 6 ++++++ libs/script/test/test_lex.c | 2 ++ libs/script/test/utils_internal.h | 1 - 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/utilities/repl.c b/apps/utilities/repl.c index b864a839c..8f1d00a6f 100644 --- a/apps/utilities/repl.c +++ b/apps/utilities/repl.c @@ -65,6 +65,8 @@ static TtyFgColor repl_token_color(const ScriptTokenType tokenType) { case ScriptTokenType_QMarkQMark: case ScriptTokenType_QMarkQMarkEq: return TtyFgColor_Green; + case ScriptTokenType_If: + return TtyFgColor_Cyan; case ScriptTokenType_ParenOpen: case ScriptTokenType_ParenClose: case ScriptTokenType_CurlyOpen: diff --git a/libs/script/include/script_lex.h b/libs/script/include/script_lex.h index 4ef64d9a1..05f0b5bb6 100644 --- a/libs/script/include/script_lex.h +++ b/libs/script/include/script_lex.h @@ -36,6 +36,7 @@ typedef enum { ScriptTokenType_Number, // 42.1337 ScriptTokenType_Identifier, // foo ScriptTokenType_Key, // $bar + ScriptTokenType_If, // if ScriptTokenType_Error, // ScriptTokenType_End, // \0 } ScriptTokenType; diff --git a/libs/script/src/lex.c b/libs/script/src/lex.c index 24548e8ae..1c9707dc5 100644 --- a/libs/script/src/lex.c +++ b/libs/script/src/lex.c @@ -109,6 +109,10 @@ 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); + } + out->type = ScriptTokenType_Identifier; out->val_identifier = string_hash(identifier); return string_consume(str, end); @@ -313,6 +317,8 @@ 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_Error: return script_error_str(token->val_error); case ScriptTokenType_End: diff --git a/libs/script/test/test_lex.c b/libs/script/test/test_lex.c index 6e2b246b3..1f09c564a 100644 --- a/libs/script/test/test_lex.c +++ b/libs/script/test/test_lex.c @@ -101,6 +101,8 @@ 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("&"), tok_err(InvalidChar)}, {string_static("|"), tok_err(InvalidChar)}, {string_static("@"), tok_err(InvalidChar)}, diff --git a/libs/script/test/utils_internal.h b/libs/script/test/utils_internal.h index 4a46ffafa..5a839adad 100644 --- a/libs/script/test/utils_internal.h +++ b/libs/script/test/utils_internal.h @@ -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_)} From b51a102edfd5306c585805f6c02d0a7cdf06d194 Mon Sep 17 00:00:00 2001 From: Bastian Blokland Date: Fri, 8 Sep 2023 23:26:26 +0300 Subject: [PATCH 3/4] script: Lex else keyword --- apps/utilities/repl.c | 1 + libs/script/include/script_lex.h | 1 + libs/script/src/lex.c | 5 +++++ libs/script/test/test_lex.c | 1 + 4 files changed, 8 insertions(+) diff --git a/apps/utilities/repl.c b/apps/utilities/repl.c index 8f1d00a6f..8f371aa47 100644 --- a/apps/utilities/repl.c +++ b/apps/utilities/repl.c @@ -66,6 +66,7 @@ static TtyFgColor repl_token_color(const ScriptTokenType tokenType) { case ScriptTokenType_QMarkQMarkEq: return TtyFgColor_Green; case ScriptTokenType_If: + case ScriptTokenType_Else: return TtyFgColor_Cyan; case ScriptTokenType_ParenOpen: case ScriptTokenType_ParenClose: diff --git a/libs/script/include/script_lex.h b/libs/script/include/script_lex.h index 05f0b5bb6..1c763a5a0 100644 --- a/libs/script/include/script_lex.h +++ b/libs/script/include/script_lex.h @@ -37,6 +37,7 @@ typedef enum { ScriptTokenType_Identifier, // foo ScriptTokenType_Key, // $bar ScriptTokenType_If, // if + ScriptTokenType_Else, // else ScriptTokenType_Error, // ScriptTokenType_End, // \0 } ScriptTokenType; diff --git a/libs/script/src/lex.c b/libs/script/src/lex.c index 1c9707dc5..112afec8b 100644 --- a/libs/script/src/lex.c +++ b/libs/script/src/lex.c @@ -112,6 +112,9 @@ static String script_lex_identifier(String str, ScriptToken* out) { 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); @@ -319,6 +322,8 @@ String script_token_str_scratch(const ScriptToken* token) { 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: diff --git a/libs/script/test/test_lex.c b/libs/script/test/test_lex.c index 1f09c564a..50080b8f9 100644 --- a/libs/script/test/test_lex.c +++ b/libs/script/test/test_lex.c @@ -102,6 +102,7 @@ spec(lex) { {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)}, From 4bfc9ad26d77b06d27d0af7b5b3018e8c70db063 Mon Sep 17 00:00:00 2001 From: Bastian Blokland Date: Fri, 8 Sep 2023 23:59:36 +0300 Subject: [PATCH 4/4] script: If expression support --- libs/script/include/script_error.h | 1 + libs/script/src/error.c | 1 + libs/script/src/read.c | 64 +++++++++++++++++++++++++----- libs/script/test/test_read.c | 32 +++++++++++++++ 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/libs/script/include/script_error.h b/libs/script/include/script_error.h index e654ffc33..04cd46ebc 100644 --- a/libs/script/include/script_error.h +++ b/libs/script/include/script_error.h @@ -15,6 +15,7 @@ typedef enum { ScriptError_UnterminatedArgumentList, ScriptError_BlockSizeExceedsMaximum, ScriptError_ArgumentCountExceedsMaximum, + ScriptError_InvalidConditionCountForIf, ScriptError_MissingColonInSelectExpression, ScriptError_UnexpectedTokenAfterExpression, diff --git a/libs/script/src/error.c b/libs/script/src/error.c index 6b7e27ce0..ec066f8fe 100644 --- a/libs/script/src/error.c +++ b/libs/script/src/error.c @@ -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"), }; diff --git a/libs/script/src/read.c b/libs/script/src/read.c index 8a03f32b4..d42b118ea 100644 --- a/libs/script/src/read.c +++ b/libs/script/src/read.c @@ -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); } @@ -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)) { @@ -338,19 +377,24 @@ static ScriptReadResult read_expr_primary(ScriptReadContext* ctx) { 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); diff --git a/libs/script/test/test_read.c b/libs/script/test/test_read.c index a263faf20..ba14a0ad0 100644 --- a/libs/script/test/test_read.c +++ b/libs/script/test/test_read.c @@ -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"), @@ -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) {