Skip to content

Commit

Permalink
20396: Adds return opcode, MINOR (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
howsohazard authored Jun 6, 2024
1 parent cbc8277 commit e4761bb
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 247 deletions.
17 changes: 12 additions & 5 deletions docs/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ var data = [
{
"parameter" : "seq [code c1] [code c2] ... [code cN]",
"output" : "*",
"description" : "Runs each code block sequentially. Evaluates to the result of the last code block run, unless it encounters a conclude in an earlier step, in which case it will halt processing and evaluate to the value returned by conclude. Note that the last step will not consume a concluded value.",
"description" : "Runs each code block sequentially. Evaluates to the result of the last code block run, unless it encounters a conclude or return in an earlier step, in which case it will halt processing and evaluate to the value returned by conclude or propagate the return. Note that the last step will not consume a concluded value.",
"example" : "(seq (print 1) (print 2) (print 3))"
},

Expand All @@ -74,9 +74,16 @@ var data = [
{
"parameter" : "conclude * conclusion",
"output" : "*",
"description" : "Evaluates to the conclusion wrapped in a conclude opcode. If a step in a seq, let, declare, or while evaluates to a conclude (excluding variable declarations for let and declare, the last step in set, let, and declare, or the condition of while), then it will conclude the execution and evaluate to the value conclusion. Note that conclude opcodes may be nested to break out of outer opcodes",
"description" : "Evaluates to the conclusion wrapped in a conclude opcode. If a step in a seq, let, declare, or while evaluates to a conclude (excluding variable declarations for let and declare, the last step in set, let, and declare, or the condition of while), then it will conclude the execution and evaluate to the value conclusion. Note that conclude opcodes may be nested to break out of outer opcodes.",
"example" : "(print (seq (print \"seq1 \") (conclude \"success\") (print \"seq2\") ) )"
},

{
"parameter" : "return * return_value",
"output" : "*",
"description" : "Evaluates to return_value wrapped in a return opcode. If a step in a seq, let, declare, or while evaluates to a return (excluding variable declarations for let and declare, the last step in set, let, and declare, or the condition of while), then it will conclude the execution and evaluate to the return opcode with its return_value. This means it will continue to conclude each level up the stack until it reaches any kind of call opcode, including call, call_sandboxed, call_entity, call_entity_get_changes, or call_container, at which point it will evaluate to return_value. Note that return opcodes may be nested to break out of multiple calls.",
"example" : " (print (call (seq 1 2 (seq (return 3) 4) 5)) \"\\n\")"
},

{
"parameter" : "call * function assoc arguments",
Expand All @@ -98,22 +105,22 @@ var data = [
"parameter" : "while bool condition [code code1] [code code2] ... [code codeN]",
"output" : "*",
"new target scope": true,
"description" : "Each time the condition evaluates to true, it runs each of the code trees sequentially, looping. Evaluates to the last codeN or null if the condition was initially false or if it encounters a conclude, it will halt processing and evaluate to the value returned by conclude. For iteration of the loop, pushes a new target scope onto the target stack, with current_index being the iteration count, and previous_result being the last evaluated codeN of the previous loop.",
"description" : "Each time the condition evaluates to true, it runs each of the code trees sequentially, looping. Evaluates to the last codeN or null if the condition was initially false or if it encounters a conclude or return, it will halt processing and evaluate to the value returned by conclude or propagate the return. For iteration of the loop, pushes a new target scope onto the target stack, with current_index being the iteration count, and previous_result being the last evaluated codeN of the previous loop.",
"example" : "(let (assoc zz 1)\n (while (< zz 10)\n (print zz)\n (assign (assoc zz (+ zz 1)))\n )\n)"
},

{
"parameter" : "let assoc data [code function1] [code function2] ... [code functionN]",
"output" : "*",
"new scope" : true,
"description" : "Pushes the key-value pairs of data onto the scope stack so that they become the new variables, then runs each code block sequentially, evaluating to the last code block run, unless it encounters a conclude, in which case it will halt processing and evaluate to the value returned by conclude. Note that the last step will not consume a concluded value.",
"description" : "Pushes the key-value pairs of data onto the scope stack so that they become the new variables, then runs each code block sequentially, evaluating to the last code block run, unless it encounters a conclude or return, in which case it will halt processing and evaluate to the value returned by conclude or propagate the return. Note that the last step will not consume a concluded value.",
"example" : "(let (assoc x 4 y 6) (print (+ x y)))"
},

{
"parameter" : "declare assoc data [code function1] [code function2] ... [code functionN]",
"output" : "*",
"description" : "For each key-value pair of data, if not already in the current context in the scope stack, it will define them. Then runs each code block sequentially, evaluating to the last code block run, unless it encounters a conclude, in which case it will halt processing and evaluate to the value returned by conclude. Note that the last step will not consume a concluded value.",
"description" : "For each key-value pair of data, if not already in the current context in the scope stack, it will define them. Then runs each code block sequentially, evaluating to the last code block run, unless it encounters a conclude or return, in which case it will halt processing and evaluate to the value returned by conclude or propagate the return. Note that the last step will not consume a concluded value.",
"example" : "(let (assoc x 4 y 6)\n (declare (assoc x 5 z 1)\n (print (+ x y z)) )\n)"
},

Expand Down
1 change: 1 addition & 0 deletions src/Amalgam/Opcodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void StringInternPool::InitializeStaticStrings()
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_PARALLEL), "parallel");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_LAMBDA), "lambda");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_CONCLUDE), "conclude");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_RETURN), "return");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_CALL), "call");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_CALL_SANDBOXED), "call_sandboxed");
EmplaceStaticString(GetStringIdFromNodeTypeFromString(ENT_WHILE), "while");
Expand Down
5 changes: 3 additions & 2 deletions src/Amalgam/Opcodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum EvaluableNodeType : uint8_t
ENT_PARALLEL,
ENT_LAMBDA,
ENT_CONCLUDE,
ENT_RETURN,
ENT_CALL,
ENT_CALL_SANDBOXED,
ENT_WHILE,
Expand Down Expand Up @@ -370,7 +371,7 @@ constexpr OrderedChildNodeType GetInstructionOrderedChildNodeType(EvaluableNodeT
return OCNT_ONE_POSITION_THEN_PAIRED;

case ENT_PARSE: case ENT_UNPARSE: case ENT_IF: case ENT_LAMBDA:
case ENT_CONCLUDE:
case ENT_CONCLUDE: case ENT_RETURN:
case ENT_CALL: case ENT_CALL_SANDBOXED:
case ENT_RETRIEVE:
case ENT_GET:
Expand Down Expand Up @@ -487,7 +488,7 @@ constexpr bool IsEvaluableNodeTypePotentiallyIdempotent(EvaluableNodeType type)
return (type == ENT_NUMBER || type == ENT_STRING
|| type == ENT_TRUE || type == ENT_FALSE
|| type == ENT_NULL || type == ENT_LIST || type == ENT_ASSOC
|| type == ENT_CONCLUDE
|| type == ENT_CONCLUDE || type == ENT_RETURN
|| IsEvaluableNodeTypeQuery(type));
}

Expand Down
6 changes: 6 additions & 0 deletions src/Amalgam/amlg_code/full_test.amlg
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
(print "2\n")
)

(print "--return--\n")

(print (call
(seq 1 2 (seq (return 3) 4) 5)
) "\n")

(print "--declare--\n")
(declare (assoc x 7))
(print x "\n")
Expand Down
5 changes: 5 additions & 0 deletions src/Amalgam/evaluablenode/EvaluableNodeManagement.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ class EvaluableNodeReference
return value.nodeType != ENIVT_CODE;
}

constexpr bool IsNonNullNodeReference()
{
return (value.nodeType == ENIVT_CODE && value.nodeValue.code != nullptr);
}

constexpr EvaluableNodeImmediateValueWithType &GetValue()
{
return value;
Expand Down
18 changes: 18 additions & 0 deletions src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,21 @@ inline EvaluableNodeReference CreateListOfStringsFromIteratorAndFunction(StringC

return EvaluableNodeReference(list, true);
}

//removes the top conclude or return node and, if possible, will free it, saving memory
inline EvaluableNodeReference RemoveTopConcludeOrReturnNode(EvaluableNodeReference result, EvaluableNodeManager *enm)
{
if(result == nullptr)
return EvaluableNodeReference::Null();

if(result->GetOrderedChildNodes().size() == 0)
{
enm->FreeNodeTreeIfPossible(result);
return EvaluableNodeReference::Null();
}

EvaluableNode *conclusion = result->GetOrderedChildNodes()[0];
enm->FreeNodeIfPossible(result);

return EvaluableNodeReference(conclusion, result.unique);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,7 @@ CompactHashMap<EvaluableNodeType, double> EvaluableNodeTreeManipulation::evaluab
{ENT_PARALLEL, 0.5},
{ENT_LAMBDA, 1.5},
{ENT_CONCLUDE, 0.05},
{ENT_RETURN, 0.05},
{ENT_CALL, 1.5},
{ENT_CALL_SANDBOXED, 0.25},
{ENT_WHILE, 0.1},
Expand Down
3 changes: 2 additions & 1 deletion src/Amalgam/interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ std::array<Interpreter::OpcodeFunction, ENT_NOT_A_BUILT_IN_TYPE + 1> Interpreter
&Interpreter::InterpretNode_ENT_SEQUENCE, // ENT_SEQUENCE
&Interpreter::InterpretNode_ENT_PARALLEL, // ENT_PARALLEL
&Interpreter::InterpretNode_ENT_LAMBDA, // ENT_LAMBDA
&Interpreter::InterpretNode_ENT_CONCLUDE, // ENT_CONCLUDE
&Interpreter::InterpretNode_ENT_CONCLUDE_and_RETURN, // ENT_CONCLUDE
&Interpreter::InterpretNode_ENT_CONCLUDE_and_RETURN, // ENT_RETURN
&Interpreter::InterpretNode_ENT_CALL, // ENT_CALL
&Interpreter::InterpretNode_ENT_CALL_SANDBOXED, // ENT_CALL_SANDBOXED
&Interpreter::InterpretNode_ENT_WHILE, // ENT_WHILE
Expand Down
2 changes: 1 addition & 1 deletion src/Amalgam/interpreter/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ class Interpreter
EvaluableNodeReference InterpretNode_ENT_SEQUENCE(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_PARALLEL(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_LAMBDA(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_CONCLUDE(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_CONCLUDE_and_RETURN(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_CALL(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_CALL_SANDBOXED(EvaluableNode *en, bool immediate_result);
EvaluableNodeReference InterpretNode_ENT_WHILE(EvaluableNode *en, bool immediate_result);
Expand Down
96 changes: 62 additions & 34 deletions src/Amalgam/interpreter/InterpreterOpcodesBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,18 +338,6 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_IF(EvaluableNode *en, bool
return EvaluableNodeReference::Null();
}

//removes the conclude node from the top of the conclusion and, if possible, will free it, saving memory
inline EvaluableNodeReference RemoveConcludeFromConclusion(EvaluableNodeReference result, EvaluableNodeManager *enm)
{
if(result == nullptr || result->GetOrderedChildNodes().size() == 0)
return EvaluableNodeReference::Null();

EvaluableNode *conclusion = result->GetOrderedChildNodes()[0];
enm->FreeNodeIfPossible(result);

return EvaluableNodeReference(conclusion, result.unique);
}

EvaluableNodeReference Interpreter::InterpretNode_ENT_SEQUENCE(EvaluableNode *en, bool immediate_result)
{
auto &ocn = en->GetOrderedChildNodes();
Expand All @@ -358,11 +346,18 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_SEQUENCE(EvaluableNode *en
EvaluableNodeReference result = EvaluableNodeReference::Null();
for(size_t i = 0; i < ocn_size; i++)
{
if(!result.IsImmediateValue() && result != nullptr && result->GetType() == ENT_CONCLUDE)
return RemoveConcludeFromConclusion(result, evaluableNodeManager);
if(result.IsNonNullNodeReference())
{
auto result_type = result->GetType();
if(result_type == ENT_CONCLUDE)
return RemoveTopConcludeOrReturnNode(result, evaluableNodeManager);
else if(result_type == ENT_RETURN)
return result;
}

//free from previous iteration
evaluableNodeManager->FreeNodeTreeIfPossible(result);

//request immediate values when not last, since any allocs for returns would be wasted
//concludes won't be immediate
result = InterpretNode(ocn[i], immediate_result || i + 1 < ocn_size);
Expand Down Expand Up @@ -457,7 +452,7 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_LAMBDA(EvaluableNode *en,
}
}

EvaluableNodeReference Interpreter::InterpretNode_ENT_CONCLUDE(EvaluableNode *en, bool immediate_result)
EvaluableNodeReference Interpreter::InterpretNode_ENT_CONCLUDE_and_RETURN(EvaluableNode *en, bool immediate_result)
{
auto &ocn = en->GetOrderedChildNodes();

Expand All @@ -469,14 +464,15 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CONCLUDE(EvaluableNode *en
if(en->GetIsIdempotent())
return evaluableNodeManager->DeepAllocCopy(en, EvaluableNodeManager::ENMM_REMOVE_ALL);

EvaluableNodeReference conclusion_value = InterpretNode(ocn[0]);
EvaluableNodeReference value = InterpretNode(ocn[0]);

//need to evaluate its parameter and return a new node encapsulating it
EvaluableNodeReference conclusion(evaluableNodeManager->AllocNode(ENT_CONCLUDE), true);
conclusion->AppendOrderedChildNode(conclusion_value);
conclusion.UpdatePropertiesBasedOnAttachedNode(conclusion_value);
auto node_type = en->GetType();
EvaluableNodeReference result(evaluableNodeManager->AllocNode(node_type), true);
result->AppendOrderedChildNode(value);
result.UpdatePropertiesBasedOnAttachedNode(value);

return conclusion;
return result;
}

EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL(EvaluableNode *en, bool immediate_result)
Expand All @@ -503,15 +499,19 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL(EvaluableNode *en, bo
PushNewCallStack(new_context);

//call the code
auto retval = InterpretNode(function, immediate_result);
auto result = InterpretNode(function, immediate_result);

//all finished with new context, but can't free it in case returning something
PopCallStack();

//call opcodes should consume the outer return opcode if there is one
if(result.IsNonNullNodeReference() && result->GetType() == ENT_RETURN)
result = RemoveTopConcludeOrReturnNode(result, evaluableNodeManager);

if(_label_profiling_enabled && function->GetNumLabels() > 0)
PerformanceProfiler::EndOperation(evaluableNodeManager->GetNumberOfUsedNodes());

return retval;
return result;
}

EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_SANDBOXED(EvaluableNode *en, bool immediate_result)
Expand Down Expand Up @@ -599,6 +599,10 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_SANDBOXED(EvaluableNo

curExecutionStep += sandbox.curExecutionStep;

//call opcodes should consume the outer return opcode if there is one
if(result.IsNonNullNodeReference() && result->GetType() == ENT_RETURN)
result = RemoveTopConcludeOrReturnNode(result, evaluableNodeManager);

if(_label_profiling_enabled && function->GetNumLabels() > 0)
PerformanceProfiler::EndOperation(evaluableNodeManager->GetNumberOfUsedNodes());

Expand Down Expand Up @@ -645,14 +649,22 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_WHILE(EvaluableNode *en, b
//cannot be evaulated as immediate
new_result = InterpretNode(ocn[i], i + 1 < ocn_size);

if(!new_result.IsImmediateValue() && new_result != nullptr && new_result->GetType() == ENT_CONCLUDE)
if(new_result.IsNonNullNodeReference())
{
//if previous result is unconsumed, free if possible
previous_result = GetAndClearPreviousResultInConstructionStack(0);
evaluableNodeManager->FreeNodeTreeIfPossible(previous_result);
auto new_result_type = new_result->GetType();
if(new_result_type == ENT_CONCLUDE || new_result_type == ENT_RETURN)
{
//if previous result is unconsumed, free if possible
previous_result = GetAndClearPreviousResultInConstructionStack(0);
evaluableNodeManager->FreeNodeTreeIfPossible(previous_result);

PopConstructionContext();
return RemoveConcludeFromConclusion(new_result, evaluableNodeManager);
PopConstructionContext();

if(new_result_type == ENT_CONCLUDE)
return RemoveTopConcludeOrReturnNode(new_result, evaluableNodeManager);
else if(new_result_type == ENT_RETURN)
return new_result;
}
}

//don't free the last new_result
Expand Down Expand Up @@ -686,22 +698,31 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_LET(EvaluableNode *en, boo
EvaluableNodeReference result = EvaluableNodeReference::Null();
for(size_t i = 1; i < ocn_size; i++)
{
if(!result.IsImmediateValue() && result != nullptr && result->GetType() == ENT_CONCLUDE)
if(result.IsNonNullNodeReference())
{
PopCallStack();
return RemoveConcludeFromConclusion(result, evaluableNodeManager);
auto result_type = result->GetType();
if(result_type == ENT_CONCLUDE)
{
PopCallStack();
return RemoveTopConcludeOrReturnNode(result, evaluableNodeManager);
}
else if(result_type == ENT_RETURN)
{
PopCallStack();
return result;
}
}

//free from previous iteration
evaluableNodeManager->FreeNodeTreeIfPossible(result);

//request immediate values when not last, since any allocs for returns would be wasted
//concludes won't be immediate
result = InterpretNode(ocn[i], immediate_result || i + 1 < ocn_size);
}

//all finished with new context, but can't free it in case returning something
PopCallStack();

return result;
}

Expand Down Expand Up @@ -823,11 +844,18 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_DECLARE(EvaluableNode *en,
//run code
for(size_t i = 1; i < ocn_size; i++)
{
if(!result.IsImmediateValue() && result != nullptr && result->GetType() == ENT_CONCLUDE)
return RemoveConcludeFromConclusion(result, evaluableNodeManager);
if(result.IsNonNullNodeReference())
{
auto result_type = result->GetType();
if(result_type == ENT_CONCLUDE)
return RemoveTopConcludeOrReturnNode(result, evaluableNodeManager);
else if(result_type == ENT_RETURN)
return result;
}

//free from previous iteration
evaluableNodeManager->FreeNodeTreeIfPossible(result);

//request immediate values when not last, since any allocs for returns would be wasted
//concludes won't be immediate
result = InterpretNode(ocn[i], immediate_result || i + 1 < ocn_size);
Expand Down
Loading

0 comments on commit e4761bb

Please sign in to comment.