From 087998bcf9888225923cb916e8ee2d75a7c3e07a Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Sun, 10 May 2026 11:51:55 -0400 Subject: [PATCH 01/13] 25476: Begins implementing scope stack break --- debug-visualizers/AmalgamInternalTypes.natvis | 1 + src/Amalgam/evaluablenode/EvaluableNode.h | 17 ++++++++++++++++- src/Amalgam/interpreter/Interpreter.h | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/debug-visualizers/AmalgamInternalTypes.natvis b/debug-visualizers/AmalgamInternalTypes.natvis index 7ec58f465..d53d8d043 100644 --- a/debug-visualizers/AmalgamInternalTypes.natvis +++ b/debug-visualizers/AmalgamInternalTypes.natvis @@ -18,6 +18,7 @@ (attributes & 8) != 0 (attributes & 16) != 0 (attributes & 32) != 0 + (attributes & 64) != 0 attributes diff --git a/src/Amalgam/evaluablenode/EvaluableNode.h b/src/Amalgam/evaluablenode/EvaluableNode.h index 69bd9e42a..0bb94afa8 100644 --- a/src/Amalgam/evaluablenode/EvaluableNode.h +++ b/src/Amalgam/evaluablenode/EvaluableNode.h @@ -62,7 +62,9 @@ class EvaluableNode FREEABLE = 1 << 4, //if true, then known to be in use with regard to garbage collection KNOWN_TO_BE_IN_USE = 1 << 5, - ALL = HAS_EXTENDED_VALUE | NEED_CYCLE_CHECK | IDEMPOTENT | CONCURRENT | FREEABLE | KNOWN_TO_BE_IN_USE + //if true, then the scope stops at this node + SCOPE_BREAK = 1 << 6, + ALL = HAS_EXTENDED_VALUE | NEED_CYCLE_CHECK | IDEMPOTENT | CONCURRENT | FREEABLE | KNOWN_TO_BE_IN_USE | SCOPE_BREAK }; //constructors @@ -943,6 +945,19 @@ class EvaluableNode } #endif + //if true, then the scope stops at this node + __forceinline bool IsScopeBreak() + { + return HasAttribute(Attribute::SCOPE_BREAK); + } + + //TODO 25476: set this where appropriate and update documentation + //sets whether the scope stops at this node + __forceinline void SetScopeBreak(bool scope_break) + { + SetAttribute(Attribute::SCOPE_BREAK, scope_break); + } + //returns true if value contains an extended type __forceinline bool HasExtendedValue() { diff --git a/src/Amalgam/interpreter/Interpreter.h b/src/Amalgam/interpreter/Interpreter.h index c54638b17..c6bf7ab2d 100644 --- a/src/Amalgam/interpreter/Interpreter.h +++ b/src/Amalgam/interpreter/Interpreter.h @@ -300,10 +300,22 @@ class Interpreter #endif ) { + #ifdef MULTITHREAD_SUPPORT + bool hit_scope_break = false; + #endif //find appropriate context for symbol by walking up the stack for(auto it = rbegin(scopeStack); it != rend(scopeStack); ++it) { - auto &mcn = (*it)->GetMappedChildNodesReference(); + EvaluableNode *scope = *it; + if(scope->IsScopeBreak()) + { + #ifdef MULTITHREAD_SUPPORT + hit_scope_break = true; + #endif + break; + } + + auto &mcn = scope->GetMappedChildNodesReference(); if(auto found = mcn.find(symbol_sid); found != end(mcn)) { bool is_freeable = true; @@ -335,7 +347,7 @@ class Interpreter #ifdef MULTITHREAD_SUPPORT //need to search further down the stack if appropriate - if(!bottomOfScopeStack && callingInterpreter != nullptr) + if(!hit_scope_break && !bottomOfScopeStack && callingInterpreter != nullptr) { bool top_is_next_stack = (scopeStack.size() == 0); auto [value_destination, scope, top_of_stack, is_freeable] = callingInterpreter->GetScopeStackSymbolLocation( From fe06e48bacaa286da8a1c2bf478b0354b906cedb Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Mon, 11 May 2026 01:11:45 -0400 Subject: [PATCH 02/13] 25476: More implementation --- src/Amalgam/evaluablenode/EvaluableNode.h | 1 - src/Amalgam/interpreter/Interpreter.cpp | 6 +++++- src/Amalgam/interpreter/Interpreter.h | 3 ++- src/Amalgam/interpreter/OpcodesControlFlow.cpp | 7 +++++-- .../OpcodesVariableDefinitionAndModification.cpp | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Amalgam/evaluablenode/EvaluableNode.h b/src/Amalgam/evaluablenode/EvaluableNode.h index 0bb94afa8..b9b73bb00 100644 --- a/src/Amalgam/evaluablenode/EvaluableNode.h +++ b/src/Amalgam/evaluablenode/EvaluableNode.h @@ -951,7 +951,6 @@ class EvaluableNode return HasAttribute(Attribute::SCOPE_BREAK); } - //TODO 25476: set this where appropriate and update documentation //sets whether the scope stops at this node __forceinline void SetScopeBreak(bool scope_break) { diff --git a/src/Amalgam/interpreter/Interpreter.cpp b/src/Amalgam/interpreter/Interpreter.cpp index 0a10ad85b..b9ef32d94 100644 --- a/src/Amalgam/interpreter/Interpreter.cpp +++ b/src/Amalgam/interpreter/Interpreter.cpp @@ -74,7 +74,7 @@ EvaluableNodeReference Interpreter::ExecuteNode(EvaluableNode *en, return retval; } -void Interpreter::InterpretAndPushNewScopeStackNode(EvaluableNode *new_scope_node) +void Interpreter::InterpretAndPushNewScopeStackNode(EvaluableNode *new_scope_node, bool scope_break) { EvaluableNodeReference new_scope = EvaluableNodeReference::Null(); bool need_to_interpret_new_scope = false; @@ -171,6 +171,9 @@ void Interpreter::InterpretAndPushNewScopeStackNode(EvaluableNode *new_scope_nod new_scope->SetIsFreeable(true); //just in case a variable is added which needs cycle checks new_scope->SetNeedCycleCheck(true); + + new_scope->SetScopeBreak(scope_break); + scopeStack.push_back(new_scope); } @@ -225,6 +228,7 @@ EvaluableNode *Interpreter::GetScopeStackGivenDepth(size_t depth EvaluableNode *Interpreter::MakeCopyOfScopeStack() { + //TODO 25476: make sure scope opcode respects scope break EvaluableNode stack_top_holder(ENT_LIST); stack_top_holder.SetOrderedChildNodes(scopeStack); //set flags conservatively before copy diff --git a/src/Amalgam/interpreter/Interpreter.h b/src/Amalgam/interpreter/Interpreter.h index c6bf7ab2d..53cf06c5c 100644 --- a/src/Amalgam/interpreter/Interpreter.h +++ b/src/Amalgam/interpreter/Interpreter.h @@ -88,7 +88,8 @@ class Interpreter //interprets new_context_node and pushes a new scope onto the stack //new_context_node should be a unique associative array, //but if not, it will attempt to put an appropriate unique associative array on scopeStack - void InterpretAndPushNewScopeStackNode(EvaluableNode *new_context_node); + //if scope_break is true, then it will break the scope from all below it and not give access below + void InterpretAndPushNewScopeStackNode(EvaluableNode *new_context_node, bool scope_break); //pops the top context off the stack //if returning_unique_value, then can potentially free the whole scope diff --git a/src/Amalgam/interpreter/OpcodesControlFlow.cpp b/src/Amalgam/interpreter/OpcodesControlFlow.cpp index 06d913dad..d2fb00ee4 100644 --- a/src/Amalgam/interpreter/OpcodesControlFlow.cpp +++ b/src/Amalgam/interpreter/OpcodesControlFlow.cpp @@ -145,7 +145,7 @@ static OpcodeInitializer _ENT_CALL(ENT_CALL, &Interpreter::InterpretNode_ENT_CAL OpcodeDetails d; d.parameters = R"(* function [assoc params])"; d.returns = R"(any)"; - d.description = R"(Evaluates `function` after pushing the `params` assoc onto the scope stack.)"; + d.description = R"(Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller.)"; d.examples = MakeAmalgamExamples({ {R"&((let { @@ -203,7 +203,10 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL(EvaluableNode *en, Ev profiling_call = true; } - InterpretAndPushNewScopeStackNode(ocn.size() > 1 ? ocn[1] : nullptr); + if(ocn.size() > 1 && !EvaluableNode::IsNull(ocn[1])) + InterpretAndPushNewScopeStackNode(ocn[1], true); + else + InterpretAndPushNewScopeStackNode(nullptr, false); //call the code auto result = InterpretNode(function, immediate_result); diff --git a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp index de136828f..c30d70d83 100644 --- a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp +++ b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp @@ -88,7 +88,7 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_LET(EvaluableNode *en, Eva if(ocn_size == 0) return EvaluableNodeReference::Null(); - InterpretAndPushNewScopeStackNode(ocn[0]); + InterpretAndPushNewScopeStackNode(ocn[0], false); //run code EvaluableNodeReference result = EvaluableNodeReference::Null(); From a8291b579d1d32f69ccb06ff9313109ad45a12bb Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Mon, 11 May 2026 04:14:09 -0400 Subject: [PATCH 03/13] 25467: More implementation, fixes bugs --- src/Amalgam/interpreter/Interpreter.cpp | 30 +++++++++++++++---- src/Amalgam/interpreter/Interpreter.h | 16 +++++----- ...codesVariableDefinitionAndModification.cpp | 2 -- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/Amalgam/interpreter/Interpreter.cpp b/src/Amalgam/interpreter/Interpreter.cpp index b9ef32d94..2db5caf78 100644 --- a/src/Amalgam/interpreter/Interpreter.cpp +++ b/src/Amalgam/interpreter/Interpreter.cpp @@ -228,13 +228,21 @@ EvaluableNode *Interpreter::GetScopeStackGivenDepth(size_t depth EvaluableNode *Interpreter::MakeCopyOfScopeStack() { - //TODO 25476: make sure scope opcode respects scope break EvaluableNode stack_top_holder(ENT_LIST); - stack_top_holder.SetOrderedChildNodes(scopeStack); //set flags conservatively before copy stack_top_holder.SetNeedCycleCheck(true); stack_top_holder.SetIsIdempotent(false); + //find scope break and erase before that + size_t scope_start_index = scopeStack.size(); + for(; scope_start_index > 0; scope_start_index--) + { + if(scopeStack[scope_start_index - 1]->IsScopeBreak()) + break; + } + stack_top_holder.GetOrderedChildNodesReference() + = std::vector(begin(scopeStack) + scope_start_index, end(scopeStack)); + EvaluableNodeReference copied_stack = evaluableNodeManager->DeepAllocCopy(&stack_top_holder); #ifdef MULTITHREAD_SUPPORT @@ -244,9 +252,21 @@ EvaluableNode *Interpreter::MakeCopyOfScopeStack() auto &stack_nodes_ocn = copied_stack->GetOrderedChildNodesReference(); for(Interpreter *interp = callingInterpreter; interp != nullptr; interp = interp->callingInterpreter) { - stack_nodes_ocn.insert(begin(stack_nodes_ocn), scopeStack.size(), nullptr); - for(size_t i = 0; i < scopeStack.size(); i++) - stack_nodes_ocn[i] = evaluableNodeManager->DeepAllocCopy(scopeStack[i]); + size_t parent_scope_start_index = interp->scopeStack.size(); + for(; parent_scope_start_index > 0; parent_scope_start_index--) + { + if(scopeStack[parent_scope_start_index]->IsScopeBreak()) + break; + } + + //TODO 25476: add tests + stack_nodes_ocn.insert(begin(stack_nodes_ocn), interp->scopeStack.size() - parent_scope_start_index, nullptr); + + for(size_t i = 0; i < interp->scopeStack.size() - parent_scope_start_index; i++) + { + stack_nodes_ocn[i] + = evaluableNodeManager->DeepAllocCopy(interp->scopeStack[i + parent_scope_start_index]); + } if(interp->bottomOfScopeStack) break; diff --git a/src/Amalgam/interpreter/Interpreter.h b/src/Amalgam/interpreter/Interpreter.h index 53cf06c5c..ac6a3cf0f 100644 --- a/src/Amalgam/interpreter/Interpreter.h +++ b/src/Amalgam/interpreter/Interpreter.h @@ -308,14 +308,6 @@ class Interpreter for(auto it = rbegin(scopeStack); it != rend(scopeStack); ++it) { EvaluableNode *scope = *it; - if(scope->IsScopeBreak()) - { - #ifdef MULTITHREAD_SUPPORT - hit_scope_break = true; - #endif - break; - } - auto &mcn = scope->GetMappedChildNodesReference(); if(auto found = mcn.find(symbol_sid); found != end(mcn)) { @@ -344,6 +336,14 @@ class Interpreter return std::make_tuple(&found->second, &mcn, it == rbegin(scopeStack), is_freeable); } + + if(scope->IsScopeBreak()) + { + #ifdef MULTITHREAD_SUPPORT + hit_scope_break = true; + #endif + break; + } } #ifdef MULTITHREAD_SUPPORT diff --git a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp index c30d70d83..9df067b95 100644 --- a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp +++ b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp @@ -1257,8 +1257,6 @@ static OpcodeInitializer _ENT_STACK(ENT_STACK, &Interpreter::InterpretNode_ENT_S ) {x 1} ))&", R"([ - {} - {x 1} {a 1} ])"} }); From 5aaac84052702b41d4ae51b9d849b66a22703c76 Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Tue, 12 May 2026 05:56:22 -0400 Subject: [PATCH 04/13] 25476: Fixes stack bug, adds unit test, introduces new potential bug --- src/Amalgam/AmalgamLanguageValidation.cpp | 22 +++++++++++++++++++++ src/Amalgam/interpreter/Interpreter.cpp | 24 +++++++++++++---------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/Amalgam/AmalgamLanguageValidation.cpp b/src/Amalgam/AmalgamLanguageValidation.cpp index ae093cd93..98f816387 100644 --- a/src/Amalgam/AmalgamLanguageValidation.cpp +++ b/src/Amalgam/AmalgamLanguageValidation.cpp @@ -2289,6 +2289,28 @@ AmalgamExample{ R"&((concat ) ) ))&", R"("filter assoc 2 : {10 1 20 2}")" }, +AmalgamExample{ R"&(;stack concurrency +||(map + (lambda + (let + {a 1} + (size (stack)) + ) + ) + (range 0 10) +))&", R"([ + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 +])"}, AmalgamExample{ R"&(;nested concurrency (apply "+" ||(map (lambda diff --git a/src/Amalgam/interpreter/Interpreter.cpp b/src/Amalgam/interpreter/Interpreter.cpp index 2db5caf78..a633e11bb 100644 --- a/src/Amalgam/interpreter/Interpreter.cpp +++ b/src/Amalgam/interpreter/Interpreter.cpp @@ -42,10 +42,19 @@ EvaluableNodeReference Interpreter::ExecuteNode(EvaluableNode *en, //use specified or create new scopeStack if(scope_stack == nullptr) { - EvaluableNode *new_context_entry = evaluableNodeManager->AllocNode(ENT_ASSOC); - new_context_entry->SetNeedCycleCheck(true); - scopeStack.clear(); - scopeStack.push_back(new_context_entry); + #ifdef MULTITHREAD_SUPPORT + bottomOfScopeStack = new_scope_stack; + if(new_scope_stack) + { + #endif + + EvaluableNode *new_context_entry = evaluableNodeManager->AllocNode(ENT_ASSOC); + new_context_entry->SetNeedCycleCheck(true); + scopeStack.clear(); + scopeStack.push_back(new_context_entry); + #ifdef MULTITHREAD_SUPPORT + } + #endif } else { @@ -62,10 +71,6 @@ EvaluableNodeReference Interpreter::ExecuteNode(EvaluableNode *en, constructionStack.clear(); else constructionStack = std::move(*construction_stack); - -#ifdef MULTITHREAD_SUPPORT - bottomOfScopeStack = new_scope_stack; -#endif evaluableNodeManager->AddActiveInterpreter(this); auto retval = InterpretNode(en, immediate_result); @@ -255,11 +260,10 @@ EvaluableNode *Interpreter::MakeCopyOfScopeStack() size_t parent_scope_start_index = interp->scopeStack.size(); for(; parent_scope_start_index > 0; parent_scope_start_index--) { - if(scopeStack[parent_scope_start_index]->IsScopeBreak()) + if(interp->scopeStack[parent_scope_start_index - 1]->IsScopeBreak()) break; } - //TODO 25476: add tests stack_nodes_ocn.insert(begin(stack_nodes_ocn), interp->scopeStack.size() - parent_scope_start_index, nullptr); for(size_t i = 0; i < interp->scopeStack.size() - parent_scope_start_index; i++) From 38f291ab7c6f68e9c712095bfbc66dc18ad5c2f2 Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Tue, 12 May 2026 08:51:12 -0400 Subject: [PATCH 05/13] 25476: Fixes bug --- .../interpreter/OpcodesVariableDefinitionAndModification.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp index 9df067b95..8aac15717 100644 --- a/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp +++ b/src/Amalgam/interpreter/OpcodesVariableDefinitionAndModification.cpp @@ -514,10 +514,6 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_ASSIGN_and_ACCUM(Evaluable if(num_params < 1) return EvaluableNodeReference::Null(); - //make sure there's at least a scopeStack to use - if(scopeStack.size() < 1) - return EvaluableNodeReference::Null(); - bool accum = (en->GetType() == ENT_ACCUM); //if only one parameter, then assume it is an assoc of variables to accum or assign From f824cecb5385245d26a99af301fec6fb0974bebc Mon Sep 17 00:00:00 2001 From: howso-automation Date: Wed, 13 May 2026 14:52:16 +0000 Subject: [PATCH 06/13] Automated docs rebuild --- docs/control_flow.md | 2 +- docs/variable_definition_and_modification.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/control_flow.md b/docs/control_flow.md index 1e7d387aa..92458c9a0 100644 --- a/docs/control_flow.md +++ b/docs/control_flow.md @@ -126,7 +126,7 @@ Output: #### Parameters `* function [assoc params]` #### Description -Evaluates `function` after pushing the `params` assoc onto the scope stack. +Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller. #### Details - Permissions required: none - Allows concurrency: false diff --git a/docs/variable_definition_and_modification.md b/docs/variable_definition_and_modification.md index 46ec10bb3..dad42020b 100644 --- a/docs/variable_definition_and_modification.md +++ b/docs/variable_definition_and_modification.md @@ -673,8 +673,6 @@ Example: Output: ```amalgam [ - {} - {x 1} {a 1} ] ``` From 885a1db7ce5244b04fc62f568a48e94cfdec3b89 Mon Sep 17 00:00:00 2001 From: howso-automation Date: Fri, 22 May 2026 16:18:15 +0000 Subject: [PATCH 07/13] Automated docs rebuild --- docs/entity_query_engine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/entity_query_engine.md b/docs/entity_query_engine.md index 13c6ef9b4..f87fff787 100644 --- a/docs/entity_query_engine.md +++ b/docs/entity_query_engine.md @@ -1133,7 +1133,7 @@ Output: 2.18978102189781 3.3166247903554 2.3333333333333335 - 86400000.00000006 + 3.381434008029686 1.2 2 0 From 76d6ab728f6b66ae4b99688d5333c2f034f43265 Mon Sep 17 00:00:00 2001 From: howso-automation Date: Tue, 2 Jun 2026 01:31:21 +0000 Subject: [PATCH 08/13] Automated docs rebuild --- docs/distance_and_surprisal.md | 2 +- docs/variable_definition_and_modification.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/distance_and_surprisal.md b/docs/distance_and_surprisal.md index 9f7145f6d..21c451792 100644 --- a/docs/distance_and_surprisal.md +++ b/docs/distance_and_surprisal.md @@ -6,7 +6,7 @@ These opcodes all contain a set of common parameters that start with the contain - list `feature_labels`: The names of the labels of the features from which to compute the distances. - number `p_value`: The parameter `p_value` is the generalized norm parameter, where the value of 1 is probability space and Manhattan distance, the default, 2 being Euclidean distance, etc. For surprisal space, using a value of 1 is generally most appropriate. - list\|assoc\|assoc of assoc `weights`: If `weights` is a list, each value maps to its respective element in the vectors. If `weights` is null, then it will assume that the `weights` are 1 and additionally will ignore null values for the vectors instead of treating them as unknown differences. If `weights` is an assoc, then the parameter `value_names` will select the `weights` from the assoc. If `weights` is an assoc of assocs, additionally the parameter `weights_selection_features` will select which set of `weights` to use. - - list\|assoc of assoc\|string `attributes`: The parameter `attributes` describes the attributes of each feature which will determine how the differences are calculated. Each entry can either be a string or assoc. If a string, then the valid values are "nominal" or "continuous". But the entry is an assoc, then there are a wide variety of attributes available depending on type. The key "difference_type" can be either "nominal" or "continuous" to describe whether the difference will only look at equality or whether more distant values will have larger differences. The key "data_type" can be one of "bool", "number", "string", or "code", and will determine whether all data will be coerced to the corresponding type (null is always allowed), where "code" indicates that no type coercion will occur. The default if omitted is continuous numeric, and the default type if only nominal specified is nominal string. The additional attributes available depend on the combination of "difference_type" and "data_type". If "difference_type" is "nominal", then the key "nominal_count" will specify the number of data points in the data set, but if omitted or null, then it will infer the count the values available. If the combination is "continuous" and "number" then the key "cycle_range" specifies the upper bound of the difference of the range between two values. For example, if the "cycle_range" is 360, then the supremum difference between two values will be 360, leading 1 and 359 to have a difference of 2. If the combination of types is "continous" and "code", then the keys "types_must_match", "nominal_numbers", "nominal_strings", and "recursive_matching" are applicable. If the key "types_must_match" is true (the default), it will only consider nodes common if the types match. If the key "nominal_numbers" is true (the default is false), then it will assume that all numbers will match only if identical; if false, it will compare similarity of values. The key "nominal_strings" defaults to true, but works similar to "nominal_numbers" except on strings using string edit distance. If the key "recursive_matching" is true or null, then it will attempt to recursively match any part of the data structure of node1 to node2. If the key "recursive_matching" is false, then it will only attempt to merge the two at the same level, which yield better results if the data structures are common, and additionally will be much faster. Additionally, for distances computed by `contained_entities` or `compute_on_contained_entities`, the value for a given feature may be the result of executing code. If the key "call_entity" is specified and is either a `call_entity` or `call_on_entity` opcode, then the opcode will be executed and the result will be compared with regard to distance or surprisal. The entity should be set to null, so the parameter should be formed as `(call_entity .null ...))`, and in most cases it will be desirable to put constraints on the call to prevent excess compute for dynamic data. + - list\|assoc of assoc\|string `attributes`: The parameter `attributes` describes the attributes of each feature which will determine how the differences are calculated. Each entry can either be a string or assoc. If a string, then the valid values are "nominal" or "continuous". But the entry is an assoc, then there are a wide variety of attributes available depending on type. The key "difference_type" can be either "nominal" or "continuous" to describe whether the difference will only look at equality or whether more distant values will have larger differences. The key "data_type" can be one of "bool", "number", "string", or "code", and will determine whether all data will be coerced to the corresponding type (null is always allowed), where "code" indicates that no type coercion will occur. The default if omitted is continuous numeric, and the default type if only nominal specified is nominal string. The additional attributes available depend on the combination of "difference_type" and "data_type". If "difference_type" is "nominal", then the key "nominal_count" will specify the number of data points in the data set, but if omitted or null, then it will infer the count the values available. If the combination is "continuous" and "number" then the key "cycle_range" specifies the upper bound of the difference of the range between two values. For example, if the "cycle_range" is 360, then the supremum difference between two values will be 360, leading 1 and 359 to have a difference of 2. If the combination of types is "continuous" and "code", then the keys "types_must_match", "nominal_numbers", "nominal_strings", and "recursive_matching" are applicable. If the key "types_must_match" is true (the default), it will only consider nodes common if the types match. If the key "nominal_numbers" is true (the default is false), then it will assume that all numbers will match only if identical; if false, it will compare similarity of values. The key "nominal_strings" defaults to true, but works similar to "nominal_numbers" except on strings using string edit distance. If the key "recursive_matching" is true or null, then it will attempt to recursively match any part of the data structure of node1 to node2. If the key "recursive_matching" is false, then it will only attempt to merge the two at the same level, which yield better results if the data structures are common, and additionally will be much faster. Additionally, for distances computed by `contained_entities` or `compute_on_contained_entities`, the value for a given feature may be the result of executing code. If the key "call_entity" is specified and is either a `call_entity` or `call_on_entity` opcode, then the opcode will be executed and the result will be compared with regard to distance or surprisal. The entity should be set to null, so the parameter should be formed as `(call_entity .null ...))`, and in most cases it will be desirable to put constraints on the call to prevent excess compute for dynamic data. - list\|assoc `deviations`: The values in the parameter `deviations` are used during distance calculation to specify uncertainty per-element, the minimum difference between two values prior to exponentiation. Specifying null as a deviation is equivalent to setting each deviation to 0. Each deviation for each feature can be a single value or a list. If it is a single value, that value is used as the deviation and differences and deviations for null values will automatically computed from the data based on the maximum difference. If a deviation is provided as a list, then the first value is the deviation, the second value is the difference to use when one of the values being compared is null, and the third value is the difference to use when both of the values are null. If the third value is omitted, it will use the second value for both. If both of the null values are omitted, then it will compute the maximum difference and use that for both. For nominal types, the value for each feature can be a numeric deviation, an assoc, or a list. If the value is an assoc it specifies deviation information, where each key of the assoc is the nominal value, and each value of the assoc can be a numeric deviation value, a list, or an assoc, with the list specifying either an assoc followed optionally by the default deviation. This inner assoc, regardless of whether it is in a list, maps the value to each actual value's deviation. - list\|string `weights_selection_features`: If `weights_selection_features` is a string and `weights` is an assoc, then it will select the `weights` for the given feature and rebalance `weights` for any unused features. - string\|number `distance_transform`: A transform will be applied to the distances based on `distance_transform`. If `distance_transform` is "surprisal" then distances will be calculated as surprisals, and weights will not be applied to the values. If `distance_transform` is "surprisal_to_prob" then distances will be calculated as surprisals and will be transformed back into probabilities for aggregating, and then transformed back to surprisals. If `distance_transform` is a number or omitted, which will default to 1.0, then it will be treated as a distance weight exponent, and will be applied to each distance as distance^distance_weight_exponent, only using entity weights for nonpositive values of `distance_transform`. Note that the corresponding parameter for `generalized_distance` is bool `surprisal_space`, and is true then all distance computations will be performed in surprisal space. diff --git a/docs/variable_definition_and_modification.md b/docs/variable_definition_and_modification.md index 3a7b3946a..0287023e2 100644 --- a/docs/variable_definition_and_modification.md +++ b/docs/variable_definition_and_modification.md @@ -876,7 +876,6 @@ Example: Output: ```amalgam (unordered_list - #react (+ 3 4) ) From 4490c680a2b2f1bd21c31682a7dd84887a24dec5 Mon Sep 17 00:00:00 2001 From: howso-automation Date: Thu, 25 Jun 2026 03:41:35 +0000 Subject: [PATCH 09/13] Automated docs rebuild --- docs/control_flow.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/control_flow.md b/docs/control_flow.md index acafe2e53..76b21cbfd 100644 --- a/docs/control_flow.md +++ b/docs/control_flow.md @@ -126,11 +126,7 @@ Output: #### Parameters `* function [assoc params] [bool|assoc constraints] [bool return_warnings]` #### Description -<<<<<<< HEAD -Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller. -======= -Evaluates `function` after pushing the `params` assoc onto the scope stack. If `constraints` is specified and not false or null, it will constrain execution. If `constraints` is true or an assoc, it will default all constraints to be on at reasonable values for small execution without access to any data beyond `params`. They optional key-value combinations for `constraints` are as follows. If "max_node_operations" is specified, it represents the number of operations that are allowed to be performed. If "max_node_operations" is 0, then an infinite of operations will be allotted, up to the limits of the current calling context. If "max_node_allocations" is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory, up to the current calling context's limit. If "max_node_allocations" is 0 and the caller also has no limit, then there is no limit to the number of nodes to be allotted as long as the machine has sufficient memory. Note that if "max_node_allocations" is specified while in a multithreaded environment, if the collective memory from all the executing threads exceeds the average memory specified by call_sandboxed, that may trigger a memory limit for the call_sandboxed. If "max_operation_depth" is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise "max_operation_depth" limits how deep nested opcodes will be called. If `return_warnings` is true (default is false), the result will be a tuple of the form [value, warnings, performance_constraint_violation], where warnings is a list of all warnings, and perf_constraint_violation is a string denoting the performance constraint exceeded (or .null if none)). The keys "read_access" and "write_access" are boolean and control whether the execution can read from or write to entities and access their relevant permissions (e.g., to load files, make system calls). If the parameter `return_warnings` is false, just the value will be returned. ->>>>>>> main +Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller. If `constraints` is specified and not false or null, it will constrain execution. If `constraints` is true or an assoc, it will default all constraints to be on at reasonable values for small execution without access to any data beyond `params`. They optional key-value combinations for `constraints` are as follows. If "max_node_operations" is specified, it represents the number of operations that are allowed to be performed. If "max_node_operations" is 0, then an infinite of operations will be allotted, up to the limits of the current calling context. If "max_node_allocations" is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory, up to the current calling context's limit. If "max_node_allocations" is 0 and the caller also has no limit, then there is no limit to the number of nodes to be allotted as long as the machine has sufficient memory. Note that if "max_node_allocations" is specified while in a multithreaded environment, if the collective memory from all the executing threads exceeds the average memory specified by call_sandboxed, that may trigger a memory limit for the call_sandboxed. If "max_operation_depth" is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise "max_operation_depth" limits how deep nested opcodes will be called. If `return_warnings` is true (default is false), the result will be a tuple of the form [value, warnings, performance_constraint_violation], where warnings is a list of all warnings, and perf_constraint_violation is a string denoting the performance constraint exceeded (or .null if none)). The keys "read_access" and "write_access" are boolean and control whether the execution can read from or write to entities and access their relevant permissions (e.g., to load files, make system calls). If the parameter `return_warnings` is false, just the value will be returned. #### Details - Permissions required: none - Allows concurrency: false From 8f4bb0d524f139e81f0324d486b6b9a48192392d Mon Sep 17 00:00:00 2001 From: howso-automation Date: Thu, 25 Jun 2026 13:45:50 +0000 Subject: [PATCH 10/13] Automated docs rebuild --- docs/control_flow.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/control_flow.md b/docs/control_flow.md index deefc1ebb..ee601ab16 100644 --- a/docs/control_flow.md +++ b/docs/control_flow.md @@ -126,11 +126,7 @@ Output: #### Parameters `* function [assoc params] [bool|assoc constraints] [bool return_warnings]` #### Description -<<<<<<< HEAD -Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller. If `constraints` is specified and not false or null, it will constrain execution. If `constraints` is true or an assoc, it will default all constraints to be on at reasonable values for small execution without access to any data beyond `params`. They optional key-value combinations for `constraints` are as follows. If "max_node_operations" is specified, it represents the number of operations that are allowed to be performed. If "max_node_operations" is 0, then an infinite of operations will be allotted, up to the limits of the current calling context. If "max_node_allocations" is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory, up to the current calling context's limit. If "max_node_allocations" is 0 and the caller also has no limit, then there is no limit to the number of nodes to be allotted as long as the machine has sufficient memory. Note that if "max_node_allocations" is specified while in a multithreaded environment, if the collective memory from all the executing threads exceeds the average memory specified by call_sandboxed, that may trigger a memory limit for the call_sandboxed. If "max_operation_depth" is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise "max_operation_depth" limits how deep nested opcodes will be called. If `return_warnings` is true (default is false), the result will be a tuple of the form [value, warnings, performance_constraint_violation], where warnings is a list of all warnings, and perf_constraint_violation is a string denoting the performance constraint exceeded (or .null if none)). The keys "read_access" and "write_access" are boolean and control whether the execution can read from or write to entities and access their relevant permissions (e.g., to load files, make system calls). If the parameter `return_warnings` is false, just the value will be returned. -======= -Evaluates `function` after pushing the `params` assoc onto the scope stack. If `constraints` is specified and not false or null, it will constrain execution. If `constraints` is true or an assoc, it will default all constraints to be on at reasonable values for small execution without access to any data beyond `params`. They optional key-value combinations for `constraints` are as follows. If "max_node_operations" is specified, it represents the number of operations that are allowed to be performed. If "max_node_operations" is 0, then an infinite of operations will be allotted, up to the limits of the current calling context. If "max_node_allocations" is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory, up to the current calling context's limit. If "max_node_allocations" is 0 and the caller also has no limit, then there is no limit to the number of nodes to be allotted as long as the machine has sufficient memory. Note that if "max_node_allocations" is specified while in a multithreaded environment, if the collective memory from all the executing threads exceeds the average memory specified by "max_node_allocations", that may trigger a memory limit for the call. If "max_operation_depth" is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise "max_operation_depth" limits how deep nested opcodes will be called. If `return_warnings` is true (default is false), the result will be a tuple of the form [value, warnings, performance_constraint_violation], where warnings is a list of all warnings, and perf_constraint_violation is a string denoting the performance constraint exceeded (or .null if none)). The keys "read_access" and "write_access" are boolean and control whether the execution can read from or write to entities and access their relevant permissions (e.g., to load files, make system calls). If the parameter `return_warnings` is false, just the value will be returned. ->>>>>>> main +Evaluates `function` after pushing the `params` assoc onto the scope stack. If `params` is missing or null, then it will allow access to the scope stack of the caller. If `constraints` is specified and not false or null, it will constrain execution. If `constraints` is true or an assoc, it will default all constraints to be on at reasonable values for small execution without access to any data beyond `params`. They optional key-value combinations for `constraints` are as follows. If "max_node_operations" is specified, it represents the number of operations that are allowed to be performed. If "max_node_operations" is 0, then an infinite of operations will be allotted, up to the limits of the current calling context. If "max_node_allocations" is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory, up to the current calling context's limit. If "max_node_allocations" is 0 and the caller also has no limit, then there is no limit to the number of nodes to be allotted as long as the machine has sufficient memory. Note that if "max_node_allocations" is specified while in a multithreaded environment, if the collective memory from all the executing threads exceeds the average memory specified by "max_node_allocations", that may trigger a memory limit for the call. If "max_operation_depth" is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise "max_operation_depth" limits how deep nested opcodes will be called. If `return_warnings` is true (default is false), the result will be a tuple of the form [value, warnings, performance_constraint_violation], where warnings is a list of all warnings, and perf_constraint_violation is a string denoting the performance constraint exceeded (or .null if none)). The keys "read_access" and "write_access" are boolean and control whether the execution can read from or write to entities and access their relevant permissions (e.g., to load files, make system calls). If the parameter `return_warnings` is false, just the value will be returned. #### Details - Permissions required: none - Allows concurrency: false From d3243e48e7602b77c084d7e82983931e82b68ec7 Mon Sep 17 00:00:00 2001 From: howso-automation Date: Sat, 11 Jul 2026 02:50:33 +0000 Subject: [PATCH 11/13] Automated docs rebuild --- docs/string_operations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/string_operations.md b/docs/string_operations.md index ceef99652..d3f8b3b7e 100644 --- a/docs/string_operations.md +++ b/docs/string_operations.md @@ -205,7 +205,7 @@ Example: ``` Output: ```amalgam -"ello wo" +"ello wor" ``` Example: ```amalgam @@ -245,7 +245,7 @@ Example: ``` Output: ```amalgam -"hxlo world" +"hxo world" ``` Example: ```amalgam @@ -377,7 +377,7 @@ Example: ``` Output: ```amalgam -"bcdefgi" +"bcdefgij" ``` Example: ```amalgam @@ -417,7 +417,7 @@ Example: ``` Output: ```amalgam -"axdefgijk" +"axefgijk" ``` [Amalgam Opcodes](./opcodes.md) From 7e74a74a8f99406a317f4fd47a4dbc329fb3531b Mon Sep 17 00:00:00 2001 From: howso-automation Date: Thu, 16 Jul 2026 20:23:33 +0000 Subject: [PATCH 12/13] Automated docs rebuild --- docs/basic_math.md | 16 ++++++++-------- docs/system_and_runtime.md | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/basic_math.md b/docs/basic_math.md index 8f03868f6..152060d67 100644 --- a/docs/basic_math.md +++ b/docs/basic_math.md @@ -4,7 +4,7 @@ #### Returns `number` #### Description -Evaluates to the sum of all numbers. +Evaluates to the sum of all numbers. If no parameters are provided it returns 0.0. #### Details - Permissions required: none - Allows concurrency: true @@ -28,9 +28,9 @@ Output: #### Parameters `[number x1] [number x2] ...` #### Returns -`number` +`null|number` #### Description -Evaluates to `x1` - `x2` - ... - `xN`. If only one parameter is passed, then it is treated as its negative +Evaluates to `x1` - `x2` - ... - `xN`. If only one parameter is passed, then it is treated as its negative. If no parameters are provided it returns null. #### Details - Permissions required: none - Allows concurrency: true @@ -64,7 +64,7 @@ Output: #### Returns `number` #### Description -Evaluates to the product of all numbers. +Evaluates to the product of all numbers. If no parameters are provided, returns 1. #### Details - Permissions required: none - Allows concurrency: true @@ -88,9 +88,9 @@ Output: #### Parameters `[number x1] [number x2] ...` #### Returns -`number` +`null|number` #### Description -Evaluates to `x1` / `x2` / ... / `xN`. +Evaluates to `x1` / `x2` / ... / `xN`. If no parameters are provided, it returns null. #### Details - Permissions required: none - Allows concurrency: true @@ -114,9 +114,9 @@ Output: #### Parameters `[number x1] [number x2] ...` #### Returns -`number` +`null|number` #### Description -Evaluates the modulus of `x1` mod `x2` mod ... mod `xN`. +Evaluates the modulus of `x1` mod `x2` mod ... mod `xN`. If no parameters are provided it returns null. #### Details - Permissions required: none - Allows concurrency: true diff --git a/docs/system_and_runtime.md b/docs/system_and_runtime.md index 4abf5d2dc..46bd04ffc 100644 --- a/docs/system_and_runtime.md +++ b/docs/system_and_runtime.md @@ -21,7 +21,7 @@ Output: ```amalgam { allows_concurrency .true - description "Evaluates to the sum of all numbers." + description "Evaluates to the sum of all numbers. If no parameters are provided it returns 0.0." examples [ {example "(+ 1 2 3 4)" output "10"} ] From 83989d0d1c8fb7770767be5ba737458cd20ac19b Mon Sep 17 00:00:00 2001 From: howso-automation Date: Fri, 11 Sep 2026 15:07:50 +0000 Subject: [PATCH 13/13] Automated docs rebuild --- docs/basic_math.md | 8 ++++---- docs/logic_and_comparison.md | 16 ++++++++-------- docs/opcodes.md | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/basic_math.md b/docs/basic_math.md index 0158aaf65..345532e53 100644 --- a/docs/basic_math.md +++ b/docs/basic_math.md @@ -5,7 +5,7 @@ parent: Opcodes nav_order: 6 --- -### Opcode: `+` {#opcode-add} +### Opcode: `+` #### Parameters `[number x1] [number x2] ...` #### Returns @@ -31,7 +31,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `-` {#opcode-subtract} +### Opcode: `-` #### Parameters `[number x1] [number x2] ...` #### Returns @@ -65,7 +65,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `*` {#opcode-multiply} +### Opcode: `*` #### Parameters `[number x1] [number x2] ...` #### Returns @@ -91,7 +91,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `/` {#opcode-divide} +### Opcode: `/` #### Parameters `[number x1] [number x2] ...` #### Returns diff --git a/docs/logic_and_comparison.md b/docs/logic_and_comparison.md index 5b802d552..2e1ffe760 100644 --- a/docs/logic_and_comparison.md +++ b/docs/logic_and_comparison.md @@ -197,7 +197,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `=` {#opcode-equal} +### Opcode: `=` #### Parameters `[any node1] [any node2] ...` #### Returns @@ -266,7 +266,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `!=` {#opcode-not_equal} +### Opcode: `!=` #### Parameters `[any node1] [any node2] ...` #### Returns @@ -348,7 +348,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `<` {#opcode-less_than} +### Opcode: `<` #### Parameters `[number|string node1] [number|string node2] ...` #### Returns @@ -398,7 +398,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `<=` {#opcode-less_than_or_equal} +### Opcode: `<=` #### Parameters `[number|string node1] [number|string node2] ...` #### Returns @@ -464,7 +464,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `>` {#opcode-greater_than} +### Opcode: `>` #### Parameters `[number|string node1] [number|string node2] ...` #### Returns @@ -514,7 +514,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `>=` {#opcode-greater_than_or_equal} +### Opcode: `>=` #### Parameters `[number|string node1] [number|string node2] ...` #### Returns @@ -580,7 +580,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `~` {#opcode-type_equal} +### Opcode: `~` #### Parameters `[any node1] [any node2] ...` #### Returns @@ -614,7 +614,7 @@ Output: [Amalgam Opcodes](./opcodes.md) -### Opcode: `!~` {#opcode-type_not_equal} +### Opcode: `!~` #### Parameters `[any node1] [any node2] ...` #### Returns diff --git a/docs/opcodes.md b/docs/opcodes.md index 5884fd81f..67d259e5b 100644 --- a/docs/opcodes.md +++ b/docs/opcodes.md @@ -45,19 +45,19 @@ - [or](./logic_and_comparison.md#opcode-or) - [xor](./logic_and_comparison.md#opcode-xor) - [not](./logic_and_comparison.md#opcode-not) - - [=](./logic_and_comparison.md#opcode-equal) - - [!=](./logic_and_comparison.md#opcode-not_equal) - - [<](./logic_and_comparison.md#opcode-less_than) - - [<=](./logic_and_comparison.md#opcode-less_than_or_equal) - - [>](./logic_and_comparison.md#opcode-greater_than) - - [>=](./logic_and_comparison.md#opcode-greater_than_or_equal) - - [~](./logic_and_comparison.md#opcode-type_equal) - - [!~](./logic_and_comparison.md#opcode-type_not_equal) + - [=](./logic_and_comparison.md#opcode-=) + - [!=](./logic_and_comparison.md#opcode-!=) + - [<](./logic_and_comparison.md#opcode-<) + - [<=](./logic_and_comparison.md#opcode-<=) + - [>](./logic_and_comparison.md#opcode->) + - [>=](./logic_and_comparison.md#opcode->=) + - [~](./logic_and_comparison.md#opcode-~) + - [!~](./logic_and_comparison.md#opcode-!~) ### Basic Math - - [+](./basic_math.md#opcode-add) - - [-](./basic_math.md#opcode-subtract) - - [\*](./basic_math.md#opcode-multiply) - - [/](./basic_math.md#opcode-divide) + - [+](./basic_math.md#opcode-+) + - [-](./basic_math.md#opcode--) + - [\*](./basic_math.md#opcode-*) + - [/](./basic_math.md#opcode-/) - [mod](./basic_math.md#opcode-mod) - [get_digits](./basic_math.md#opcode-get_digits) - [set_digits](./basic_math.md#opcode-set_digits)