mirror of
https://github.com/actions/runner.git
synced 2025-12-18 16:26:58 +00:00
1211 lines
39 KiB
C#
1211 lines
39 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|
|
|
namespace GitHub.DistributedTask.ObjectTemplating
|
|
{
|
|
/// <summary>
|
|
/// This class allows callers to easily traverse a template object.
|
|
/// This class hides the details of expression expansion, depth tracking,
|
|
/// and memory tracking.
|
|
/// </summary>
|
|
internal sealed class TemplateUnraveler
|
|
{
|
|
internal TemplateUnraveler(
|
|
TemplateContext context,
|
|
TemplateToken template,
|
|
Int32 removeBytes)
|
|
{
|
|
m_context = context;
|
|
m_memory = context.Memory;
|
|
|
|
// Initialize the reader state
|
|
MoveFirst(template, removeBytes);
|
|
}
|
|
|
|
internal Boolean AllowScalar(
|
|
Boolean expand,
|
|
out ScalarToken scalar)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (expand)
|
|
{
|
|
Unravel(expand: true);
|
|
}
|
|
|
|
if (m_current?.Value is ScalarToken scalarToken)
|
|
{
|
|
scalar = scalarToken;
|
|
|
|
// Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
|
|
m_memory.AddBytes(scalar);
|
|
|
|
MoveNext();
|
|
return true;
|
|
}
|
|
|
|
scalar = null;
|
|
return false;
|
|
}
|
|
|
|
internal Boolean AllowSequenceStart(
|
|
Boolean expand,
|
|
out SequenceToken sequence)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (expand)
|
|
{
|
|
Unravel(expand: true);
|
|
}
|
|
|
|
if (m_current is SequenceState sequenceState && sequenceState.IsStart)
|
|
{
|
|
sequence = new SequenceToken(sequenceState.Value.FileId, sequenceState.Value.Line, sequenceState.Value.Column);
|
|
|
|
// Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
|
|
m_memory.AddBytes(sequence);
|
|
|
|
MoveNext();
|
|
return true;
|
|
}
|
|
|
|
sequence = null;
|
|
return false;
|
|
}
|
|
|
|
internal Boolean AllowSequenceEnd(Boolean expand)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (expand)
|
|
{
|
|
Unravel(expand: true);
|
|
}
|
|
|
|
if (m_current is SequenceState sequenceState && sequenceState.IsEnd)
|
|
{
|
|
MoveNext();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal Boolean AllowMappingStart(
|
|
Boolean expand,
|
|
out MappingToken mapping)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (expand)
|
|
{
|
|
Unravel(expand: true);
|
|
}
|
|
|
|
if (m_current is MappingState mappingState && mappingState.IsStart)
|
|
{
|
|
mapping = new MappingToken(mappingState.Value.FileId, mappingState.Value.Line, mappingState.Value.Column);
|
|
|
|
// Add bytes before they are emitted to the caller (so the caller doesn't have to track bytes)
|
|
m_memory.AddBytes(mapping);
|
|
|
|
MoveNext();
|
|
return true;
|
|
}
|
|
|
|
mapping = null;
|
|
return false;
|
|
}
|
|
|
|
internal Boolean AllowMappingEnd(Boolean expand)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (expand)
|
|
{
|
|
Unravel(expand: true);
|
|
}
|
|
|
|
if (m_current is MappingState mappingState && mappingState.IsEnd)
|
|
{
|
|
MoveNext();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal void ReadEnd()
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (m_current != null)
|
|
{
|
|
throw new InvalidOperationException("Expected end of template object. " + DumpState());
|
|
}
|
|
}
|
|
|
|
internal void ReadMappingEnd()
|
|
{
|
|
if (!AllowMappingEnd(expand: false))
|
|
{
|
|
throw new InvalidOperationException("Unexpected state while attempting to read the mapping end. " + DumpState());
|
|
}
|
|
}
|
|
|
|
internal void SkipSequenceItem()
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (!(m_current?.Parent is SequenceState ancestor))
|
|
{
|
|
throw new InvalidOperationException("Unexpected state while attempting to skip the current sequence item. " + DumpState());
|
|
}
|
|
|
|
MoveNext(skipNestedEvents: true);
|
|
}
|
|
|
|
internal void SkipMappingKey()
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (!(m_current?.Parent is MappingState ancestor) || !ancestor.IsKey)
|
|
{
|
|
throw new InvalidOperationException("Unexpected state while attempting to skip the current mapping key. " + DumpState());
|
|
}
|
|
|
|
MoveNext(skipNestedEvents: true);
|
|
}
|
|
|
|
internal void SkipMappingValue()
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (!(m_current?.Parent is MappingState ancestor) || ancestor.IsKey)
|
|
{
|
|
throw new InvalidOperationException("Unexpected state while attempting to skip the current mapping value. " + DumpState());
|
|
}
|
|
|
|
MoveNext(skipNestedEvents: true);
|
|
}
|
|
|
|
private String DumpState()
|
|
{
|
|
var result = new StringBuilder();
|
|
|
|
if (m_current == null)
|
|
{
|
|
result.AppendLine("State: (null)");
|
|
}
|
|
else
|
|
{
|
|
result.AppendLine("State:");
|
|
result.AppendLine();
|
|
|
|
// Push state hierarchy
|
|
var stack = new Stack<ReaderState>();
|
|
var curr = m_current;
|
|
while (curr != null)
|
|
{
|
|
result.AppendLine(curr.ToString());
|
|
curr = curr.Parent;
|
|
}
|
|
}
|
|
|
|
return result.ToString();
|
|
}
|
|
|
|
private void MoveFirst(
|
|
TemplateToken value,
|
|
Int32 removeBytes)
|
|
{
|
|
if (!(value is LiteralToken) && !(value is SequenceToken) && !(value is MappingToken) && !(value is BasicExpressionToken))
|
|
{
|
|
throw new NotSupportedException($"Unexpected type '{value?.GetType().Name}' when initializing object reader state");
|
|
}
|
|
|
|
m_memory.IncrementEvents();
|
|
m_current = ReaderState.CreateState(null, value, m_context, removeBytes);
|
|
}
|
|
|
|
private void MoveNext(Boolean skipNestedEvents = false)
|
|
{
|
|
m_memory.IncrementEvents();
|
|
|
|
if (m_current == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Sequence start
|
|
if (m_current is SequenceState sequenceState &&
|
|
sequenceState.IsStart &&
|
|
!skipNestedEvents)
|
|
{
|
|
// Move to the first item or sequence end
|
|
m_current = sequenceState.Next();
|
|
}
|
|
// Mapping start
|
|
else if (m_current is MappingState mappingState &&
|
|
mappingState.IsStart &&
|
|
!skipNestedEvents)
|
|
{
|
|
// Move to the first item key or mapping end
|
|
m_current = mappingState.Next();
|
|
}
|
|
// Parent is a sequence
|
|
else if (m_current.Parent is SequenceState parentSequenceState)
|
|
{
|
|
// Move to the next item or sequence end
|
|
m_current.Remove();
|
|
m_current = parentSequenceState.Next();
|
|
}
|
|
// Parent is a mapping
|
|
else if (m_current.Parent is MappingState parentMappingState)
|
|
{
|
|
// Move to the next item value, item key, or mapping end
|
|
m_current.Remove();
|
|
m_current = parentMappingState.Next();
|
|
}
|
|
// Parent is an expression end
|
|
else if (m_current.Parent != null)
|
|
{
|
|
m_current.Remove();
|
|
m_current = m_current.Parent;
|
|
}
|
|
// Parent is null
|
|
else
|
|
{
|
|
m_current.Remove();
|
|
m_current = null;
|
|
}
|
|
|
|
m_expanded = false;
|
|
Unravel(expand: false);
|
|
}
|
|
|
|
private void Unravel(Boolean expand)
|
|
{
|
|
if (m_expanded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (m_current == null)
|
|
{
|
|
break;
|
|
}
|
|
// Literal
|
|
else if (m_current is LiteralState literalState)
|
|
{
|
|
break;
|
|
}
|
|
else if (m_current is BasicExpressionState basicExpressionState)
|
|
{
|
|
// Sequence item is a basic expression start
|
|
// For example:
|
|
// steps:
|
|
// - script: credScan
|
|
// - ${{ parameters.preBuild }}
|
|
// - script: build
|
|
if (basicExpressionState.IsStart &&
|
|
m_current.Parent is SequenceState)
|
|
{
|
|
if (expand)
|
|
{
|
|
SequenceItemBasicExpression();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Mapping key is a basic expression start
|
|
// For example:
|
|
// steps:
|
|
// - ${{ parameters.scriptHost }}: echo hi
|
|
else if (basicExpressionState.IsStart &&
|
|
m_current.Parent is MappingState parentMappingState &&
|
|
parentMappingState.IsKey)
|
|
{
|
|
if (expand)
|
|
{
|
|
MappingKeyBasicExpression();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Mapping value is a basic expression start
|
|
// For example:
|
|
// steps:
|
|
// - script: credScan
|
|
// - script: ${{ parameters.tool }}
|
|
else if (basicExpressionState.IsStart &&
|
|
m_current.Parent is MappingState parentMappingState2 &&
|
|
!parentMappingState2.IsKey)
|
|
{
|
|
if (expand)
|
|
{
|
|
MappingValueBasicExpression();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (basicExpressionState.IsStart &&
|
|
m_current.Parent is null)
|
|
{
|
|
if (expand)
|
|
{
|
|
RootBasicExpression();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Basic expression end
|
|
else if (basicExpressionState.IsEnd)
|
|
{
|
|
EndExpression();
|
|
}
|
|
else
|
|
{
|
|
UnexpectedState();
|
|
}
|
|
}
|
|
else if (m_current is MappingState mappingState)
|
|
{
|
|
// Mapping end, closing an "insert" mapping insertion
|
|
if (mappingState.IsEnd &&
|
|
m_current.Parent is InsertExpressionState)
|
|
{
|
|
m_current.Remove();
|
|
m_current = m_current.Parent; // Skip to the expression end
|
|
}
|
|
// Normal mapping start
|
|
else if (mappingState.IsStart)
|
|
{
|
|
break;
|
|
}
|
|
// Normal mapping end
|
|
else if (mappingState.IsEnd)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
UnexpectedState();
|
|
}
|
|
}
|
|
else if (m_current is SequenceState sequenceState)
|
|
{
|
|
// Sequence end, closing a sequence insertion
|
|
if (sequenceState.IsEnd &&
|
|
m_current.Parent is BasicExpressionState &&
|
|
m_current.Parent.Parent is SequenceState)
|
|
{
|
|
m_current.Remove();
|
|
m_current = m_current.Parent; // Skip to the expression end
|
|
}
|
|
// Normal sequence start
|
|
else if (sequenceState.IsStart)
|
|
{
|
|
break;
|
|
}
|
|
// Normal sequence end
|
|
else if (sequenceState.IsEnd)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
UnexpectedState();
|
|
}
|
|
}
|
|
else if (m_current is InsertExpressionState insertExpressionState)
|
|
{
|
|
// Mapping key, beginning an "insert" mapping insertion
|
|
// For example:
|
|
// - job: a
|
|
// variables:
|
|
// ${{ insert }}: ${{ parameters.jobVariables }}
|
|
if (insertExpressionState.IsStart &&
|
|
m_current.Parent is MappingState parentMappingState &&
|
|
parentMappingState.IsKey)
|
|
{
|
|
if (expand)
|
|
{
|
|
StartMappingInsertion();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// Expression end
|
|
else if (insertExpressionState.IsEnd)
|
|
{
|
|
EndExpression();
|
|
}
|
|
// Not allowed
|
|
else if (insertExpressionState.IsStart)
|
|
{
|
|
m_context.Error(insertExpressionState.Value, TemplateStrings.DirectiveNotAllowed(insertExpressionState.Value.Directive));
|
|
m_current.Remove();
|
|
m_current = insertExpressionState.ToStringToken();
|
|
}
|
|
else
|
|
{
|
|
UnexpectedState();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnexpectedState();
|
|
}
|
|
|
|
m_memory.IncrementEvents();
|
|
} while (true);
|
|
|
|
m_expanded = expand;
|
|
}
|
|
|
|
private void SequenceItemBasicExpression()
|
|
{
|
|
// The template looks like:
|
|
//
|
|
// steps:
|
|
// - ${{ parameters.preSteps }}
|
|
// - script: build
|
|
//
|
|
// The current state looks like:
|
|
//
|
|
// MappingState // The document starts with a mapping
|
|
//
|
|
// SequenceState // The "steps" sequence
|
|
//
|
|
// BasicExpressionState // m_current
|
|
|
|
var expressionState = m_current as BasicExpressionState;
|
|
var expression = expressionState.Value;
|
|
TemplateToken value;
|
|
var removeBytes = 0;
|
|
try
|
|
{
|
|
value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_context.Error(expression, ex);
|
|
value = null;
|
|
}
|
|
|
|
// Move to the nested sequence, skip the sequence start
|
|
if (value is SequenceToken nestedSequence)
|
|
{
|
|
m_current = expressionState.Next(nestedSequence, isSequenceInsertion: true, removeBytes: removeBytes);
|
|
}
|
|
// Move to the new value
|
|
else if (value != null)
|
|
{
|
|
m_current = expressionState.Next(value, removeBytes);
|
|
}
|
|
// Move to the expression end
|
|
else if (value == null)
|
|
{
|
|
expressionState.End();
|
|
}
|
|
}
|
|
|
|
private void MappingKeyBasicExpression()
|
|
{
|
|
// The template looks like:
|
|
//
|
|
// steps:
|
|
// - ${{ parameters.scriptHost }}: echo hi
|
|
//
|
|
// The current state looks like:
|
|
//
|
|
// MappingState // The document starts with a mapping
|
|
//
|
|
// SequenceState // The "steps" sequence
|
|
//
|
|
// MappingState // The step mapping
|
|
//
|
|
// BasicExpressionState // m_current
|
|
|
|
// The expression should evaluate to a string
|
|
var expressionState = m_current as BasicExpressionState;
|
|
var expression = expressionState.Value as BasicExpressionToken;
|
|
StringToken stringToken;
|
|
var removeBytes = 0;
|
|
try
|
|
{
|
|
stringToken = expression.EvaluateStringToken(expressionState.Context, out removeBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_context.Error(expression, ex);
|
|
stringToken = null;
|
|
}
|
|
|
|
// Move to the stringToken
|
|
if (stringToken != null)
|
|
{
|
|
m_current = expressionState.Next(stringToken, removeBytes);
|
|
}
|
|
// Move to the next key or mapping end
|
|
else
|
|
{
|
|
m_current.Remove();
|
|
var parentMappingState = m_current.Parent as MappingState;
|
|
parentMappingState.Next().Remove(); // Skip the value
|
|
m_current = parentMappingState.Next(); // Next key or mapping end
|
|
}
|
|
}
|
|
|
|
private void MappingValueBasicExpression()
|
|
{
|
|
// The template looks like:
|
|
//
|
|
// steps:
|
|
// - script: credScan
|
|
// - script: ${{ parameters.tool }}
|
|
//
|
|
// The current state looks like:
|
|
//
|
|
// MappingState // The document starts with a mapping
|
|
//
|
|
// SequenceState // The "steps" sequence
|
|
//
|
|
// MappingState // The step mapping
|
|
//
|
|
// BasicExpressionState // m_current
|
|
|
|
var expressionState = m_current as BasicExpressionState;
|
|
var expression = expressionState.Value;
|
|
TemplateToken value;
|
|
var removeBytes = 0;
|
|
try
|
|
{
|
|
value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_context.Error(expression, ex);
|
|
value = new StringToken(expression.FileId, expression.Line, expression.Column, String.Empty);
|
|
}
|
|
|
|
// Move to the new value
|
|
m_current = expressionState.Next(value, removeBytes);
|
|
}
|
|
|
|
private void RootBasicExpression()
|
|
{
|
|
// The template looks like:
|
|
//
|
|
// ${{ parameters.tool }}
|
|
//
|
|
// The current state looks like:
|
|
//
|
|
// BasicExpressionState // m_current
|
|
|
|
var expressionState = m_current as BasicExpressionState;
|
|
var expression = expressionState.Value;
|
|
TemplateToken value;
|
|
var removeBytes = 0;
|
|
try
|
|
{
|
|
value = expression.EvaluateTemplateToken(expressionState.Context, out removeBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_context.Error(expression, ex);
|
|
value = new StringToken(expression.FileId, expression.Line, expression.Column, String.Empty);
|
|
}
|
|
|
|
// Move to the new value
|
|
m_current = expressionState.Next(value, removeBytes);
|
|
}
|
|
|
|
private void StartMappingInsertion()
|
|
{
|
|
// The template looks like:
|
|
//
|
|
// jobs:
|
|
// - job: a
|
|
// variables:
|
|
// ${{ insert }}: ${{ parameters.jobVariables }}
|
|
//
|
|
// The current state looks like:
|
|
//
|
|
// MappingState // The document starts with a mapping
|
|
//
|
|
// SequenceState // The "jobs" sequence
|
|
//
|
|
// MappingState // The "job" mapping
|
|
//
|
|
// MappingState // The "variables" mapping
|
|
//
|
|
// InsertExpressionState // m_current
|
|
|
|
var expressionState = m_current as InsertExpressionState;
|
|
var parentMappingState = expressionState.Parent as MappingState;
|
|
var nestedValue = parentMappingState.Value[parentMappingState.Index].Value;
|
|
var nestedMapping = nestedValue as MappingToken;
|
|
var removeBytes = 0;
|
|
if (nestedMapping != null)
|
|
{
|
|
// Intentionally empty
|
|
}
|
|
else if (nestedValue is BasicExpressionToken basicExpression)
|
|
{
|
|
// The expression should evaluate to a mapping
|
|
try
|
|
{
|
|
nestedMapping = basicExpression.EvaluateMappingToken(expressionState.Context, out removeBytes);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_context.Error(basicExpression, ex);
|
|
nestedMapping = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_context.Error(nestedValue, TemplateStrings.ExpectedMapping());
|
|
nestedMapping = null;
|
|
}
|
|
|
|
// Move to the nested first key
|
|
if (nestedMapping?.Count > 0)
|
|
{
|
|
m_current = expressionState.Next(nestedMapping, removeBytes);
|
|
}
|
|
// Move to the expression end
|
|
else
|
|
{
|
|
if (removeBytes > 0)
|
|
{
|
|
m_memory.SubtractBytes(removeBytes);
|
|
}
|
|
|
|
expressionState.End();
|
|
}
|
|
}
|
|
|
|
private void EndExpression()
|
|
{
|
|
// End of document
|
|
if (m_current.Parent == null)
|
|
{
|
|
m_current.Remove();
|
|
m_current = null;
|
|
}
|
|
// End basic expression
|
|
else if (m_current is BasicExpressionState)
|
|
{
|
|
// Move to the next item or sequence end
|
|
if (m_current.Parent is SequenceState parentSequenceState)
|
|
{
|
|
m_current.Remove();
|
|
m_current = parentSequenceState.Next();
|
|
}
|
|
// Move to the next key, next value, or mapping end
|
|
else
|
|
{
|
|
m_current.Remove();
|
|
var parentMappingState = m_current.Parent as MappingState;
|
|
m_current = parentMappingState.Next();
|
|
}
|
|
}
|
|
// End "insert" mapping insertion
|
|
else
|
|
{
|
|
// Move to the next key or mapping end
|
|
m_current.Remove();
|
|
var parentMappingState = m_current.Parent as MappingState;
|
|
parentMappingState.Next().Remove(); // Skip the value
|
|
m_current = parentMappingState.Next();
|
|
}
|
|
}
|
|
|
|
private void UnexpectedState()
|
|
{
|
|
throw new InvalidOperationException("Expected state while unraveling expressions. " + DumpState());
|
|
}
|
|
|
|
private abstract class ReaderState
|
|
{
|
|
public ReaderState(
|
|
ReaderState parent,
|
|
TemplateToken value,
|
|
TemplateContext context)
|
|
{
|
|
Parent = parent;
|
|
Value = value;
|
|
Context = context;
|
|
}
|
|
|
|
public static ReaderState CreateState(
|
|
ReaderState parent,
|
|
TemplateToken value,
|
|
TemplateContext context,
|
|
Int32 removeBytes = 0)
|
|
{
|
|
switch (value.Type)
|
|
{
|
|
case TokenType.Null:
|
|
case TokenType.Boolean:
|
|
case TokenType.Number:
|
|
case TokenType.String:
|
|
return new LiteralState(parent, value as LiteralToken, context, removeBytes);
|
|
|
|
case TokenType.Sequence:
|
|
return new SequenceState(parent, value as SequenceToken, context, removeBytes);
|
|
|
|
case TokenType.Mapping:
|
|
return new MappingState(parent, value as MappingToken, context, removeBytes);
|
|
|
|
case TokenType.BasicExpression:
|
|
return new BasicExpressionState(parent, value as BasicExpressionToken, context, removeBytes);
|
|
|
|
case TokenType.InsertExpression:
|
|
if (removeBytes > 0)
|
|
{
|
|
throw new InvalidOperationException($"Unexpected {nameof(removeBytes)}");
|
|
}
|
|
|
|
return new InsertExpressionState(parent, value as InsertExpressionToken, context);
|
|
|
|
default:
|
|
throw new NotSupportedException($"Unexpected {nameof(ReaderState)} type: {value?.GetType().Name}");
|
|
}
|
|
}
|
|
|
|
public ReaderState Parent { get; }
|
|
public TemplateContext Context { get; protected set; }
|
|
public TemplateToken Value { get; }
|
|
|
|
public abstract void Remove();
|
|
}
|
|
|
|
private abstract class ReaderState<T> : ReaderState
|
|
where T : class
|
|
{
|
|
public ReaderState(
|
|
ReaderState parent,
|
|
TemplateToken value,
|
|
TemplateContext context)
|
|
: base(parent, value, context)
|
|
{
|
|
}
|
|
|
|
public new T Value
|
|
{
|
|
get
|
|
{
|
|
if (!Object.ReferenceEquals(base.Value, m_value))
|
|
{
|
|
m_value = base.Value as T;
|
|
}
|
|
|
|
return m_value;
|
|
}
|
|
}
|
|
|
|
private T m_value;
|
|
}
|
|
|
|
private sealed class LiteralState : ReaderState<LiteralToken>
|
|
{
|
|
public LiteralState(
|
|
ReaderState parent,
|
|
LiteralToken literal,
|
|
TemplateContext context,
|
|
Int32 removeBytes)
|
|
: base(parent, literal, context)
|
|
{
|
|
context.Memory.AddBytes(literal);
|
|
context.Memory.IncrementDepth();
|
|
m_removeBytes = removeBytes;
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
Context.Memory.SubtractBytes(Value);
|
|
Context.Memory.DecrementDepth();
|
|
|
|
// Subtract the memory overhead of the template token.
|
|
// We are now done traversing it and pointers to it no longer need to exist.
|
|
if (m_removeBytes > 0)
|
|
{
|
|
Context.Memory.SubtractBytes(m_removeBytes);
|
|
}
|
|
}
|
|
|
|
public override String ToString()
|
|
{
|
|
var result = new StringBuilder();
|
|
result.AppendLine($"{GetType().Name}");
|
|
return result.ToString();
|
|
}
|
|
|
|
private Int32 m_removeBytes;
|
|
}
|
|
|
|
private sealed class SequenceState : ReaderState<SequenceToken>
|
|
{
|
|
public SequenceState(
|
|
ReaderState parent,
|
|
SequenceToken sequence,
|
|
TemplateContext context,
|
|
Int32 removeBytes)
|
|
: base(parent, sequence, context)
|
|
{
|
|
context.Memory.AddBytes(sequence);
|
|
context.Memory.IncrementDepth();
|
|
m_removeBytes = removeBytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether the state represents the sequence-start event
|
|
/// </summary>
|
|
public Boolean IsStart { get; private set; } = true;
|
|
|
|
/// <summary>
|
|
/// The current index within the sequence
|
|
/// </summary>
|
|
public Int32 Index { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Indicates whether the state represents the sequence-end event
|
|
/// </summary>
|
|
public Boolean IsEnd => !IsStart && Index >= Value.Count;
|
|
|
|
public ReaderState Next()
|
|
{
|
|
// Adjust the state
|
|
if (IsStart)
|
|
{
|
|
IsStart = false;
|
|
}
|
|
else
|
|
{
|
|
Index++;
|
|
}
|
|
|
|
// Return the next event
|
|
if (!IsEnd)
|
|
{
|
|
return CreateState(this, Value[Index], Context);
|
|
}
|
|
else
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public ReaderState End()
|
|
{
|
|
IsStart = false;
|
|
Index = Value.Count;
|
|
return this;
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
Context.Memory.SubtractBytes(Value);
|
|
Context.Memory.DecrementDepth();
|
|
|
|
// Subtract the memory overhead of the template token.
|
|
// We are now done traversing it and pointers to it no longer need to exist.
|
|
if (m_removeBytes > 0)
|
|
{
|
|
Context.Memory.SubtractBytes(m_removeBytes);
|
|
}
|
|
}
|
|
|
|
public override String ToString()
|
|
{
|
|
var result = new StringBuilder();
|
|
result.AppendLine($"{GetType().Name}:");
|
|
result.AppendLine($" IsStart: {IsStart}");
|
|
result.AppendLine($" Index: {Index}");
|
|
result.AppendLine($" IsEnd: {IsEnd}");
|
|
return result.ToString();
|
|
}
|
|
|
|
private Int32 m_removeBytes;
|
|
}
|
|
|
|
private sealed class MappingState : ReaderState<MappingToken>
|
|
{
|
|
public MappingState(
|
|
ReaderState parent,
|
|
MappingToken mapping,
|
|
TemplateContext context,
|
|
Int32 removeBytes)
|
|
: base(parent, mapping, context)
|
|
{
|
|
context.Memory.AddBytes(mapping);
|
|
context.Memory.IncrementDepth();
|
|
m_removeBytes = removeBytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether the state represents the mapping-start event
|
|
/// </summary>
|
|
public Boolean IsStart { get; private set; } = true;
|
|
|
|
/// <summary>
|
|
/// The current index within the mapping
|
|
/// </summary>
|
|
public Int32 Index { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Indicates whether the state represents a mapping-key position
|
|
/// </summary>
|
|
public Boolean IsKey { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Indicates whether the state represents the mapping-end event
|
|
/// </summary>
|
|
public Boolean IsEnd => !IsStart && Index >= Value.Count;
|
|
|
|
public ReaderState Next()
|
|
{
|
|
// Adjust the state
|
|
if (IsStart)
|
|
{
|
|
IsStart = false;
|
|
IsKey = true;
|
|
}
|
|
else if (IsKey)
|
|
{
|
|
IsKey = false;
|
|
}
|
|
else
|
|
{
|
|
Index++;
|
|
IsKey = true;
|
|
}
|
|
|
|
// Return the next event
|
|
if (!IsEnd)
|
|
{
|
|
if (IsKey)
|
|
{
|
|
return CreateState(this, Value[Index].Key, Context);
|
|
}
|
|
else
|
|
{
|
|
return CreateState(this, Value[Index].Value, Context);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public ReaderState End()
|
|
{
|
|
IsStart = false;
|
|
Index = Value.Count;
|
|
return this;
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
Context.Memory.SubtractBytes(Value);
|
|
Context.Memory.DecrementDepth();
|
|
|
|
// Subtract the memory overhead of the template token.
|
|
// We are now done traversing it and pointers to it no longer need to exist.
|
|
if (m_removeBytes > 0)
|
|
{
|
|
Context.Memory.SubtractBytes(m_removeBytes);
|
|
}
|
|
}
|
|
|
|
public override String ToString()
|
|
{
|
|
var result = new StringBuilder();
|
|
result.AppendLine($"{GetType().Name}:");
|
|
result.AppendLine($" IsStart: {IsStart}");
|
|
result.AppendLine($" Index: {Index}");
|
|
result.AppendLine($" IsKey: {IsKey}");
|
|
result.AppendLine($" IsEnd: {IsEnd}");
|
|
return result.ToString();
|
|
}
|
|
|
|
private Int32 m_removeBytes;
|
|
}
|
|
|
|
private sealed class BasicExpressionState : ReaderState<BasicExpressionToken>
|
|
{
|
|
public BasicExpressionState(
|
|
ReaderState parent,
|
|
BasicExpressionToken expression,
|
|
TemplateContext context,
|
|
Int32 removeBytes)
|
|
: base(parent, expression, context)
|
|
{
|
|
context.Memory.AddBytes(expression);
|
|
context.Memory.IncrementDepth();
|
|
m_removeBytes = removeBytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether entering the expression
|
|
/// </summary>
|
|
public Boolean IsStart { get; private set; } = true;
|
|
|
|
/// <summary>
|
|
/// Indicates whether leaving the expression
|
|
/// </summary>
|
|
public Boolean IsEnd => !IsStart;
|
|
|
|
public ReaderState Next(
|
|
TemplateToken value,
|
|
Int32 removeBytes = 0)
|
|
{
|
|
// Adjust the state
|
|
IsStart = false;
|
|
|
|
// Return the nested state
|
|
return CreateState(this, value, Context, removeBytes);
|
|
}
|
|
|
|
public ReaderState Next(
|
|
SequenceToken value,
|
|
Boolean isSequenceInsertion = false,
|
|
Int32 removeBytes = 0)
|
|
{
|
|
// Adjust the state
|
|
IsStart = false;
|
|
|
|
// Create the nested state
|
|
var nestedState = CreateState(this, value, Context, removeBytes);
|
|
if (isSequenceInsertion)
|
|
{
|
|
var nestedSequenceState = nestedState as SequenceState;
|
|
return nestedSequenceState.Next(); // Skip the sequence start
|
|
}
|
|
else
|
|
{
|
|
return nestedState;
|
|
}
|
|
}
|
|
|
|
public ReaderState End()
|
|
{
|
|
IsStart = false;
|
|
return this;
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
Context.Memory.SubtractBytes(Value);
|
|
Context.Memory.DecrementDepth();
|
|
|
|
// Subtract the memory overhead of the template token.
|
|
// We are now done traversing it and pointers to it no longer need to exist.
|
|
if (m_removeBytes > 0)
|
|
{
|
|
Context.Memory.SubtractBytes(m_removeBytes);
|
|
}
|
|
}
|
|
|
|
public override String ToString()
|
|
{
|
|
var result = new StringBuilder();
|
|
result.AppendLine($"{GetType().Name}:");
|
|
result.AppendLine($" IsStart: {IsStart}");
|
|
return result.ToString();
|
|
}
|
|
|
|
private Int32 m_removeBytes;
|
|
}
|
|
|
|
private sealed class InsertExpressionState : ReaderState<InsertExpressionToken>
|
|
{
|
|
public InsertExpressionState(
|
|
ReaderState parent,
|
|
InsertExpressionToken expression,
|
|
TemplateContext context)
|
|
: base(parent, expression, context)
|
|
{
|
|
Context.Memory.AddBytes(expression);
|
|
Context.Memory.IncrementDepth();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether entering or leaving the expression
|
|
/// </summary>
|
|
public Boolean IsStart { get; private set; } = true;
|
|
|
|
/// <summary>
|
|
/// Indicates whether leaving the expression
|
|
/// </summary>
|
|
public Boolean IsEnd => !IsStart;
|
|
|
|
public ReaderState Next(
|
|
MappingToken value,
|
|
Int32 removeBytes = 0)
|
|
{
|
|
// Adjust the state
|
|
IsStart = false;
|
|
|
|
// Create the nested state
|
|
var nestedState = CreateState(this, value, Context, removeBytes) as MappingState;
|
|
return nestedState.Next(); // Skip the mapping start
|
|
}
|
|
|
|
public ReaderState End()
|
|
{
|
|
IsStart = false;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This happens when the expression is not allowed
|
|
/// </summary>
|
|
public ReaderState ToStringToken()
|
|
{
|
|
var literal = new StringToken(Value.FileId, Value.Line, Value.Column, $"{TemplateConstants.OpenExpression} {Value.Directive} {TemplateConstants.CloseExpression}");
|
|
return CreateState(Parent, literal, Context);
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
Context.Memory.SubtractBytes(Value);
|
|
Context.Memory.DecrementDepth();
|
|
}
|
|
|
|
public override String ToString()
|
|
{
|
|
var result = new StringBuilder();
|
|
result.AppendLine($"{GetType().Name}:");
|
|
result.AppendLine($" IsStart: {IsStart}");
|
|
return result.ToString();
|
|
}
|
|
}
|
|
|
|
private readonly TemplateContext m_context;
|
|
private readonly TemplateMemory m_memory;
|
|
private ReaderState m_current;
|
|
private Boolean m_expanded;
|
|
}
|
|
}
|