Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

script: Script variable support #577

Merged
merged 43 commits into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b008416
script: Fix value tests
BastianBlokland Sep 9, 2023
1a499b5
repl: Add debug feature to output tokens
BastianBlokland Sep 9, 2023
f81305d
script: Refactor builtin funcs
BastianBlokland Sep 9, 2023
73f2971
script: Refactor builtin function definition
BastianBlokland Sep 9, 2023
88f1df2
repl: Add flag to output expr stats
BastianBlokland Sep 9, 2023
70f932a
repl: List script value count
BastianBlokland Sep 9, 2023
f012573
script: De-duplicate script values
BastianBlokland Sep 9, 2023
2f036a9
script: Lex var keyword
BastianBlokland Sep 9, 2023
6606553
script: Variable runtime support
BastianBlokland Sep 9, 2023
1167343
script: Avoid inlining script eval func
BastianBlokland Sep 9, 2023
10d69e6
script: Rename builtin vars to consts
BastianBlokland Sep 9, 2023
14346ff
script: Reduce max variable count to 16
BastianBlokland Sep 9, 2023
4f2c8d9
repl: Use heap for render buffer
BastianBlokland Sep 9, 2023
f119cba
script: Add variable declare support
BastianBlokland Sep 9, 2023
fa2d13b
script: Support var initializer
BastianBlokland Sep 9, 2023
3642e66
script: Add variable eval tests
BastianBlokland Sep 9, 2023
ac83dbe
script: Add var read tests
BastianBlokland Sep 9, 2023
b233062
script: Increase variable test coverage
BastianBlokland Sep 9, 2023
657eb69
script: Add nested scopes test
BastianBlokland Sep 9, 2023
7206732
script: Add variable limit test
BastianBlokland Sep 9, 2023
6d021cb
script: Increase var test coverage
BastianBlokland Sep 9, 2023
048a573
script: Fail if var conflicts with func
BastianBlokland Sep 9, 2023
a1bcc0a
script: Add error for arg count mismatch on builtin
BastianBlokland Sep 9, 2023
55220f1
script: Refactor script scopes
BastianBlokland Sep 9, 2023
09eca20
script: Add scopes for if branches
BastianBlokland Sep 9, 2023
23baf07
script: Add scopes for select expr
BastianBlokland Sep 9, 2023
6a688ef
script: Fix incorrect precedence on var declare
BastianBlokland Sep 9, 2023
f108d88
script: Expand scope test coverage
BastianBlokland Sep 10, 2023
4e28bb9
script: Refactor scope handling
BastianBlokland Sep 10, 2023
3f15634
script: Refactor token consuming
BastianBlokland Sep 10, 2023
83f3aa2
script: Reimplement logic and / or as if exprs
BastianBlokland Sep 10, 2023
59ac511
script: Fix unsafe var access in logic ops
BastianBlokland Sep 10, 2023
0563e5b
script: Fix unsafe var access in ?? op
BastianBlokland Sep 10, 2023
530eeef
script: Fix unsafe mem access in ??=
BastianBlokland Sep 10, 2023
41a855f
script: Fix unsafe var access in var decl
BastianBlokland Sep 10, 2023
0bf597e
script: Respect precedence in scoped exprs
BastianBlokland Sep 10, 2023
f27b47e
script: Add script input on test failure
BastianBlokland Sep 10, 2023
5db7002
script: Fix incorrect error in block scopes
BastianBlokland Sep 10, 2023
01f76de
script: Add variable assign support
BastianBlokland Sep 10, 2023
cccc48e
script: Refactor identifier parsing
BastianBlokland Sep 10, 2023
fd1f088
script: Refactor mem store / modify ops
BastianBlokland Sep 10, 2023
253d14d
script: Add variable modify support
BastianBlokland Sep 10, 2023
b8e2144
script: Add scope around if statements
BastianBlokland Sep 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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