diff --git a/apps/utilities/repl.c b/apps/utilities/repl.c index 742a0e3ad..64435fe5e 100644 --- a/apps/utilities/repl.c +++ b/apps/utilities/repl.c @@ -14,10 +14,23 @@ */ typedef enum { - ReplFlags_None = 0, - ReplFlags_OutputAst = 1 << 0, + ReplFlags_None = 0, + ReplFlags_OutputTokens = 1 << 0, + ReplFlags_OutputAst = 1 << 1, + ReplFlags_OutputStats = 1 << 2, } ReplFlags; +typedef struct { + u32 exprs[ScriptExprType_Count]; + u32 exprsTotal; +} ReplScriptStats; + +static void repl_script_collect_stats(void* ctx, const ScriptDoc* doc, const ScriptExpr expr) { + ReplScriptStats* stats = ctx; + ++stats->exprs[script_expr_type(doc, expr)]; + ++stats->exprsTotal; +} + static void repl_output(const String text) { file_write_sync(g_file_stdout, text); } static void repl_output_error(const String message) { @@ -29,6 +42,46 @@ static void repl_output_error(const String message) { repl_output(text); } +static void repl_output_tokens(String text) { + Mem bufferMem = alloc_alloc(g_alloc_scratch, usize_kibibyte, 1); + DynString buffer = dynstring_create_over(bufferMem); + + for (;;) { + ScriptToken token; + text = script_lex(text, null, &token); + if (token.type == ScriptTokenType_End) { + break; + } + dynstring_append(&buffer, script_token_str_scratch(&token)); + dynstring_append_char(&buffer, ' '); + } + dynstring_append_char(&buffer, '\n'); + + repl_output(dynstring_view(&buffer)); + dynstring_destroy(&buffer); +} + +static void repl_output_ast(const ScriptDoc* script, const ScriptExpr expr) { + repl_output(fmt_write_scratch("{}\n", script_expr_fmt(script, expr))); +} + +static void repl_output_stats(const ScriptDoc* script, const ScriptExpr expr) { + ReplScriptStats stats = {0}; + script_expr_visit(script, expr, &stats, repl_script_collect_stats); + + // clang-format off + repl_output(fmt_write_scratch("Expr value: {}\n", fmt_int(stats.exprs[ScriptExprType_Value]))); + repl_output(fmt_write_scratch("Expr var-load: {}\n", fmt_int(stats.exprs[ScriptExprType_VarLoad]))); + repl_output(fmt_write_scratch("Expr var-store: {}\n", fmt_int(stats.exprs[ScriptExprType_VarStore]))); + repl_output(fmt_write_scratch("Expr mem-load: {}\n", fmt_int(stats.exprs[ScriptExprType_MemLoad]))); + repl_output(fmt_write_scratch("Expr mem-store: {}\n", fmt_int(stats.exprs[ScriptExprType_MemStore]))); + repl_output(fmt_write_scratch("Expr intrinsic: {}\n", fmt_int(stats.exprs[ScriptExprType_Intrinsic]))); + repl_output(fmt_write_scratch("Expr block: {}\n", fmt_int(stats.exprs[ScriptExprType_Block]))); + repl_output(fmt_write_scratch("Expr total: {}\n", fmt_int(stats.exprsTotal))); + repl_output(fmt_write_scratch("Values total: {}\n", fmt_int(script_values_total(script)))); + // clang-format on +} + static TtyFgColor repl_token_color(const ScriptTokenType tokenType) { switch (tokenType) { case ScriptTokenType_Error: @@ -68,6 +121,7 @@ static TtyFgColor repl_token_color(const ScriptTokenType tokenType) { return TtyFgColor_Green; case ScriptTokenType_If: case ScriptTokenType_Else: + case ScriptTokenType_Var: return TtyFgColor_Cyan; case ScriptTokenType_ParenOpen: case ScriptTokenType_ParenClose: @@ -121,13 +175,20 @@ static void repl_edit_submit(ReplState* state) { string_maybe_free(g_alloc_heap, state->editPrevText); state->editPrevText = string_maybe_dup(g_alloc_heap, dynstring_view(state->editBuffer)); + if (state->flags & ReplFlags_OutputTokens) { + repl_output_tokens(dynstring_view(state->editBuffer)); + } + ScriptDoc* script = script_create(g_alloc_heap); ScriptReadResult res; script_read(script, dynstring_view(state->editBuffer), &res); if (res.type == ScriptResult_Success) { if (state->flags & ReplFlags_OutputAst) { - repl_output(fmt_write_scratch("{}\n", script_expr_fmt(script, res.expr))); + repl_output_ast(script, res.expr); + } + if (state->flags & ReplFlags_OutputStats) { + repl_output_stats(script, res.expr); } const ScriptVal value = script_eval(script, state->scriptMem, res.expr); repl_output(fmt_write_scratch("{}\n", script_val_fmt(value))); @@ -140,8 +201,7 @@ static void repl_edit_submit(ReplState* state) { } static void repl_edit_render(const ReplState* state) { - Mem bufferMem = alloc_alloc(g_alloc_scratch, usize_kibibyte, 1); - DynString buffer = dynstring_create_over(bufferMem); + DynString buffer = dynstring_create(g_alloc_heap, usize_kibibyte); tty_write_clear_line_sequence(&buffer, TtyClearMode_All); // Clear line. tty_write_set_cursor_hor_sequence(&buffer, 0); // Move cursor to beginning of line. @@ -256,17 +316,25 @@ static i32 repl_run_interactive(const ReplFlags flags) { return 0; } -static CliId g_astFlag, g_helpFlag; +static CliId g_tokensFlag, g_astFlag, g_statsFlag, g_helpFlag; void app_cli_configure(CliApp* app) { cli_app_register_desc(app, string_lit("Script ReadEvalPrintLoop utility.")); + g_tokensFlag = cli_register_flag(app, 't', string_lit("tokens"), CliOptionFlags_None); + cli_register_desc(app, g_tokensFlag, string_lit("Ouput the tokens.")); + g_astFlag = cli_register_flag(app, 'a', string_lit("ast"), CliOptionFlags_None); cli_register_desc(app, g_astFlag, string_lit("Ouput the abstract-syntax-tree expressions.")); + g_statsFlag = cli_register_flag(app, 's', string_lit("stats"), CliOptionFlags_None); + cli_register_desc(app, g_statsFlag, string_lit("Ouput script statistics.")); + g_helpFlag = cli_register_flag(app, 'h', string_lit("help"), CliOptionFlags_None); cli_register_desc(app, g_helpFlag, string_lit("Display this help page.")); + cli_register_exclusions(app, g_helpFlag, g_tokensFlag); cli_register_exclusions(app, g_helpFlag, g_astFlag); + cli_register_exclusions(app, g_helpFlag, g_statsFlag); } i32 app_cli_run(const CliApp* app, const CliInvocation* invoc) { @@ -276,9 +344,15 @@ i32 app_cli_run(const CliApp* app, const CliInvocation* invoc) { } ReplFlags flags = ReplFlags_None; + if (cli_parse_provided(invoc, g_tokensFlag)) { + flags |= ReplFlags_OutputTokens; + } if (cli_parse_provided(invoc, g_astFlag)) { flags |= ReplFlags_OutputAst; } + if (cli_parse_provided(invoc, g_statsFlag)) { + flags |= ReplFlags_OutputStats; + } return repl_run_interactive(flags); } diff --git a/libs/script/include/script_doc.h b/libs/script/include/script_doc.h index 7e6c5a55d..0d2e3fafc 100644 --- a/libs/script/include/script_doc.h +++ b/libs/script/include/script_doc.h @@ -7,6 +7,10 @@ // Forward declare from 'core_alloc.h'. typedef struct sAllocator Allocator; +#define script_var_count 16 + +typedef u8 ScriptVarId; + /** * Definition of a Script Document for storing script expressions. */ @@ -17,6 +21,8 @@ typedef struct sScriptDoc ScriptDoc; */ typedef enum { ScriptExprType_Value, + ScriptExprType_VarLoad, + ScriptExprType_VarStore, ScriptExprType_MemLoad, ScriptExprType_MemStore, ScriptExprType_Intrinsic, @@ -46,6 +52,8 @@ void script_destroy(ScriptDoc*); * Add new expressions. */ ScriptExpr script_add_value(ScriptDoc*, ScriptVal val); +ScriptExpr script_add_var_load(ScriptDoc*, ScriptVarId); +ScriptExpr script_add_var_store(ScriptDoc*, ScriptVarId, ScriptExpr val); ScriptExpr script_add_mem_load(ScriptDoc*, StringHash key); ScriptExpr script_add_mem_store(ScriptDoc*, StringHash key, ScriptExpr val); ScriptExpr script_add_intrinsic(ScriptDoc*, ScriptIntrinsic, const ScriptExpr args[]); @@ -56,6 +64,7 @@ ScriptExpr script_add_block(ScriptDoc*, const ScriptExpr exprs[], u32 exprCount) */ ScriptExprType script_expr_type(const ScriptDoc*, ScriptExpr); bool script_expr_readonly(const ScriptDoc*, ScriptExpr); +u32 script_values_total(const ScriptDoc*); typedef void (*ScriptVisitor)(void* ctx, const ScriptDoc*, ScriptExpr); void script_expr_visit(const ScriptDoc*, ScriptExpr, void* ctx, ScriptVisitor); diff --git a/libs/script/include/script_error.h b/libs/script/include/script_error.h index 5c419147f..f13be1caf 100644 --- a/libs/script/include/script_error.h +++ b/libs/script/include/script_error.h @@ -7,10 +7,14 @@ typedef enum { ScriptError_KeyEmpty, ScriptError_UnterminatedString, ScriptError_RecursionLimitExceeded, + ScriptError_VariableLimitExceeded, + ScriptError_VariableIdentifierMissing, + ScriptError_VariableIdentifierConflicts, ScriptError_MissingPrimaryExpression, ScriptError_InvalidPrimaryExpression, - ScriptError_NoConstantFoundForIdentifier, + ScriptError_NoVariableFoundForIdentifier, ScriptError_NoFunctionFoundForIdentifier, + ScriptError_IncorrectArgumentCountForBuiltinFunction, ScriptError_UnclosedParenthesizedExpression, ScriptError_UnterminatedScope, ScriptError_UnterminatedArgumentList, diff --git a/libs/script/include/script_intrinsic.h b/libs/script/include/script_intrinsic.h index 1d8c2f597..c38a10c31 100644 --- a/libs/script/include/script_intrinsic.h +++ b/libs/script/include/script_intrinsic.h @@ -19,8 +19,6 @@ typedef enum { ScriptIntrinsic_LessOrEqual, ScriptIntrinsic_Greater, ScriptIntrinsic_GreaterOrEqual, - ScriptIntrinsic_LogicAnd, - ScriptIntrinsic_LogicOr, ScriptIntrinsic_NullCoalescing, ScriptIntrinsic_Add, ScriptIntrinsic_Sub, diff --git a/libs/script/include/script_lex.h b/libs/script/include/script_lex.h index db05a4b2b..0963f3722 100644 --- a/libs/script/include/script_lex.h +++ b/libs/script/include/script_lex.h @@ -39,6 +39,7 @@ typedef enum { ScriptTokenType_String, // "Hello World" ScriptTokenType_If, // if ScriptTokenType_Else, // else + ScriptTokenType_Var, // var ScriptTokenType_Error, // ScriptTokenType_End, // \0 } ScriptTokenType; diff --git a/libs/script/src/doc.c b/libs/script/src/doc.c index a694edea1..64bcde91b 100644 --- a/libs/script/src/doc.c +++ b/libs/script/src/doc.c @@ -2,7 +2,6 @@ #include "core_array.h" #include "core_diag.h" #include "core_dynarray.h" -#include "core_math.h" #include "doc_internal.h" @@ -18,7 +17,14 @@ static ScriptExprData* script_doc_expr_data(const ScriptDoc* doc, const ScriptEx } static ScriptValId script_doc_val_add(ScriptDoc* doc, const ScriptVal val) { - const ScriptValId id = (ScriptValId)doc->values.size; + // Check if there is an existing identical value. + ScriptValId id = 0; + for (; id != doc->values.size; ++id) { + if (script_val_equal(val, dynarray_begin_t(&doc->values, ScriptVal)[id])) { + return id; + } + } + // If not: Register a new value *dynarray_push_t(&doc->values, ScriptVal) = val; return id; } @@ -28,19 +34,6 @@ static ScriptVal script_doc_val_data(const ScriptDoc* doc, const ScriptValId id) return *dynarray_at_t(&doc->values, id, ScriptVal); } -static void script_doc_constant_add(ScriptDoc* doc, const String name, const ScriptVal val) { - const StringHash nameHash = string_hash(name); - const ScriptValId valId = script_doc_val_add(doc, val); - - array_for_t(doc->constants, ScriptConstant, constant) { - if (!constant->nameHash) { - *constant = (ScriptConstant){.nameHash = nameHash, .valId = valId}; - return; - } - } - diag_crash_msg("Script constants count exceeded"); -} - static ScriptExprSet script_doc_expr_set_add(ScriptDoc* doc, const ScriptExpr exprs[], const u32 count) { const ScriptExprSet set = (ScriptExprSet)doc->exprSets.size; @@ -54,27 +47,13 @@ static const ScriptExpr* script_doc_expr_set_data(const ScriptDoc* doc, const Sc ScriptDoc* script_create(Allocator* alloc) { ScriptDoc* doc = alloc_alloc_t(alloc, ScriptDoc); - *doc = (ScriptDoc){ + + *doc = (ScriptDoc){ .exprData = dynarray_create_t(alloc, ScriptExprData, 64), .exprSets = dynarray_create_t(alloc, ScriptExpr, 32), .values = dynarray_create_t(alloc, ScriptVal, 32), .alloc = alloc, }; - - // Register build-in constants. - script_doc_constant_add(doc, string_lit("null"), script_null()); - script_doc_constant_add(doc, string_lit("true"), script_bool(true)); - script_doc_constant_add(doc, string_lit("false"), script_bool(false)); - script_doc_constant_add(doc, string_lit("pi"), script_number(math_pi_f64)); - script_doc_constant_add(doc, string_lit("deg_to_rad"), script_number(math_deg_to_rad)); - script_doc_constant_add(doc, string_lit("rad_to_deg"), script_number(math_rad_to_deg)); - script_doc_constant_add(doc, string_lit("up"), script_vector3(geo_up)); - script_doc_constant_add(doc, string_lit("down"), script_vector3(geo_down)); - script_doc_constant_add(doc, string_lit("left"), script_vector3(geo_left)); - script_doc_constant_add(doc, string_lit("right"), script_vector3(geo_right)); - script_doc_constant_add(doc, string_lit("forward"), script_vector3(geo_forward)); - script_doc_constant_add(doc, string_lit("backward"), script_vector3(geo_backward)); - return doc; } @@ -95,6 +74,26 @@ ScriptExpr script_add_value(ScriptDoc* doc, const ScriptVal val) { }); } +ScriptExpr script_add_var_load(ScriptDoc* doc, const ScriptVarId var) { + diag_assert_msg(var < script_var_count, "Out of bounds script variable"); + return script_doc_expr_add( + doc, + (ScriptExprData){ + .type = ScriptExprType_VarLoad, + .data_var_load = {.var = var}, + }); +} + +ScriptExpr script_add_var_store(ScriptDoc* doc, const ScriptVarId var, const ScriptExpr val) { + diag_assert_msg(var < script_var_count, "Out of bounds script variable"); + return script_doc_expr_add( + doc, + (ScriptExprData){ + .type = ScriptExprType_VarStore, + .data_var_store = {.var = var, .val = val}, + }); +} + ScriptExpr script_add_mem_load(ScriptDoc* doc, const StringHash key) { diag_assert_msg(key, "Empty key is not valid"); return script_doc_expr_add( @@ -149,6 +148,8 @@ static void script_visitor_readonly(void* ctx, const ScriptDoc* doc, const Scrip *isReadonly = false; return; case ScriptExprType_Value: + case ScriptExprType_VarLoad: + case ScriptExprType_VarStore: // NOTE: Variables are volatile so are considered readonly. case ScriptExprType_MemLoad: case ScriptExprType_Intrinsic: case ScriptExprType_Block: @@ -179,8 +180,12 @@ void script_expr_visit( const ScriptExprData* data = script_doc_expr_data(doc, expr); switch (data->type) { case ScriptExprType_Value: + case ScriptExprType_VarLoad: case ScriptExprType_MemLoad: return; // No children. + case ScriptExprType_VarStore: + script_expr_visit(doc, data->data_var_store.val, ctx, visitor); + return; case ScriptExprType_MemStore: script_expr_visit(doc, data->data_mem_store.val, ctx, visitor); return; @@ -206,6 +211,8 @@ void script_expr_visit( UNREACHABLE } +u32 script_values_total(const ScriptDoc* doc) { return (u32)doc->values.size; } + static void script_expr_str_write_sep(const u32 indent, DynString* str) { dynstring_append_char(str, '\n'); dynstring_append_chars(str, ' ', indent * 2); @@ -226,6 +233,13 @@ void script_expr_str_write( script_val_str_write(script_doc_val_data(doc, data->data_value.valId), str); fmt_write(str, "]"); return; + case ScriptExprType_VarLoad: + fmt_write(str, "[var-load: {}]", fmt_int(data->data_var_load.var)); + return; + case ScriptExprType_VarStore: + fmt_write(str, "[var-store: {}]", fmt_int(data->data_var_store.var)); + script_expr_str_write_child(doc, data->data_var_store.val, indent + 1, str); + return; case ScriptExprType_MemLoad: fmt_write(str, "[mem-load: ${}]", fmt_int(data->data_mem_load.key)); return; @@ -268,23 +282,3 @@ String script_expr_str_scratch(const ScriptDoc* doc, const ScriptExpr expr) { dynstring_destroy(&str); return res; } - -ScriptExpr script_add_value_id(ScriptDoc* doc, const ScriptValId valId) { - return script_doc_expr_add( - doc, - (ScriptExprData){ - .type = ScriptExprType_Value, - .data_value = {.valId = valId}, - }); -} - -ScriptValId script_doc_constant_lookup(const ScriptDoc* doc, const StringHash nameHash) { - diag_assert_msg(nameHash, "Constant name cannot be empty"); - - array_for_t(doc->constants, ScriptConstant, constant) { - if (constant->nameHash == nameHash) { - return constant->valId; - } - } - return sentinel_u32; -} diff --git a/libs/script/src/doc_internal.h b/libs/script/src/doc_internal.h index 2ea500373..826968849 100644 --- a/libs/script/src/doc_internal.h +++ b/libs/script/src/doc_internal.h @@ -1,8 +1,6 @@ #pragma once #include "script_doc.h" -#define script_constants_max 15 - typedef u32 ScriptValId; typedef u32 ScriptExprSet; @@ -10,6 +8,15 @@ typedef struct { ScriptValId valId; } ScriptExprValue; +typedef struct { + ScriptVarId var; +} ScriptExprVarLoad; + +typedef struct { + ScriptVarId var; + ScriptExpr val; +} ScriptExprVarStore; + typedef struct { StringHash key; } ScriptExprMemLoad; @@ -33,6 +40,8 @@ typedef struct { ScriptExprType type; union { ScriptExprValue data_value; + ScriptExprVarLoad data_var_load; + ScriptExprVarStore data_var_store; ScriptExprMemLoad data_mem_load; ScriptExprMemStore data_mem_store; ScriptExprIntrinsic data_intrinsic; @@ -40,27 +49,9 @@ typedef struct { }; } ScriptExprData; -typedef struct { - StringHash nameHash; - ScriptValId valId; -} ScriptConstant; - struct sScriptDoc { - DynArray exprData; // ScriptExprData[] - DynArray exprSets; // ScriptExpr[] - DynArray values; // ScriptVal[] - ScriptConstant constants[script_constants_max]; - Allocator* alloc; + DynArray exprData; // ScriptExprData[] + DynArray exprSets; // ScriptExpr[] + DynArray values; // ScriptVal[] + Allocator* alloc; }; - -/** - * Add new expressions. - */ -ScriptExpr script_add_value_id(ScriptDoc*, ScriptValId); - -/** - * Lookup a constant by name. - * NOTE: Returns 'sentinel_u32' if no constant was found with the given name. - * Pre-condition: namehash != 0. - */ -ScriptValId script_doc_constant_lookup(const ScriptDoc*, StringHash nameHash); diff --git a/libs/script/src/error.c b/libs/script/src/error.c index 6593af8f4..ba3672e63 100644 --- a/libs/script/src/error.c +++ b/libs/script/src/error.c @@ -8,10 +8,14 @@ static const String g_errorStrs[] = { string_static("KeyEmpty"), string_static("UnterminatedString"), string_static("RecursionLimitExceeded"), + string_static("VariableLimitExceeded"), + string_static("VariableIdentifierMissing"), + string_static("VariableIdentifierConflicts"), string_static("MissingPrimaryExpression"), string_static("InvalidPrimaryExpression"), - string_static("NoConstantFoundForIdentifier"), + string_static("NoVariableFoundForIdentifier"), string_static("NoFunctionFoundForIdentifier"), + string_static("IncorrectArgumentCountForBuiltinFunction"), string_static("UnclosedParenthesizedExpression"), string_static("UnterminatedScope"), string_static("UnterminatedArgumentList"), diff --git a/libs/script/src/eval.c b/libs/script/src/eval.c index 0db95eed5..f7d30cac8 100644 --- a/libs/script/src/eval.c +++ b/libs/script/src/eval.c @@ -1,3 +1,4 @@ +#include "core_annotation.h" #include "core_diag.h" #include "script_eval.h" #include "script_mem.h" @@ -8,6 +9,7 @@ typedef struct { const ScriptDoc* doc; ScriptMem* m; + ScriptVal vars[script_var_count]; } ScriptEvalContext; static ScriptVal eval(ScriptEvalContext*, ScriptExpr); @@ -25,6 +27,16 @@ INLINE_HINT static ScriptVal eval_value(ScriptEvalContext* ctx, const ScriptExpr return dynarray_begin_t(&ctx->doc->values, ScriptVal)[expr->valId]; } +INLINE_HINT static ScriptVal eval_var_load(ScriptEvalContext* ctx, const ScriptExprVarLoad* expr) { + return ctx->vars[expr->var]; +} + +INLINE_HINT static ScriptVal eval_var_store(ScriptEvalContext* ctx, const ScriptExprVarStore* exp) { + const ScriptVal val = eval(ctx, exp->val); + ctx->vars[exp->var] = val; + return val; +} + INLINE_HINT static ScriptVal eval_mem_load(ScriptEvalContext* ctx, const ScriptExprMemLoad* expr) { return script_mem_get(ctx->m, expr->key); } @@ -72,10 +84,6 @@ INLINE_HINT static ScriptVal eval_intr(ScriptEvalContext* ctx, const ScriptExprI return script_bool(script_val_greater(eval(ctx, args[0]), eval(ctx, args[1]))); case ScriptIntrinsic_GreaterOrEqual: return script_bool(!script_val_less(eval(ctx, args[0]), eval(ctx, args[1]))); - case ScriptIntrinsic_LogicAnd: - return script_bool(script_truthy(eval(ctx, args[0])) && script_truthy(eval(ctx, args[1]))); - case ScriptIntrinsic_LogicOr: - return script_bool(script_truthy(eval(ctx, args[0])) || script_truthy(eval(ctx, args[1]))); case ScriptIntrinsic_NullCoalescing: { const ScriptVal a = eval(ctx, args[0]); return script_val_has(a) ? a : eval(ctx, args[1]); @@ -118,10 +126,14 @@ INLINE_HINT static ScriptVal eval_block(ScriptEvalContext* ctx, const ScriptExpr return ret; } -static ScriptVal eval(ScriptEvalContext* ctx, const ScriptExpr expr) { +NO_INLINE_HINT static ScriptVal eval(ScriptEvalContext* ctx, const ScriptExpr expr) { switch (script_expr_type(ctx->doc, expr)) { case ScriptExprType_Value: return eval_value(ctx, &expr_data(ctx, expr)->data_value); + case ScriptExprType_VarLoad: + return eval_var_load(ctx, &expr_data(ctx, expr)->data_var_load); + case ScriptExprType_VarStore: + return eval_var_store(ctx, &expr_data(ctx, expr)->data_var_store); case ScriptExprType_MemLoad: return eval_mem_load(ctx, &expr_data(ctx, expr)->data_mem_load); case ScriptExprType_MemStore: diff --git a/libs/script/src/intrinsic.c b/libs/script/src/intrinsic.c index 640401345..c5abb7506 100644 --- a/libs/script/src/intrinsic.c +++ b/libs/script/src/intrinsic.c @@ -23,8 +23,6 @@ u32 script_intrinsic_arg_count(const ScriptIntrinsic i) { case ScriptIntrinsic_LessOrEqual: case ScriptIntrinsic_Greater: case ScriptIntrinsic_GreaterOrEqual: - case ScriptIntrinsic_LogicAnd: - case ScriptIntrinsic_LogicOr: case ScriptIntrinsic_NullCoalescing: case ScriptIntrinsic_Add: case ScriptIntrinsic_Sub: @@ -65,8 +63,6 @@ String script_intrinsic_str(const ScriptIntrinsic i) { string_static("less-or-equal"), string_static("greater"), string_static("greater-or-equal"), - string_static("logic-and"), - string_static("logic-or"), string_static("null-coalescing"), string_static("add"), string_static("sub"), diff --git a/libs/script/src/lex.c b/libs/script/src/lex.c index 4243b8784..95a490e3f 100644 --- a/libs/script/src/lex.c +++ b/libs/script/src/lex.c @@ -1,3 +1,4 @@ +#include "core_array.h" #include "core_diag.h" #include "core_format.h" #include "core_math.h" @@ -7,6 +8,17 @@ #define script_token_err(_ERR_) \ (ScriptToken) { .type = ScriptTokenType_Error, .val_error = (_ERR_) } +typedef struct { + String id; + ScriptTokenType token; +} ScriptLexKeyword; + +static const ScriptLexKeyword g_lexKeywords[] = { + {.id = string_static("if"), .token = ScriptTokenType_If}, + {.id = string_static("else"), .token = ScriptTokenType_Else}, + {.id = string_static("var"), .token = ScriptTokenType_Var}, +}; + static bool script_is_word_start(const u8 c) { // Either ascii letter or start of non-ascii utf8 character. static const u8 g_utf8Start = 0xC0; @@ -150,11 +162,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); - } - if (string_eq(identifier, string_lit("else"))) { - return out->type = ScriptTokenType_Else, string_consume(str, end); + array_for_t(g_lexKeywords, ScriptLexKeyword, keyword) { + if (string_eq(identifier, keyword->id)) { + return out->type = keyword->token, string_consume(str, end); + } } out->type = ScriptTokenType_Identifier; @@ -371,6 +382,8 @@ String script_token_str_scratch(const ScriptToken* token) { return string_lit("if"); case ScriptTokenType_Else: return string_lit("else"); + case ScriptTokenType_Var: + return string_lit("var"); case ScriptTokenType_Error: return script_error_str(token->val_error); case ScriptTokenType_End: diff --git a/libs/script/src/read.c b/libs/script/src/read.c index 88e21c189..da6cf1798 100644 --- a/libs/script/src/read.c +++ b/libs/script/src/read.c @@ -1,5 +1,7 @@ #include "core_array.h" +#include "core_bits.h" #include "core_diag.h" +#include "core_math.h" #include "core_thread.h" #include "script_lex.h" #include "script_read.h" @@ -9,6 +11,8 @@ #define script_depth_max 25 #define script_block_size_max 128 #define script_args_max 10 +#define script_builtin_consts_max 32 +#define script_builtin_funcs_max 32 #define script_err(_ERR_) \ (ScriptReadResult) { .type = ScriptResult_Fail, .error = (_ERR_) } @@ -17,26 +21,96 @@ (ScriptReadResult) { .type = ScriptResult_Success, .expr = (_EXPR_) } typedef struct { - String name; - StringHash nameHash; // NOTE: Initialized at runtime. + StringHash idHash; + ScriptVal val; +} ScriptBuiltinConst; + +static ScriptBuiltinConst g_scriptBuiltinConsts[script_builtin_consts_max]; +static u32 g_scriptBuiltinConstCount; + +static void script_builtin_const_add(const String id, const ScriptVal val) { + diag_assert(g_scriptBuiltinConstCount != script_builtin_consts_max); + g_scriptBuiltinConsts[g_scriptBuiltinConstCount++] = (ScriptBuiltinConst){ + .idHash = string_hash(id), + .val = val, + }; +} + +static const ScriptBuiltinConst* script_builtin_const_lookup(const StringHash id) { + for (u32 i = 0; i != g_scriptBuiltinConstCount; ++i) { + if (g_scriptBuiltinConsts[i].idHash == id) { + return &g_scriptBuiltinConsts[i]; + } + } + return null; +} + +typedef struct { + StringHash idHash; + u32 argCount; ScriptIntrinsic intr; -} ScriptFunction; - -static ScriptFunction g_scriptIntrinsicFuncs[] = { - {.name = string_static("vector"), .intr = ScriptIntrinsic_ComposeVector3}, - {.name = string_static("vector_x"), .intr = ScriptIntrinsic_VectorX}, - {.name = string_static("vector_y"), .intr = ScriptIntrinsic_VectorY}, - {.name = string_static("vector_z"), .intr = ScriptIntrinsic_VectorZ}, - {.name = string_static("distance"), .intr = ScriptIntrinsic_Distance}, - {.name = string_static("distance"), .intr = ScriptIntrinsic_Magnitude}, - {.name = string_static("normalize"), .intr = ScriptIntrinsic_Normalize}, - {.name = string_static("angle"), .intr = ScriptIntrinsic_Angle}, - {.name = string_static("random"), .intr = ScriptIntrinsic_Random}, - {.name = string_static("random"), .intr = ScriptIntrinsic_RandomBetween}, - {.name = string_static("round_down"), .intr = ScriptIntrinsic_RoundDown}, - {.name = string_static("round_nearest"), .intr = ScriptIntrinsic_RoundNearest}, - {.name = string_static("round_up"), .intr = ScriptIntrinsic_RoundUp}, -}; +} ScriptBuiltinFunc; + +static ScriptBuiltinFunc g_scriptBuiltinFuncs[script_builtin_funcs_max]; +static u32 g_scriptBuiltinFuncCount; + +static void script_builtin_func_add(const String id, const ScriptIntrinsic intr) { + diag_assert(g_scriptBuiltinFuncCount != script_builtin_funcs_max); + g_scriptBuiltinFuncs[g_scriptBuiltinFuncCount++] = (ScriptBuiltinFunc){ + .idHash = string_hash(id), + .argCount = script_intrinsic_arg_count(intr), + .intr = intr, + }; +} + +static bool script_builtin_func_exists(const StringHash id) { + for (u32 i = 0; i != g_scriptBuiltinFuncCount; ++i) { + if (g_scriptBuiltinFuncs[i].idHash == id) { + return true; + } + } + return false; +} + +static const ScriptBuiltinFunc* script_builtin_func_lookup(const StringHash id, const u32 argc) { + for (u32 i = 0; i != g_scriptBuiltinFuncCount; ++i) { + if (g_scriptBuiltinFuncs[i].idHash == id && g_scriptBuiltinFuncs[i].argCount == argc) { + return &g_scriptBuiltinFuncs[i]; + } + } + return null; +} + +static void script_builtin_init() { + // Builtin constants. + script_builtin_const_add(string_lit("null"), script_null()); + script_builtin_const_add(string_lit("true"), script_bool(true)); + script_builtin_const_add(string_lit("false"), script_bool(false)); + script_builtin_const_add(string_lit("pi"), script_number(math_pi_f64)); + script_builtin_const_add(string_lit("deg_to_rad"), script_number(math_deg_to_rad)); + script_builtin_const_add(string_lit("rad_to_deg"), script_number(math_rad_to_deg)); + script_builtin_const_add(string_lit("up"), script_vector3(geo_up)); + script_builtin_const_add(string_lit("down"), script_vector3(geo_down)); + script_builtin_const_add(string_lit("left"), script_vector3(geo_left)); + script_builtin_const_add(string_lit("right"), script_vector3(geo_right)); + script_builtin_const_add(string_lit("forward"), script_vector3(geo_forward)); + script_builtin_const_add(string_lit("backward"), script_vector3(geo_backward)); + + // Builtin functions. + script_builtin_func_add(string_lit("vector"), ScriptIntrinsic_ComposeVector3); + script_builtin_func_add(string_lit("vector_x"), ScriptIntrinsic_VectorX); + script_builtin_func_add(string_lit("vector_y"), ScriptIntrinsic_VectorY); + script_builtin_func_add(string_lit("vector_z"), ScriptIntrinsic_VectorZ); + script_builtin_func_add(string_lit("distance"), ScriptIntrinsic_Distance); + script_builtin_func_add(string_lit("distance"), ScriptIntrinsic_Magnitude); + script_builtin_func_add(string_lit("normalize"), ScriptIntrinsic_Normalize); + script_builtin_func_add(string_lit("angle"), ScriptIntrinsic_Angle); + script_builtin_func_add(string_lit("random"), ScriptIntrinsic_Random); + script_builtin_func_add(string_lit("random"), ScriptIntrinsic_RandomBetween); + script_builtin_func_add(string_lit("round_down"), ScriptIntrinsic_RoundDown); + script_builtin_func_add(string_lit("round_nearest"), ScriptIntrinsic_RoundNearest); + script_builtin_func_add(string_lit("round_up"), ScriptIntrinsic_RoundUp); +} typedef enum { OpPrecedence_None, @@ -114,12 +188,6 @@ static ScriptIntrinsic token_op_binary(const ScriptTokenType type) { return ScriptIntrinsic_Div; case ScriptTokenType_Percent: return ScriptIntrinsic_Mod; - case ScriptTokenType_AmpAmp: - return ScriptIntrinsic_LogicAnd; - case ScriptTokenType_PipePipe: - return ScriptIntrinsic_LogicOr; - case ScriptTokenType_QMarkQMark: - return ScriptIntrinsic_NullCoalescing; default: diag_assert_fail("Invalid binary operation token"); UNREACHABLE @@ -147,30 +215,137 @@ static ScriptIntrinsic token_op_binary_modify(const ScriptTokenType type) { } typedef struct { - ScriptDoc* doc; - String input; - u32 recursionDepth; + StringHash id; + ScriptVarId varSlot; +} ScriptVarMeta; + +typedef struct sScriptScope { + ScriptVarMeta vars[script_var_count]; + struct sScriptScope* next; +} ScriptScope; + +typedef struct { + ScriptDoc* doc; + String input; + ScriptScope* scopeRoot; + u8 varAvailability[bits_to_bytes(script_var_count) + 1]; // Bitmask of free vars. + u32 recursionDepth; } ScriptReadContext; -static ScriptReadResult read_expr(ScriptReadContext*, OpPrecedence minPrecedence); +static bool script_var_alloc(ScriptReadContext* ctx, ScriptVarId* out) { + const usize index = bitset_next(mem_var(ctx->varAvailability), 0); + if (UNLIKELY(sentinel_check(index))) { + return false; + } + bitset_clear(mem_var(ctx->varAvailability), index); + *out = (ScriptVarId)index; + return true; +} -typedef enum { - ScriptBlockType_Root, - ScriptBlockType_Scope, -} ScriptBlockType; +static void script_var_free(ScriptReadContext* ctx, const ScriptVarId var) { + diag_assert(!bitset_test(mem_var(ctx->varAvailability), var)); + bitset_set(mem_var(ctx->varAvailability), var); +} -/** - * NOTE: For scope block the caller is expected to consume the opening curly brace. - */ -static ScriptReadResult read_expr_block(ScriptReadContext* ctx, const ScriptBlockType type) { - ScriptToken token; - ScriptExpr exprs[script_block_size_max]; - u32 exprCount = 0; +static void script_var_free_all(ScriptReadContext* ctx) { + bitset_set_all(mem_var(ctx->varAvailability), script_var_count); +} + +static ScriptScope* script_scope_head(ScriptReadContext* ctx) { return ctx->scopeRoot; } +static ScriptScope* script_scope_tail(ScriptReadContext* ctx) { + ScriptScope* scope = ctx->scopeRoot; + for (; scope->next; scope = scope->next) + ; + return scope; +} + +static void script_scope_push(ScriptReadContext* ctx, ScriptScope* scope) { + script_scope_tail(ctx)->next = scope; +} + +static void script_scope_pop(ScriptReadContext* ctx) { + ScriptScope* scope = script_scope_tail(ctx); + diag_assert(scope && scope != ctx->scopeRoot); + + // Remove the scope from the scope list. + ScriptScope* newTail = ctx->scopeRoot; + for (; newTail->next != scope; newTail = newTail->next) + ; + newTail->next = null; + + // Free all the variables that the scope declared. + for (u32 i = 0; i != script_var_count; ++i) { + if (scope->vars[i].id) { + script_var_free(ctx, scope->vars[i].varSlot); + } + } +} + +static bool script_var_declare(ScriptReadContext* ctx, const StringHash id, ScriptVarId* out) { + ScriptScope* scope = script_scope_tail(ctx); + diag_assert(scope); + + for (u32 i = 0; i != script_var_count; ++i) { + if (scope->vars[i].id) { + continue; // Var already in use. + } + if (!script_var_alloc(ctx, out)) { + return false; + } + scope->vars[i] = (ScriptVarMeta){.id = id, .varSlot = *out}; + return true; + } + return false; +} +static const ScriptVarMeta* script_var_lookup(ScriptReadContext* ctx, const StringHash id) { + for (ScriptScope* scope = script_scope_head(ctx); scope; scope = scope->next) { + for (u32 i = 0; i != script_var_count; ++i) { + if (scope->vars[i].id == id) { + return &scope->vars[i]; + } + if (!scope->vars[i].id) { + break; + } + } + } + return null; +} + +static ScriptToken read_peek(ScriptReadContext* ctx) { + ScriptToken token; script_lex(ctx->input, null, &token); - if (token.type == ScriptTokenType_CurlyClose || token.type == ScriptTokenType_End) { - // Empty scope. - goto BlockEnd; + return token; +} + +static ScriptToken read_consume(ScriptReadContext* ctx) { + ScriptToken token; + ctx->input = script_lex(ctx->input, g_stringtable, &token); + return token; +} + +static bool read_consume_if(ScriptReadContext* ctx, const ScriptTokenType type) { + ScriptToken token; + const String rem = script_lex(ctx->input, g_stringtable, &token); + if (token.type == type) { + ctx->input = rem; + return true; + } + return false; +} + +static ScriptReadResult read_expr(ScriptReadContext*, OpPrecedence minPrecedence); + +static bool read_is_block_end(const ScriptTokenType type) { + return type == ScriptTokenType_End || type == ScriptTokenType_CurlyClose; +} + +static ScriptReadResult read_expr_block(ScriptReadContext* ctx) { + ScriptExpr exprs[script_block_size_max]; + u32 exprCount = 0; + + if (read_is_block_end(read_peek(ctx).type)) { + goto BlockEnd; // Empty block. } BlockNext: @@ -183,28 +358,14 @@ static ScriptReadResult read_expr_block(ScriptReadContext* ctx, const ScriptBloc } exprs[exprCount++] = arg.expr; - const String remInput = script_lex(ctx->input, null, &token); - if (token.type == ScriptTokenType_SemiColon) { - ctx->input = remInput; // Consume the semi. - - script_lex(ctx->input, null, &token); - if (token.type == ScriptTokenType_End) { - ctx->input = string_empty; - goto BlockEnd; - } - if (type == ScriptBlockType_Scope && token.type == ScriptTokenType_CurlyClose) { + if (read_consume_if(ctx, ScriptTokenType_SemiColon)) { + if (read_is_block_end(read_peek(ctx).type)) { goto BlockEnd; } goto BlockNext; } BlockEnd: - if (type == ScriptBlockType_Scope) { - ctx->input = script_lex(ctx->input, null, &token); - if (UNLIKELY(token.type != ScriptTokenType_CurlyClose)) { - return script_err(ScriptError_UnterminatedScope); - } - } switch (exprCount) { case 0: return script_expr(script_add_value(ctx->doc, script_null())); @@ -215,6 +376,42 @@ static ScriptReadResult read_expr_block(ScriptReadContext* ctx, const ScriptBloc } } +/** + * NOTE: Caller is expected to consume the opening curly-brace. + */ +static ScriptReadResult read_expr_scope_block(ScriptReadContext* ctx) { + ScriptScope scope = {0}; + script_scope_push(ctx, &scope); + + const ScriptReadResult res = read_expr_block(ctx); + + diag_assert(&scope == script_scope_tail(ctx)); + script_scope_pop(ctx); + + if (UNLIKELY(res.type == ScriptResult_Fail)) { + return res; + } + + const ScriptToken token = read_consume(ctx); + if (UNLIKELY(token.type != ScriptTokenType_CurlyClose)) { + return script_err(ScriptError_UnterminatedScope); + } + + return res; +} + +static ScriptReadResult read_expr_scope_single(ScriptReadContext* ctx, const OpPrecedence prec) { + ScriptScope scope = {0}; + script_scope_push(ctx, &scope); + + const ScriptReadResult res = read_expr(ctx, prec); + + diag_assert(&scope == script_scope_tail(ctx)); + script_scope_pop(ctx); + + return res; +} + /** * NOTE: Caller is expected to consume the opening parenthesis. */ @@ -223,8 +420,7 @@ static ScriptReadResult read_expr_paren(ScriptReadContext* ctx) { if (UNLIKELY(res.type == ScriptResult_Fail)) { return res; } - ScriptToken closeToken; - ctx->input = script_lex(ctx->input, null, &closeToken); + const ScriptToken closeToken = read_consume(ctx); if (UNLIKELY(closeToken.type != ScriptTokenType_ParenClose)) { return script_err(ScriptError_UnclosedParenthesizedExpression); } @@ -245,17 +441,18 @@ typedef struct { #define script_args_err(_ERR_) \ (ScriptArgsResult) { .type = ScriptResult_Fail, .error = (_ERR_) } +static bool read_is_args_end(const ScriptTokenType type) { + return type == ScriptTokenType_End || type == ScriptTokenType_ParenClose; +} + /** * NOTE: Caller is expected to consume the opening parenthesis. */ static ScriptArgsResult read_args(ScriptReadContext* ctx, ScriptExpr out[script_args_max]) { - ScriptToken token; - u32 count = 0; + u32 count = 0; - script_lex(ctx->input, null, &token); - if (token.type == ScriptTokenType_ParenClose || token.type == ScriptTokenType_End) { - // Empty argument list. - goto ArgEnd; + if (read_is_args_end(read_peek(ctx).type)) { + goto ArgEnd; // Empty argument list. } ArgNext: @@ -268,26 +465,120 @@ static ScriptArgsResult read_args(ScriptReadContext* ctx, ScriptExpr out[script_ } out[count++] = arg.expr; - const String remInput = script_lex(ctx->input, null, &token); - if (token.type == ScriptTokenType_Comma) { - ctx->input = remInput; // Consume the comma. + if (read_consume_if(ctx, ScriptTokenType_Comma)) { goto ArgNext; } -ArgEnd: - ctx->input = script_lex(ctx->input, null, &token); - if (UNLIKELY(token.type != ScriptTokenType_ParenClose)) { +ArgEnd:; + const ScriptToken endToken = read_consume(ctx); + if (UNLIKELY(endToken.type != ScriptTokenType_ParenClose)) { return script_args_err(ScriptError_UnterminatedArgumentList); } return script_args_success(count); } -static ScriptReadResult read_expr_constant(ScriptReadContext* ctx, const StringHash identifier) { - const ScriptValId constValId = script_doc_constant_lookup(ctx->doc, identifier); - if (LIKELY(!sentinel_check(constValId))) { - return script_expr(script_add_value_id(ctx->doc, constValId)); +static ScriptReadResult read_expr_var_declare(ScriptReadContext* ctx) { + const ScriptToken token = read_consume(ctx); + if (UNLIKELY(token.type != ScriptTokenType_Identifier)) { + return script_err(ScriptError_VariableIdentifierMissing); + } + if (script_builtin_const_lookup(token.val_identifier)) { + return script_err(ScriptError_VariableIdentifierConflicts); + } + if (script_var_lookup(ctx, token.val_identifier)) { + return script_err(ScriptError_VariableIdentifierConflicts); + } + if (script_builtin_func_exists(token.val_identifier)) { + return script_err(ScriptError_VariableIdentifierConflicts); + } + + ScriptExpr valExpr; + if (read_consume_if(ctx, ScriptTokenType_Eq)) { + const ScriptReadResult res = read_expr(ctx, OpPrecedence_Assignment); + if (UNLIKELY(res.type == ScriptResult_Fail)) { + return script_err(res.error); + } + valExpr = res.expr; + } else { + valExpr = script_add_value(ctx->doc, script_null()); + } + + ScriptVarId varId; + if (!script_var_declare(ctx, token.val_identifier, &varId)) { + return script_err(ScriptError_VariableLimitExceeded); } - return script_err(ScriptError_NoConstantFoundForIdentifier); + + return script_expr(script_add_var_store(ctx->doc, varId, valExpr)); +} + +static ScriptReadResult read_expr_var_lookup(ScriptReadContext* ctx, const StringHash identifier) { + const ScriptBuiltinConst* builtin = script_builtin_const_lookup(identifier); + if (builtin) { + return script_expr(script_add_value(ctx->doc, builtin->val)); + } + const ScriptVarMeta* var = script_var_lookup(ctx, identifier); + if (var) { + return script_expr(script_add_var_load(ctx->doc, var->varSlot)); + } + return script_err(ScriptError_NoVariableFoundForIdentifier); +} + +static ScriptReadResult read_expr_var_assign(ScriptReadContext* ctx, const StringHash identifier) { + const ScriptVarMeta* var = script_var_lookup(ctx, identifier); + if (UNLIKELY(!var)) { + return script_err(ScriptError_NoVariableFoundForIdentifier); + } + + const ScriptReadResult res = read_expr(ctx, OpPrecedence_Assignment); + if (UNLIKELY(res.type == ScriptResult_Fail)) { + return script_err(res.error); + } + + return script_expr(script_add_var_store(ctx->doc, var->varSlot, res.expr)); +} + +static ScriptReadResult read_expr_var_modify( + ScriptReadContext* ctx, const StringHash identifier, const ScriptTokenType type) { + const ScriptVarMeta* var = script_var_lookup(ctx, identifier); + if (UNLIKELY(!var)) { + return script_err(ScriptError_NoVariableFoundForIdentifier); + } + + const bool newScope = type == ScriptTokenType_QMarkQMarkEq; + const ScriptReadResult val = newScope ? read_expr_scope_single(ctx, OpPrecedence_Assignment) + : read_expr(ctx, OpPrecedence_Assignment); + if (UNLIKELY(val.type == ScriptResult_Fail)) { + return val; + } + + const ScriptExpr loadExpr = script_add_var_load(ctx->doc, var->varSlot); + const ScriptIntrinsic itr = token_op_binary_modify(type); + const ScriptExpr intrArgs[] = {loadExpr, val.expr}; + const ScriptExpr itrExpr = script_add_intrinsic(ctx->doc, itr, intrArgs); + return script_expr(script_add_var_store(ctx->doc, var->varSlot, itrExpr)); +} + +static ScriptReadResult read_expr_mem_store(ScriptReadContext* ctx, const StringHash key) { + const ScriptReadResult val = read_expr(ctx, OpPrecedence_Assignment); + if (UNLIKELY(val.type == ScriptResult_Fail)) { + return val; + } + return script_expr(script_add_mem_store(ctx->doc, key, val.expr)); +} + +static ScriptReadResult +read_expr_mem_modify(ScriptReadContext* ctx, const StringHash key, const ScriptTokenType type) { + const bool newScope = type == ScriptTokenType_QMarkQMarkEq; + const ScriptReadResult val = newScope ? read_expr_scope_single(ctx, OpPrecedence_Assignment) + : read_expr(ctx, OpPrecedence_Assignment); + if (UNLIKELY(val.type == ScriptResult_Fail)) { + return val; + } + const ScriptExpr loadExpr = script_add_mem_load(ctx->doc, key); + const ScriptIntrinsic itr = token_op_binary_modify(type); + const ScriptExpr intrArgs[] = {loadExpr, val.expr}; + const ScriptExpr itrExpr = script_add_intrinsic(ctx->doc, itr, intrArgs); + return script_expr(script_add_mem_store(ctx->doc, key, itrExpr)); } /** @@ -300,46 +591,49 @@ static ScriptReadResult read_expr_function(ScriptReadContext* ctx, const StringH return script_err(argsRes.error); } - array_for_t(g_scriptIntrinsicFuncs, ScriptFunction, func) { - if (func->nameHash != identifier) { - continue; - } - if (argsRes.argCount != script_intrinsic_arg_count(func->intr)) { - continue; - } - return script_expr(script_add_intrinsic(ctx->doc, func->intr, args)); + const ScriptBuiltinFunc* builtin = script_builtin_func_lookup(identifier, argsRes.argCount); + if (builtin) { + return script_expr(script_add_intrinsic(ctx->doc, builtin->intr, args)); } + if (script_builtin_func_exists(identifier)) { + return script_err(ScriptError_IncorrectArgumentCountForBuiltinFunction); + } + return script_err(ScriptError_NoFunctionFoundForIdentifier); } static ScriptReadResult read_expr_if(ScriptReadContext* ctx) { - ScriptToken token; - ctx->input = script_lex(ctx->input, g_stringtable, &token); + const ScriptToken token = read_consume(ctx); if (UNLIKELY(token.type != ScriptTokenType_ParenOpen)) { return script_err(ScriptError_InvalidConditionCountForIf); } + // NOTE: Add a new scope so variables defined in the condition are only available in the branches. + ScriptScope scope = {0}; + script_scope_push(ctx, &scope); + ScriptExpr conditions[script_args_max]; const ScriptArgsResult conditionRes = read_args(ctx, conditions); if (UNLIKELY(conditionRes.type == ScriptResult_Fail)) { + script_scope_pop(ctx); return script_err(conditionRes.error); } if (UNLIKELY(conditionRes.argCount != 1)) { + script_scope_pop(ctx); return script_err(ScriptError_InvalidConditionCountForIf); } - const ScriptReadResult b1 = read_expr(ctx, OpPrecedence_None); + const ScriptReadResult b1 = read_expr_scope_single(ctx, OpPrecedence_Conditional); if (UNLIKELY(b1.type == ScriptResult_Fail)) { + script_scope_pop(ctx); 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); + ScriptExpr b2Expr; + if (read_consume_if(ctx, ScriptTokenType_Else)) { + const ScriptReadResult b2 = read_expr_scope_single(ctx, OpPrecedence_Conditional); if (UNLIKELY(b2.type == ScriptResult_Fail)) { + script_scope_pop(ctx); return b2; } b2Expr = b2.expr; @@ -347,23 +641,25 @@ static ScriptReadResult read_expr_if(ScriptReadContext* ctx) { b2Expr = script_add_value(ctx->doc, script_null()); } + diag_assert(&scope == script_scope_tail(ctx)); + script_scope_pop(ctx); + 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); + const ScriptReadResult b1 = read_expr_scope_single(ctx, OpPrecedence_Conditional); if (UNLIKELY(b1.type == ScriptResult_Fail)) { return b1; } - ScriptToken token; - ctx->input = script_lex(ctx->input, g_stringtable, &token); + const ScriptToken token = read_consume(ctx); if (UNLIKELY(token.type != ScriptTokenType_Colon)) { return script_err(ScriptError_MissingColonInSelectExpression); } - const ScriptReadResult b2 = read_expr(ctx, OpPrecedence_None); + const ScriptReadResult b2 = read_expr_scope_single(ctx, OpPrecedence_Conditional); if (UNLIKELY(b2.type == ScriptResult_Fail)) { return b2; } @@ -372,6 +668,35 @@ static ScriptReadResult read_expr_select(ScriptReadContext* ctx, const ScriptExp return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_If, intrArgs)); } +static ScriptReadResult read_expr_logic_and(ScriptReadContext* ctx, const ScriptExpr lhs) { + const ScriptReadResult rhs = read_expr_scope_single(ctx, OpPrecedence_Logical); + if (UNLIKELY(rhs.type == ScriptResult_Fail)) { + return rhs; + } + const ScriptExpr falseExpr = script_add_value(ctx->doc, script_bool(false)); + const ScriptExpr intrArgs[] = {lhs, rhs.expr, falseExpr}; + return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_If, intrArgs)); +} + +static ScriptReadResult read_expr_logic_or(ScriptReadContext* ctx, const ScriptExpr lhs) { + const ScriptReadResult rhs = read_expr_scope_single(ctx, OpPrecedence_Logical); + if (UNLIKELY(rhs.type == ScriptResult_Fail)) { + return rhs; + } + const ScriptExpr trueExpr = script_add_value(ctx->doc, script_bool(true)); + const ScriptExpr intrArgs[] = {lhs, trueExpr, rhs.expr}; + return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_If, intrArgs)); +} + +static ScriptReadResult read_expr_null_coalescing(ScriptReadContext* ctx, const ScriptExpr lhs) { + const ScriptReadResult rhs = read_expr_scope_single(ctx, OpPrecedence_Logical); + if (UNLIKELY(rhs.type == ScriptResult_Fail)) { + return rhs; + } + const ScriptExpr intrArgs[] = {lhs, rhs.expr}; + return script_expr(script_add_intrinsic(ctx->doc, ScriptIntrinsic_NullCoalescing, intrArgs)); +} + static ScriptReadResult read_expr_primary(ScriptReadContext* ctx) { ScriptToken token; ctx->input = script_lex(ctx->input, g_stringtable, &token); @@ -386,23 +711,38 @@ static ScriptReadResult read_expr_primary(ScriptReadContext* ctx) { * Scope. */ case ScriptTokenType_CurlyOpen: - return read_expr_block(ctx, ScriptBlockType_Scope); + return read_expr_scope_block(ctx); /** * Keywords. */ case ScriptTokenType_If: return read_expr_if(ctx); + case ScriptTokenType_Var: + return read_expr_var_declare(ctx); /** * Identifiers. */ case ScriptTokenType_Identifier: { ScriptToken nextToken; const String remInput = script_lex(ctx->input, null, &nextToken); - if (nextToken.type == ScriptTokenType_ParenOpen) { - ctx->input = remInput; // Consume the opening parenthesis. + switch (nextToken.type) { + case ScriptTokenType_ParenOpen: + ctx->input = remInput; // Consume the 'nextToken'. return read_expr_function(ctx, token.val_identifier); + case ScriptTokenType_Eq: + ctx->input = remInput; // Consume the 'nextToken'. + return read_expr_var_assign(ctx, token.val_identifier); + case ScriptTokenType_PlusEq: + case ScriptTokenType_MinusEq: + case ScriptTokenType_StarEq: + case ScriptTokenType_SlashEq: + case ScriptTokenType_PercentEq: + case ScriptTokenType_QMarkQMarkEq: + ctx->input = remInput; // Consume the 'nextToken'. + return read_expr_var_modify(ctx, token.val_key, nextToken.type); + default: + return read_expr_var_lookup(ctx, token.val_identifier); } - return read_expr_constant(ctx, token.val_identifier); } /** * Unary operators. @@ -431,31 +771,17 @@ static ScriptReadResult read_expr_primary(ScriptReadContext* ctx) { ScriptToken nextToken; const String remInput = script_lex(ctx->input, null, &nextToken); switch (nextToken.type) { - case ScriptTokenType_Eq: { - ctx->input = remInput; // Consume the 'nextToken'. - const ScriptReadResult val = read_expr(ctx, OpPrecedence_Assignment); - if (UNLIKELY(val.type == ScriptResult_Fail)) { - return val; - } - return script_expr(script_add_mem_store(ctx->doc, token.val_key, val.expr)); - } + case ScriptTokenType_Eq: + ctx->input = remInput; // Consume the 'nextToken'. + return read_expr_mem_store(ctx, token.val_key); case ScriptTokenType_PlusEq: case ScriptTokenType_MinusEq: case ScriptTokenType_StarEq: case ScriptTokenType_SlashEq: case ScriptTokenType_PercentEq: - case ScriptTokenType_QMarkQMarkEq: { - ctx->input = remInput; // Consume the 'nextToken'. - const ScriptReadResult val = read_expr(ctx, OpPrecedence_Assignment); - if (UNLIKELY(val.type == ScriptResult_Fail)) { - return val; - } - const ScriptExpr loadExpr = script_add_mem_load(ctx->doc, token.val_key); - const ScriptIntrinsic itr = token_op_binary_modify(nextToken.type); - const ScriptExpr intrArgs[] = {loadExpr, val.expr}; - const ScriptExpr itrExpr = script_add_intrinsic(ctx->doc, itr, intrArgs); - return script_expr(script_add_mem_store(ctx->doc, token.val_key, itrExpr)); - } + case ScriptTokenType_QMarkQMarkEq: + ctx->input = remInput; // Consume the 'nextToken'. + return read_expr_mem_modify(ctx, token.val_key, nextToken.type); default: return script_expr(script_add_mem_load(ctx->doc, token.val_key)); } @@ -521,10 +847,7 @@ static ScriptReadResult read_expr(ScriptReadContext* ctx, const OpPrecedence min case ScriptTokenType_Minus: case ScriptTokenType_Star: case ScriptTokenType_Slash: - case ScriptTokenType_Percent: - case ScriptTokenType_AmpAmp: - case ScriptTokenType_PipePipe: - case ScriptTokenType_QMarkQMark: { + case ScriptTokenType_Percent: { const ScriptReadResult rhs = read_expr(ctx, opPrecedence); if (UNLIKELY(rhs.type == ScriptResult_Fail)) { return rhs; @@ -533,6 +856,15 @@ static ScriptReadResult read_expr(ScriptReadContext* ctx, const OpPrecedence min const ScriptExpr intrArgs[] = {res.expr, rhs.expr}; res = script_expr(script_add_intrinsic(ctx->doc, intr, intrArgs)); } break; + case ScriptTokenType_AmpAmp: + res = read_expr_logic_and(ctx, res.expr); + break; + case ScriptTokenType_PipePipe: + res = read_expr_logic_or(ctx, res.expr); + break; + case ScriptTokenType_QMarkQMark: + res = read_expr_null_coalescing(ctx, res.expr); + break; default: diag_assert_fail("Invalid operator token"); UNREACHABLE @@ -550,9 +882,7 @@ static void script_read_init() { } thread_spinlock_lock(&g_initLock); if (!g_init) { - array_for_t(g_scriptIntrinsicFuncs, ScriptFunction, func) { - func->nameHash = string_hash(func->name); - } + script_builtin_init(); g_init = true; } thread_spinlock_unlock(&g_initLock); @@ -561,15 +891,18 @@ static void script_read_init() { void script_read(ScriptDoc* doc, const String str, ScriptReadResult* res) { script_read_init(); - ScriptReadContext ctx = { - .doc = doc, - .input = str, + ScriptScope scopeRoot = {0}; + ScriptReadContext ctx = { + .doc = doc, + .input = str, + .scopeRoot = &scopeRoot, }; - *res = read_expr_block(&ctx, ScriptBlockType_Root); + script_var_free_all(&ctx); - ScriptToken token; - script_lex(ctx.input, null, &token); - if (UNLIKELY(res->type == ScriptResult_Success && token.type != ScriptTokenType_End)) { + *res = read_expr_block(&ctx); + + const ScriptToken endToken = read_peek(&ctx); + if (UNLIKELY(res->type == ScriptResult_Success && endToken.type != ScriptTokenType_End)) { *res = script_err(ScriptError_UnexpectedTokenAfterExpression); } } diff --git a/libs/script/test/test_eval.c b/libs/script/test/test_eval.c index e04c3be8b..ec4cdecc6 100644 --- a/libs/script/test/test_eval.c +++ b/libs/script/test/test_eval.c @@ -53,13 +53,18 @@ spec(eval) { {string_static("vector_y(vector(1, true, 3))"), script_null()}, {string_static("vector_z(vector(1, true, 3))"), script_null()}, - // Memory loads. + // Variable access. + {string_static("var i"), script_null()}, + {string_static("var i = 42"), script_number(42)}, + {string_static("var i; i"), script_null()}, + {string_static("var i = 42; i"), script_number(42)}, + {string_static("{var i = 42}; var i = 1; i"), script_number(1)}, + + // Memory access. {string_static("$v1"), script_bool(true)}, {string_static("$v2"), script_number(1337)}, {string_static("$v3"), script_null()}, {string_static("$non_existent"), script_null()}, - - // Memory stores. {string_static("$v4 = true"), script_bool(true)}, // Arithmetic. diff --git a/libs/script/test/test_lex.c b/libs/script/test/test_lex.c index 477b0c71b..46a31092b 100644 --- a/libs/script/test/test_lex.c +++ b/libs/script/test/test_lex.c @@ -116,6 +116,7 @@ spec(lex) { {string_static("if"), tok_simple(If)}, {string_static("else"), tok_simple(Else)}, + {string_static("var"), tok_simple(Var)}, {string_static("&"), tok_err(InvalidChar)}, {string_static("|"), tok_err(InvalidChar)}, diff --git a/libs/script/test/test_read.c b/libs/script/test/test_read.c index 8746415a4..a0cac0518 100644 --- a/libs/script/test/test_read.c +++ b/libs/script/test/test_read.c @@ -1,6 +1,7 @@ #include "check_spec.h" #include "core_alloc.h" #include "core_array.h" +#include "script_error.h" #include "script_read.h" #include "utils_internal.h" @@ -110,6 +111,20 @@ spec(read) { " [value: 2]\n" " [value: 3]"), }, + { + string_static("if(true) {2} else {3}"), + string_static("[intrinsic: if]\n" + " [value: true]\n" + " [value: 2]\n" + " [value: 3]"), + }, + { + string_static("if(true) {} else {}"), + string_static("[intrinsic: if]\n" + " [value: true]\n" + " [value: null]\n" + " [value: null]"), + }, { string_static("if(false) 2 else if(true) 3"), string_static("[intrinsic: if]\n" @@ -120,6 +135,28 @@ spec(read) { " [value: 3]\n" " [value: null]"), }, + { + string_static("if(var i = 42) { i } else { i }"), + string_static("[intrinsic: if]\n" + " [var-store: 0]\n" + " [value: 42]\n" + " [var-load: 0]\n" + " [var-load: 0]"), + }, + { + string_static("if(var i = 1) i; if(var i = 2) i"), + string_static("[block]\n" + " [intrinsic: if]\n" + " [var-store: 0]\n" + " [value: 1]\n" + " [var-load: 0]\n" + " [value: null]\n" + " [intrinsic: if]\n" + " [var-store: 0]\n" + " [value: 2]\n" + " [var-load: 0]\n" + " [value: null]"), + }, // Unary expressions. { @@ -208,16 +245,36 @@ spec(read) { }, { string_static("true && false"), - string_static("[intrinsic: logic-and]\n" + string_static("[intrinsic: if]\n" " [value: true]\n" + " [value: false]\n" + " [value: false]"), + }, + { + string_static("true && 2 * 4"), + string_static("[intrinsic: if]\n" + " [value: true]\n" + " [intrinsic: mul]\n" + " [value: 2]\n" + " [value: 4]\n" " [value: false]"), }, { string_static("true || false"), - string_static("[intrinsic: logic-or]\n" + string_static("[intrinsic: if]\n" + " [value: true]\n" " [value: true]\n" " [value: false]"), }, + { + string_static("true || 2 * 4"), + string_static("[intrinsic: if]\n" + " [value: true]\n" + " [value: true]\n" + " [intrinsic: mul]\n" + " [value: 2]\n" + " [value: 4]"), + }, { string_static("null ?? true"), string_static("[intrinsic: null-coalescing]\n" @@ -247,7 +304,69 @@ spec(read) { " [value: 4]"), }, - // Modify expressions. + // Variable modify expressions. + { + string_static("var a; a += 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: add]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a -= 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: sub]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a *= 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: mul]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a /= 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: div]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a %= 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: mod]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a ?\?= 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [intrinsic: null-coalescing]\n" + " [var-load: 0]\n" + " [value: 42]"), + }, + + // Memory modify expressions. { string_static("$hello += 42"), string_static("[mem-store: $3944927369]\n" @@ -391,7 +510,8 @@ spec(read) { { string_static("true || {$a = 1; false}; $a"), string_static("[block]\n" - " [intrinsic: logic-or]\n" + " [intrinsic: if]\n" + " [value: true]\n" " [value: true]\n" " [block]\n" " [mem-store: $3645546703]\n" @@ -454,6 +574,100 @@ spec(read) { " [value: 1]\n" " [value: 2]"), }, + + // Variables. + { + string_static("var a"), + string_static("[var-store: 0]\n" + " [value: null]"), + }, + { + string_static("var a; a = 42"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-store: 0]\n" + " [value: 42]"), + }, + { + string_static("var a; a"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: null]\n" + " [var-load: 0]"), + }, + { + string_static("var a = 42"), + string_static("[var-store: 0]\n" + " [value: 42]"), + }, + { + string_static("var a = 1; var b = 2; var c = 3; var d = 4"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 1]\n" + " [var-store: 1]\n" + " [value: 2]\n" + " [var-store: 2]\n" + " [value: 3]\n" + " [var-store: 3]\n" + " [value: 4]"), + }, + { + string_static("{var a = 1}; {var b = 2}; {var c = 3}; {var d = 4}"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 1]\n" + " [var-store: 0]\n" + " [value: 2]\n" + " [var-store: 0]\n" + " [value: 3]\n" + " [var-store: 0]\n" + " [value: 4]"), + }, + { + string_static("{var a = 1}; {var a = 2}; {var a = 3}; {var a = 4}"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 1]\n" + " [var-store: 0]\n" + " [value: 2]\n" + " [var-store: 0]\n" + " [value: 3]\n" + " [var-store: 0]\n" + " [value: 4]"), + }, + { + string_static("var a = 42; {a}"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 42]\n" + " [var-load: 0]"), + }, + { + string_static("var a = 42; {a * a}"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 42]\n" + " [intrinsic: mul]\n" + " [var-load: 0]\n" + " [var-load: 0]"), + }, + { + string_static("var a = 1; { var b = 2; { var c = 3; a; b; c; } }"), + string_static("[block]\n" + " [var-store: 0]\n" + " [value: 1]\n" + " [block]\n" + " [var-store: 1]\n" + " [value: 2]\n" + " [block]\n" + " [var-store: 2]\n" + " [value: 3]\n" + " [var-load: 0]\n" + " [var-load: 1]\n" + " [var-load: 2]"), + }, }; for (u32 i = 0; i != array_elems(g_testData); ++i) { @@ -470,10 +684,12 @@ spec(read) { String input; ScriptError expected; } g_testData[] = { - {string_static("hello"), ScriptError_NoConstantFoundForIdentifier}, + {string_static("hello"), ScriptError_NoVariableFoundForIdentifier}, {string_static("<"), ScriptError_InvalidPrimaryExpression}, + {string_static("1 &&"), ScriptError_MissingPrimaryExpression}, + {string_static("1 ||"), ScriptError_MissingPrimaryExpression}, {string_static("1 <"), ScriptError_MissingPrimaryExpression}, - {string_static("1 < hello"), ScriptError_NoConstantFoundForIdentifier}, + {string_static("1 < hello"), ScriptError_NoVariableFoundForIdentifier}, {string_static(")"), ScriptError_InvalidPrimaryExpression}, {string_static("("), ScriptError_MissingPrimaryExpression}, {string_static("(1"), ScriptError_UnclosedParenthesizedExpression}, @@ -487,13 +703,14 @@ spec(read) { {string_static("1 ?"), ScriptError_MissingPrimaryExpression}, {string_static("1?1"), ScriptError_MissingColonInSelectExpression}, {string_static("1 ? 1"), ScriptError_MissingColonInSelectExpression}, - {string_static("1 ? foo"), ScriptError_NoConstantFoundForIdentifier}, - {string_static("1 ? 1 : foo"), ScriptError_NoConstantFoundForIdentifier}, - {string_static("distance"), ScriptError_NoConstantFoundForIdentifier}, + {string_static("1 ? foo"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("1 ? 1 : foo"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("distance"), ScriptError_NoVariableFoundForIdentifier}, {string_static("distance("), ScriptError_UnterminatedArgumentList}, {string_static("distance(,"), ScriptError_InvalidPrimaryExpression}, {string_static("distance(1 2"), ScriptError_UnterminatedArgumentList}, {string_static("distance(1,"), ScriptError_MissingPrimaryExpression}, + {string_static("distance(1,2,3)"), ScriptError_IncorrectArgumentCountForBuiltinFunction}, {string_static("hello()"), ScriptError_NoFunctionFoundForIdentifier}, {string_static("hello(null)"), ScriptError_NoFunctionFoundForIdentifier}, {string_static("hello(1,2,3,4,5)"), ScriptError_NoFunctionFoundForIdentifier}, @@ -511,19 +728,43 @@ spec(read) { {string_static("if(1,2)"), ScriptError_InvalidConditionCountForIf}, {string_static("if(1)"), ScriptError_MissingPrimaryExpression}, {string_static("if(1) 1 else"), ScriptError_MissingPrimaryExpression}, + {string_static("if(1) 1; 2 else 3"), ScriptError_UnexpectedTokenAfterExpression}, + {string_static("if(1) var i = 42 else i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("if(1) 2; else 2;"), ScriptError_InvalidPrimaryExpression}, + {string_static("if(var i = 42) {}; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("1 ? var i = 42 : i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("false && var i = 42; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("true || var i = 42; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("1 ?? var i = 42; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("$test \?\?= var i = 42; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("var a; a \?\?= var i = 42; i"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("var i; { var i = 99 }"), ScriptError_VariableIdentifierConflicts}, + {string_static("var"), ScriptError_VariableIdentifierMissing}, + {string_static("var pi"), ScriptError_VariableIdentifierConflicts}, + {string_static("var random"), ScriptError_VariableIdentifierConflicts}, + {string_static("var a; var a"), ScriptError_VariableIdentifierConflicts}, + {string_static("var a ="), ScriptError_MissingPrimaryExpression}, + {string_static("var a = a"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("b ="), ScriptError_NoVariableFoundForIdentifier}, + {string_static("var b; b ="), ScriptError_MissingPrimaryExpression}, + {string_static("a"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("{var a}; a"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("a += 1"), ScriptError_NoVariableFoundForIdentifier}, + {string_static("var a; a +="), ScriptError_MissingPrimaryExpression}, }; for (u32 i = 0; i != array_elems(g_testData); ++i) { ScriptReadResult res; script_read(doc, g_testData[i].input, &res); - check_require_msg(res.type == ScriptResult_Fail, "Read succeeded (index: {})", fmt_int(i)); + check_require_msg( + res.type == ScriptResult_Fail, "Read succeeded [{}]", fmt_text(g_testData[i].input)); check_msg( res.error == g_testData[i].expected, - "{} == {} (index: {})", + "{} == {} [{}]", script_error_fmt(res.error), script_error_fmt(g_testData[i].expected), - fmt_int(i)); + fmt_text(g_testData[i].input)); } } @@ -555,5 +796,20 @@ spec(read) { dynstring_destroy(&str); } + it("fails when using too many variables") { + DynString str = dynstring_create(g_alloc_scratch, 1024); + for (u32 i = 0; i != (script_var_count + 1); ++i) { + dynstring_append(&str, fmt_write_scratch("var v{} = 42;", fmt_int(i))); + } + + ScriptReadResult res; + script_read(doc, dynstring_view(&str), &res); + + check_require(res.type == ScriptResult_Fail); + check_eq_int(res.error, ScriptError_VariableLimitExceeded); + + dynstring_destroy(&str); + } + teardown() { script_destroy(doc); } } diff --git a/libs/script/test/test_val.c b/libs/script/test/test_val.c index c92818690..700c364f1 100644 --- a/libs/script/test/test_val.c +++ b/libs/script/test/test_val.c @@ -1,5 +1,6 @@ #include "check_spec.h" #include "core_array.h" +#include "core_stringtable.h" #include "core_time.h" #include "script_val.h" @@ -122,9 +123,12 @@ spec(val) { {script_time(time_hour), string_lit("3600")}, {script_time(time_milliseconds(500)), string_lit("0.5")}, {script_time(time_milliseconds(42)), string_lit("0.042")}, - {script_string(string_hash_lit("Hello World")), string_lit("#F185CECD")}, + {script_string(string_hash_lit("Hello World")), string_lit("\"Hello World\"")}, }; + // NOTE: Normally we expect the script lexer to register the strings. + stringtable_add(g_stringtable, string_lit("Hello World")); + for (u32 i = 0; i != array_elems(testData); ++i) { check_eq_string(script_val_str_scratch(testData[i].value), testData[i].expected); }