Skip to content

Commit

Permalink
script: Script variable support (#577)
Browse files Browse the repository at this point in the history
* script: Fix value tests

* repl: Add debug feature to output tokens

* script: Refactor builtin funcs

* script: Refactor builtin function definition

* repl: Add flag to output expr stats

* repl: List script value count

* script: De-duplicate script values

* script: Lex var keyword

* script: Variable runtime support

* script: Avoid inlining script eval func

* script: Rename builtin vars to consts

* script: Reduce max variable count to 16

* repl: Use heap for render buffer

* script: Add variable declare support

* script: Support var initializer

* script: Add variable eval tests

* script: Add var read tests

* script: Increase variable test coverage

* script: Add nested scopes test

* script: Add variable limit test

* script: Increase var test coverage

* script: Fail if var conflicts with func

* script: Add error for arg count mismatch on builtin

* script: Refactor script scopes

* script: Add scopes for if branches

* script: Add scopes for select expr

* script: Fix incorrect precedence on var declare

* script: Expand scope test coverage

* script: Refactor scope handling

* script: Refactor token consuming

* script: Reimplement logic and / or as if exprs

* script: Fix unsafe var access in logic ops

* script: Fix unsafe var access in ?? op

* script: Fix unsafe mem access in ??=

* script: Fix unsafe var access in var decl

* script: Respect precedence in scoped exprs

* script: Add script input on test failure

* script: Fix incorrect error in block scopes

* script: Add variable assign support

* script: Refactor identifier parsing

* script: Refactor mem store / modify ops

* script: Add variable modify support

* script: Add scope around if statements
  • Loading branch information
BastianBlokland authored Sep 10, 2023
1 parent c81dea4 commit 3605494
Show file tree
Hide file tree
Showing 16 changed files with 947 additions and 252 deletions.
86 changes: 80 additions & 6 deletions apps/utilities/repl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)));
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
9 changes: 9 additions & 0 deletions libs/script/include/script_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -17,6 +21,8 @@ typedef struct sScriptDoc ScriptDoc;
*/
typedef enum {
ScriptExprType_Value,
ScriptExprType_VarLoad,
ScriptExprType_VarStore,
ScriptExprType_MemLoad,
ScriptExprType_MemStore,
ScriptExprType_Intrinsic,
Expand Down Expand Up @@ -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[]);
Expand All @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion libs/script/include/script_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions libs/script/include/script_intrinsic.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ typedef enum {
ScriptIntrinsic_LessOrEqual,
ScriptIntrinsic_Greater,
ScriptIntrinsic_GreaterOrEqual,
ScriptIntrinsic_LogicAnd,
ScriptIntrinsic_LogicOr,
ScriptIntrinsic_NullCoalescing,
ScriptIntrinsic_Add,
ScriptIntrinsic_Sub,
Expand Down
1 change: 1 addition & 0 deletions libs/script/include/script_lex.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
96 changes: 45 additions & 51 deletions libs/script/src/doc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include "core_array.h"
#include "core_diag.h"
#include "core_dynarray.h"
#include "core_math.h"

#include "doc_internal.h"

Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Loading

0 comments on commit 3605494

Please sign in to comment.