File: Expression\TokenCursor.cs
Web Access
Project: src\src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj (Microsoft.ML.Transforms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML.Transforms
{
    using Conditional = System.Diagnostics.ConditionalAttribute;
 
    internal sealed class TokenCursor
    {
        // This is the token stream. We cache items as we consume them.
        // This code assumes that the enumerator will produce an Eof token,
        // When the Eof is produced, _tokens is disposed and set to null.
        private IEnumerator<Token> _tokens;
 
        // The cache buffer.
        private Token[] _buffer;
 
        // The logical token index of _buffer[0].
        private int _itokBase;
 
        // The limit of the cached range within _buffer (relative to the buffer).
        private int _itokLim;
 
        // The current position within the buffer.
        private int _itokCur;
        private Token _tokCur;
        private TokKind _tidCur;
 
        // If this is >= 0, this position is "pinned" - we need to keep it in the buffer.
        // This is to support rewinding.
        private int _itokPin;
 
        public TokenCursor(IEnumerable<Token> tokens)
        {
            Contracts.AssertValue(tokens);
            _tokens = tokens.GetEnumerator();
            _buffer = new Token[0x0400];
            _itokPin = -1;
 
            // Get the first token.
            FetchCore();
 
            _tokCur = _buffer[_itokCur];
            _tidCur = _tokCur.Kind;
 
            AssertValid();
        }
 
        [Conditional("DEBUG")]
        private void AssertValid()
        {
            Contracts.AssertValue(_buffer);
 
            // _itokCur should never reach _itokLim.
            Contracts.Assert(0 <= _itokCur && _itokCur < _itokLim && _itokLim <= _buffer.Length);
 
            // The last token in the buffer is Eof iff _tokens is null.
            Contracts.Assert((_tokens != null) == (_buffer[_itokLim - 1].Kind != TokKind.Eof));
 
            // _tokCur and _tidCur should match _itokCur.
            Contracts.Assert(_tokCur == _buffer[_itokCur]);
            Contracts.Assert(_tidCur == _tokCur.Kind);
        }
 
        public Token TokCur
        {
            get
            {
                AssertValid();
                return _tokCur;
            }
        }
 
        public TokKind TidCur
        {
            get
            {
                AssertValid();
                return _tidCur;
            }
        }
 
        public TokKind CtxCur
        {
            get
            {
                AssertValid();
                return _tokCur.KindContext;
            }
        }
 
        // This fetches an additional token from _tokens and caches it.
        // If needed, makes room in the cache (_buffer) by either sliding items
        // or resizing _buffer.
        private void FetchToken()
        {
            Contracts.Assert(_tokens != null);
 
            if (_itokLim >= _buffer.Length)
            {
                // Need more room. See if we can "slide".
                if (_itokCur > 0 && _itokPin != 0)
                {
                    int itokMin = _itokCur;
                    if (0 < _itokPin && _itokPin < itokMin)
                        itokMin = _itokPin;
                    int itokSrc = itokMin;
                    int itokDst = 0;
                    while (itokSrc < _itokLim)
                        _buffer[itokDst++] = _buffer[itokSrc++];
                    if (0 < _itokPin)
                        _itokPin -= itokMin;
                    _itokLim -= itokMin;
                    _itokCur -= itokMin;
                    _itokBase += itokMin;
                }
                else
                {
                    // Need to resize the buffer.
                    Array.Resize(ref _buffer, 2 * _buffer.Length);
                }
            }
 
            FetchCore();
        }
 
        private void FetchCore()
        {
            Contracts.Assert(_tokens != null);
            Contracts.Assert(_itokLim < _buffer.Length);
 
            if (!_tokens.MoveNext())
            {
                Contracts.Assert(false, "Token stream should end with an Eof token!");
                throw Contracts.Except();
            }
 
            // Cache the new token.
            Token tok = _buffer[_itokLim++] = _tokens.Current;
            Contracts.Assert(tok != null);
 
            // See if we're done pulling items from _tokens.
            if (tok.Kind == TokKind.Eof)
            {
                _tokens.Dispose();
                _tokens = null;
            }
        }
 
        // This expects that _itokCur + ditok is either within the buffered token range or
        // just at the end of it. In other words, it does not support skipping tokens.
        private void MoveBy(int ditok)
        {
            AssertValid();
            Contracts.Assert(-_itokCur <= ditok && ditok <= _itokLim - _itokCur);
            Contracts.Assert(ditok < _itokLim - _itokCur || _tokens != null);
 
            while (ditok >= _itokLim - _itokCur)
                FetchToken();
 
            _itokCur += ditok;
            _tokCur = _buffer[_itokCur];
            _tidCur = _tokCur.Kind;
            AssertValid();
        }
 
        public TokKind TidNext()
        {
            AssertValid();
            if (_tidCur != TokKind.Eof)
                MoveBy(1);
            return _tidCur;
        }
 
        /// <summary>
        /// This expects that ItokCur + ditok is either within the buffered token range or just
        /// at the end of it. In other words, it does not support skipping tokens.
        /// </summary>
        public Token TokPeek(int ditok)
        {
            AssertValid();
            Contracts.Assert(-_itokCur <= ditok && ditok <= _itokLim - _itokCur);
            Contracts.Assert(ditok < _itokLim - _itokCur || _tokens != null);
 
            while (ditok >= _itokLim - _itokCur)
                FetchToken();
 
            return _buffer[_itokCur + ditok];
        }
    }
}