<commit_before>﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ProjectHekate.Scripting
{
    public enum IdentifierType
    {
        Property,
        Variable
    }

    public struct IdentifierRecord
    {
        private readonly string _name;
        private readonly int _index;
        
        public string Name
        {
            get { return _name; }
        }

        public int Index
        {
            get { return _index; }
        }

        public IdentifierRecord(string name, int index)
        {
            _name = name;
            _index = index;
        }
    }

    public class CodeBlock
    {
        public int Index { get; set;  }

        public IReadOnlyList<float> Code { get; private set; }
        private readonly List<float> _code;

        public IReadOnlyList<IdentifierRecord> NumericalVariables { get; private set; }
        private readonly List<IdentifierRecord> _numericalVariables;
        private readonly Dictionary<string, int> _numericalVariablesNameToIndex;

        public IReadOnlyList<IdentifierRecord> EmitterVariables { get; private set; }
        private readonly List<IdentifierRecord> _emitterVariables;
        private readonly Dictionary<string, int> _emitterVariablesNameToIndex;
        
        public CodeBlock()
        {
            _code = new List<float>();
            Code = _code.AsReadOnly();

            _numericalVariables = new List<IdentifierRecord>();
            _numericalVariablesNameToIndex = new Dictionary<string, int>();

            _emitterVariables = new List<IdentifierRecord>();
            _emitterVariablesNameToIndex = new Dictionary<string, int>();
            
            NumericalVariables = _numericalVariables.AsReadOnly();
            EmitterVariables = _emitterVariables.AsReadOnly();
        }

        public void Add(Instruction inst)
        {
            _code.Add((byte)inst);
        }

        public void Add(byte b)
        {
            _code.Add(b);
        }

        public void Add(CodeBlock block)
        {
            if(block == null) throw new ArgumentNullException("block");

            _code.AddRange(block.Code);
        }

        public void Add(float f)
        {
            _code.Add(f);
        }

        /// <summary>
        /// Adds a numerical variable to the code record.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the index of the identifier</returns>
        /// <exception cref="System.ArgumentException">Thrown when an identifier with that name already exists</exception>
        public int AddNumericalVariable(string name)
        {
            return AddIdentifierToList(name, _numericalVariables, _numericalVariablesNameToIndex);
        }

        /// <summary>
        /// Gets the numerical variable with a given name.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the identifier record belong to the numerical variable with the given name</returns>
        /// <exception cref="System.ArgumentException">Thrown when a variable with that name does not exist</exception>
        public IdentifierRecord GetNumericalVariable(string name)
        {
            return GetSpecificIdentifier(name, _numericalVariables, _numericalVariablesNameToIndex);
        }

        /// <summary>
        /// Adds a emitter variable to the code record.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the index of the variable</returns>
        /// <exception cref="System.ArgumentException">Thrown when an variable with that name already exists</exception>
        public int AddEmitterVariable(string name)
        {
            return AddIdentifierToList(name, _emitterVariables, _emitterVariablesNameToIndex);
        }

        /// <summary>
        /// Gets the emitter variable with a given name.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the identifier record belonging to the emitter with the given name</returns>
        /// <exception cref="System.ArgumentException">Thrown when a variable with that name does not exist</exception>
        public IdentifierRecord GetEmitterVariable(string name)
        {
            return GetSpecificIdentifier(name, _emitterVariables, _emitterVariablesNameToIndex);
        }

        private int AddIdentifierToList(string name, ICollection<IdentifierRecord> identifierList, IDictionary<string, int> identifierNameToIndexMap)
        {
            // TODO: make method thread-safe
            // TODO: make sure it checks all other identifiers too (emitters being made, final emitters, etc)
            if (_numericalVariablesNameToIndex.ContainsKey(name))
                throw new ArgumentException("An identifier with the name \"" + name + "\" already exists (as a numerical variable) in this script.", "name");

            var identifier = new IdentifierRecord(name, identifierList.Count); // this is so bad and unsafe
            identifierList.Add(identifier);
            identifierNameToIndexMap[name] = identifier.Index;

            return identifier.Index;
        }

        private IdentifierRecord GetSpecificIdentifier(string name, IList<IdentifierRecord> identifierList, IDictionary<string, int> identifierNameToIndexMap)
        {
            int index;
            var worked = identifierNameToIndexMap.TryGetValue(name, out index);

            if (!worked) throw new ArgumentException("An identifier with the name " + name + " could not be found.");

            return identifierList[index];
        }
    }
    
    public class FunctionCodeBlock : CodeBlock
    {
        public FunctionCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class BulletUpdaterCodeBlock : CodeBlock
    {
        public BulletUpdaterCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class EmitterUpdaterCodeBlock : CodeBlock
    {
        public EmitterUpdaterCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class ScriptState
    {
        public int CurrentInstructionIndex { get; set; }
        public int CodeBlockIndex { get; set; }

        public IReadOnlyList<float> NumericalVariables { get; set; }
        public IReadOnlyList<object> EmitterVariables { get; set; } // TODO: change this to SOMETHING else
        public IReadOnlyList<float> Properties { get; set; }  

        private float[] _numericalVariables;
        private object[] _emitterVariables; // TODO: change this to SOMETHING else
        private float[] _properties;

        public ScriptState()
        {
            _numericalVariables = new float[VirtualMachine.MaxNumericalVariables];
            _emitterVariables = new object[VirtualMachine.MaxEmitterVariables];
            _properties = new float[VirtualMachine.MaxProperties];

            NumericalVariables = Array.AsReadOnly(_numericalVariables);
            EmitterVariables = Array.AsReadOnly(_emitterVariables);
            Properties = Array.AsReadOnly(_properties);
        }
    }

    public interface IVirtualMachine
    {
        IReadOnlyList<FunctionCodeBlock> FunctionCodeBlocks { get; }
        IReadOnlyList<BulletUpdaterCodeBlock> BulletUpdaterCodeBlocks { get; }
        IReadOnlyList<EmitterUpdaterCodeBlock> EmitterUpdaterCodeBlocks { get; }
        IReadOnlyList<IdentifierRecord> PropertyRecords { get; } 

        /// <summary>
        /// Adds a function code block to the virtual machine.
        /// </summary>
        /// <param name="name">The name of the bullet updater</param>
        /// <param name="codeBlock">The function code block</param>
        /// <returns>Returns the index of the function code block (also populates the Index property of the code block)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a function with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the function has already been added, but with a different name</exception>
        int AddFunctionCodeBlock(string name, FunctionCodeBlock codeBlock);

        /// <summary>
        /// Adds a bullet updater code block to the virtual machine.
        /// </summary>
        /// <param name="name">The name of the bullet updater</param>
        /// <param name="codeBlock">The bullet updater code block</param>
        /// <returns>Returns the index of the bullet updater code block (also populates the Index property of the code block)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a bullet updater with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the bullet updater has already been added, but with a different name</exception>
        int AddBulletUpdaterCodeBlock(string name, BulletUpdaterCodeBlock codeBlock);

        /// <summary>
        /// Adds an emitter updater code block to the program code block.
        /// </summary>
        /// <param name="name">The name of the emitter updater</param>
        /// <param name="codeBlock">The code block for the emitter updater</param>
        /// <returns>Returns the index of the emitter updater code block (also populates the Index property of the codeBlock)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a emitter updater with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the emitter updater has already been added, but with a different name</exception>
        int AddEmitterUpdaterCodeBlock(string name, EmitterUpdaterCodeBlock codeBlock);

        /// <summary>
        /// Adds a property to the virtual machine. A property is a float-type variable that belongs to all emitters.
        /// </summary>
        /// <param name="name">The name of the property</param>
        /// <returns>Returns the index of the property</returns>
        /// <exception cref="System.ArgumentException">Thrown when a property with that name already exists</exception>
        int AddProperty(string name);

        /// <summary>
        /// Gets a property
        /// </summary>
        /// <param name="name"></param>
        /// <returns>Returns the identifier record of the property with the given name</returns>
        /// <exception cref="ArgumentException">Thrown when a property with that name does not exist</exception>
        IdentifierRecord GetProperty(string name);
    }

    public class VirtualMachine : IVirtualMachine
    {
        public const int MaxNumericalVariables = 64;
        public const int MaxEmitterVariables = 8;
        public const int MaxProperties = 32;


        public IReadOnlyList<FunctionCodeBlock> FunctionCodeBlocks { get; private set; }
        public IReadOnlyList<BulletUpdaterCodeBlock> BulletUpdaterCodeBlocks { get; private set; }
        public IReadOnlyList<EmitterUpdaterCodeBlock> EmitterUpdaterCodeBlocks { get; private set; }
        public IReadOnlyList<IdentifierRecord> PropertyRecords { get; private set; }

        private readonly List<FunctionCodeBlock> _functionCodeBlocks;
        private readonly List<BulletUpdaterCodeBlock> _bulletUpdaterCodeBlocks;
        private readonly List<EmitterUpdaterCodeBlock> _emitterUpdaterCodeBlocks;
        private readonly List<IdentifierRecord> _propertyRecords;
        private readonly Dictionary<string, int> _functionCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _bulletUpdaterCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _emitterUpdaterCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _propertyNameToIndex;

        public VirtualMachine()
        {
            _functionCodeBlocks = new List<FunctionCodeBlock>();
            _functionCodeBlockNameToIndex = new Dictionary<string, int>();

            _bulletUpdaterCodeBlocks = new List<BulletUpdaterCodeBlock>();
            _bulletUpdaterCodeBlockNameToIndex = new Dictionary<string, int>();

            _emitterUpdaterCodeBlocks = new List<EmitterUpdaterCodeBlock>();
            _emitterUpdaterCodeBlockNameToIndex = new Dictionary<string, int>();

            _propertyRecords = new List<IdentifierRecord>();
            _propertyNameToIndex = new Dictionary<string, int>();

            FunctionCodeBlocks = _functionCodeBlocks.AsReadOnly();
            BulletUpdaterCodeBlocks = _bulletUpdaterCodeBlocks.AsReadOnly();
            EmitterUpdaterCodeBlocks = _emitterUpdaterCodeBlocks.AsReadOnly();
            PropertyRecords = _propertyRecords.AsReadOnly();
        }

        public int AddFunctionCodeBlock(string name, FunctionCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _functionCodeBlocks, _functionCodeBlockNameToIndex, codeBlock);
        }

        public int AddBulletUpdaterCodeBlock(string name, BulletUpdaterCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _bulletUpdaterCodeBlocks, _bulletUpdaterCodeBlockNameToIndex, codeBlock);
        }

        public int AddEmitterUpdaterCodeBlock(string name, EmitterUpdaterCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _emitterUpdaterCodeBlocks, _emitterUpdaterCodeBlockNameToIndex, codeBlock);
        }

        public int AddProperty(string name)
        {
            // TODO: make method thread-safe
            if (_propertyNameToIndex.ContainsKey(name))
                throw new ArgumentException("A property with the name \"" + name + "\" already exists in this virtual machine.", "name");

            var identifier = new IdentifierRecord(name, _propertyRecords.Count); // this is so bad and unsafe
            _propertyRecords.Add(identifier);
            _propertyNameToIndex[name] = identifier.Index;

            return identifier.Index;
        }

        public IdentifierRecord GetProperty(string name)
        {
            int index;
            var worked = _propertyNameToIndex.TryGetValue(name, out index);

            if (!worked) throw new ArgumentException("A property with the name " + name + " could not be found.");

            return _propertyRecords[index];
        }

        private int AddSpecializedCodeBlock<TCodeBlockType>(string name, ICollection<TCodeBlockType> codeBlockList, IDictionary<string,int> codeBlockNameToIndexMap, TCodeBlockType codeBlock) where TCodeBlockType : CodeBlock
        {
            // TODO: make method thread-safe
            ThrowIfCodeBlockWithNameAlreadyExists(name);
            ThrowIfCodeBlockAlreadyExists(codeBlock);

            codeBlockList.Add(codeBlock);
            codeBlock.Index = codeBlockList.Count;
            codeBlockNameToIndexMap[name] = codeBlock.Index;

            return codeBlock.Index;
        }

        private void ThrowIfCodeBlockWithNameAlreadyExists(string name)
        {
            if (_functionCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("A function with the name \"" + name + "\" already exists in this script.", "name");
            if (_bulletUpdaterCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("A bullet updater with the name \"" + name + "\" already exists in this script.", "name");
            if (_emitterUpdaterCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("An emitter updater with the name \"" + name + "\" already exists in this script.", "name");
        }

        private void ThrowIfCodeBlockAlreadyExists(CodeBlock codeBlock)
        {
            if (_functionCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as a function).", "codeBlock");
            if (_bulletUpdaterCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as a bullet updater).", "codeBlock");
            if (_emitterUpdaterCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as an emitter updater).", "codeBlock");
        }
    }
}
<commit_msg>Adds more getters to the VirtualMachine (for functions, emitter updaters, bullet updaters).<commit_after>﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ProjectHekate.Scripting
{
    public enum IdentifierType
    {
        Property,
        Variable
    }

    public struct IdentifierRecord
    {
        private readonly string _name;
        private readonly int _index;
        
        public string Name
        {
            get { return _name; }
        }

        public int Index
        {
            get { return _index; }
        }

        public IdentifierRecord(string name, int index)
        {
            _name = name;
            _index = index;
        }
    }

    public class CodeBlock
    {
        public int Index { get; set;  }

        public IReadOnlyList<float> Code { get; private set; }
        private readonly List<float> _code;

        public IReadOnlyList<IdentifierRecord> NumericalVariables { get; private set; }
        private readonly List<IdentifierRecord> _numericalVariables;
        private readonly Dictionary<string, int> _numericalVariablesNameToIndex;

        public IReadOnlyList<IdentifierRecord> EmitterVariables { get; private set; }
        private readonly List<IdentifierRecord> _emitterVariables;
        private readonly Dictionary<string, int> _emitterVariablesNameToIndex;
        
        public CodeBlock()
        {
            _code = new List<float>();
            Code = _code.AsReadOnly();

            _numericalVariables = new List<IdentifierRecord>();
            _numericalVariablesNameToIndex = new Dictionary<string, int>();

            _emitterVariables = new List<IdentifierRecord>();
            _emitterVariablesNameToIndex = new Dictionary<string, int>();
            
            NumericalVariables = _numericalVariables.AsReadOnly();
            EmitterVariables = _emitterVariables.AsReadOnly();
        }

        public void Add(Instruction inst)
        {
            _code.Add((byte)inst);
        }

        public void Add(byte b)
        {
            _code.Add(b);
        }

        public void Add(CodeBlock block)
        {
            if(block == null) throw new ArgumentNullException("block");

            _code.AddRange(block.Code);
        }

        public void Add(float f)
        {
            _code.Add(f);
        }

        /// <summary>
        /// Adds a numerical variable to the code record.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the index of the identifier</returns>
        /// <exception cref="System.ArgumentException">Thrown when an identifier with that name already exists</exception>
        public int AddNumericalVariable(string name)
        {
            return AddIdentifierToList(name, _numericalVariables, _numericalVariablesNameToIndex);
        }

        /// <summary>
        /// Gets the numerical variable with a given name.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the identifier record belong to the numerical variable with the given name</returns>
        /// <exception cref="System.ArgumentException">Thrown when a variable with that name does not exist</exception>
        public IdentifierRecord GetNumericalVariable(string name)
        {
            return GetSpecificIdentifier(name, _numericalVariables, _numericalVariablesNameToIndex);
        }

        /// <summary>
        /// Adds a emitter variable to the code record.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the index of the variable</returns>
        /// <exception cref="System.ArgumentException">Thrown when an variable with that name already exists</exception>
        public int AddEmitterVariable(string name)
        {
            return AddIdentifierToList(name, _emitterVariables, _emitterVariablesNameToIndex);
        }

        /// <summary>
        /// Gets the emitter variable with a given name.
        /// </summary>
        /// <param name="name">The name of the variable</param>
        /// <returns>Returns the identifier record belonging to the emitter with the given name</returns>
        /// <exception cref="System.ArgumentException">Thrown when a variable with that name does not exist</exception>
        public IdentifierRecord GetEmitterVariable(string name)
        {
            return GetSpecificIdentifier(name, _emitterVariables, _emitterVariablesNameToIndex);
        }

        private int AddIdentifierToList(string name, ICollection<IdentifierRecord> identifierList, IDictionary<string, int> identifierNameToIndexMap)
        {
            // TODO: make method thread-safe
            // TODO: make sure it checks all other identifiers too (emitters being made, final emitters, etc)
            if (_numericalVariablesNameToIndex.ContainsKey(name))
                throw new ArgumentException("An identifier with the name \"" + name + "\" already exists (as a numerical variable) in this script.", "name");

            var identifier = new IdentifierRecord(name, identifierList.Count); // this is so bad and unsafe
            identifierList.Add(identifier);
            identifierNameToIndexMap[name] = identifier.Index;

            return identifier.Index;
        }

        private IdentifierRecord GetSpecificIdentifier(string name, IList<IdentifierRecord> identifierList, IDictionary<string, int> identifierNameToIndexMap)
        {
            int index;
            var worked = identifierNameToIndexMap.TryGetValue(name, out index);

            if (!worked) throw new ArgumentException("An identifier with the name " + name + " could not be found.");

            return identifierList[index];
        }
    }
    
    public class FunctionCodeBlock : CodeBlock
    {
        public FunctionCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class BulletUpdaterCodeBlock : CodeBlock
    {
        public BulletUpdaterCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class EmitterUpdaterCodeBlock : CodeBlock
    {
        public EmitterUpdaterCodeBlock(IEnumerable<string> paramNames)
        {
            // the parameters are added as local variables
            foreach(var paramName in paramNames) {
                AddNumericalVariable(paramName);
            }
        }
    }

    public class ScriptState
    {
        public int CurrentInstructionIndex { get; set; }
        public int CodeBlockIndex { get; set; }

        public IReadOnlyList<float> NumericalVariables { get; set; }
        public IReadOnlyList<object> EmitterVariables { get; set; } // TODO: change this to SOMETHING else
        public IReadOnlyList<float> Properties { get; set; }  

        private float[] _numericalVariables;
        private object[] _emitterVariables; // TODO: change this to SOMETHING else
        private float[] _properties;

        public ScriptState()
        {
            _numericalVariables = new float[VirtualMachine.MaxNumericalVariables];
            _emitterVariables = new object[VirtualMachine.MaxEmitterVariables];
            _properties = new float[VirtualMachine.MaxProperties];

            NumericalVariables = Array.AsReadOnly(_numericalVariables);
            EmitterVariables = Array.AsReadOnly(_emitterVariables);
            Properties = Array.AsReadOnly(_properties);
        }
    }

    public interface IVirtualMachine
    {
        IReadOnlyList<FunctionCodeBlock> FunctionCodeBlocks { get; }
        IReadOnlyList<BulletUpdaterCodeBlock> BulletUpdaterCodeBlocks { get; }
        IReadOnlyList<EmitterUpdaterCodeBlock> EmitterUpdaterCodeBlocks { get; }
        IReadOnlyList<IdentifierRecord> PropertyRecords { get; } 

        /// <summary>
        /// Adds a function code block to the virtual machine.
        /// </summary>
        /// <param name="name">The name of the bullet updater</param>
        /// <param name="codeBlock">The function code block</param>
        /// <returns>Returns the index of the function code block (also populates the Index property of the code block)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a function with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the function has already been added, but with a different name</exception>
        int AddFunctionCodeBlock(string name, FunctionCodeBlock codeBlock);

        /// <summary>
        /// Gets a function code block by name if it exists.
        /// </summary>
        /// <param name="name">The name of the function code block</param>
        /// <returns>The function code block mapped with the given name</returns>
        /// <exception cref="ArgumentException">Thrown when a function with that name does not exist</exception>
        FunctionCodeBlock GetFunctionCodeBlock(string name);

        /// <summary>
        /// Adds a bullet updater code block to the virtual machine.
        /// </summary>
        /// <param name="name">The name of the bullet updater</param>
        /// <param name="codeBlock">The bullet updater code block</param>
        /// <returns>Returns the index of the bullet updater code block (also populates the Index property of the code block)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a bullet updater with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the bullet updater has already been added, but with a different name</exception>
        int AddBulletUpdaterCodeBlock(string name, BulletUpdaterCodeBlock codeBlock);

        /// <summary>
        /// Gets a bullet updater code block by name if it exists.
        /// </summary>
        /// <param name="name">The name of the bullet updater code block</param>
        /// <returns>The bullet updater code block mapped with the given name</returns>
        /// <exception cref="ArgumentException">Thrown when a bullet updater with that name does not exist</exception>
        BulletUpdaterCodeBlock GetBulletUpdaterCodeBlock(string name);

        /// <summary>
        /// Adds an emitter updater code block to the program code block.
        /// </summary>
        /// <param name="name">The name of the emitter updater</param>
        /// <param name="codeBlock">The code block for the emitter updater</param>
        /// <returns>Returns the index of the emitter updater code block (also populates the Index property of the codeBlock)</returns>
        /// <exception cref="System.ArgumentException">Thrown when a emitter updater with that name already exists</exception>
        /// <exception cref="System.ArgumentException">Thrown when the emitter updater has already been added, but with a different name</exception>
        int AddEmitterUpdaterCodeBlock(string name, EmitterUpdaterCodeBlock codeBlock);

        /// <summary>
        /// Gets a emitter updater code block by name if it exists.
        /// </summary>
        /// <param name="name">The name of the emitter updater code block</param>
        /// <returns>The emitter updater code block mapped with the given name</returns>
        /// <exception cref="ArgumentException">Thrown when a emitter updater with that name does not exist</exception>
        EmitterUpdaterCodeBlock GetEmitterUpdaterCodeBlock(string name);

        /// <summary>
        /// Adds a property to the virtual machine. A property is a float-type variable that belongs to all emitters.
        /// </summary>
        /// <param name="name">The name of the property</param>
        /// <returns>Returns the index of the property</returns>
        /// <exception cref="System.ArgumentException">Thrown when a property with that name already exists</exception>
        int AddProperty(string name);

        /// <summary>
        /// Gets a property.
        /// </summary>
        /// <param name="name"></param>
        /// <returns>Returns the identifier record of the property with the given name</returns>
        /// <exception cref="ArgumentException">Thrown when a property with that name does not exist</exception>
        IdentifierRecord GetProperty(string name);
    }

    public class VirtualMachine : IVirtualMachine
    {
        public const int MaxNumericalVariables = 64;
        public const int MaxEmitterVariables = 8;
        public const int MaxProperties = 32;


        public IReadOnlyList<FunctionCodeBlock> FunctionCodeBlocks { get; private set; }
        public IReadOnlyList<BulletUpdaterCodeBlock> BulletUpdaterCodeBlocks { get; private set; }
        public IReadOnlyList<EmitterUpdaterCodeBlock> EmitterUpdaterCodeBlocks { get; private set; }
        public IReadOnlyList<IdentifierRecord> PropertyRecords { get; private set; }

        private readonly List<FunctionCodeBlock> _functionCodeBlocks;
        private readonly List<BulletUpdaterCodeBlock> _bulletUpdaterCodeBlocks;
        private readonly List<EmitterUpdaterCodeBlock> _emitterUpdaterCodeBlocks;
        private readonly List<IdentifierRecord> _propertyRecords;
        private readonly Dictionary<string, int> _functionCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _bulletUpdaterCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _emitterUpdaterCodeBlockNameToIndex;
        private readonly Dictionary<string, int> _propertyNameToIndex;

        public VirtualMachine()
        {
            _functionCodeBlocks = new List<FunctionCodeBlock>();
            _functionCodeBlockNameToIndex = new Dictionary<string, int>();

            _bulletUpdaterCodeBlocks = new List<BulletUpdaterCodeBlock>();
            _bulletUpdaterCodeBlockNameToIndex = new Dictionary<string, int>();

            _emitterUpdaterCodeBlocks = new List<EmitterUpdaterCodeBlock>();
            _emitterUpdaterCodeBlockNameToIndex = new Dictionary<string, int>();

            _propertyRecords = new List<IdentifierRecord>();
            _propertyNameToIndex = new Dictionary<string, int>();

            FunctionCodeBlocks = _functionCodeBlocks.AsReadOnly();
            BulletUpdaterCodeBlocks = _bulletUpdaterCodeBlocks.AsReadOnly();
            EmitterUpdaterCodeBlocks = _emitterUpdaterCodeBlocks.AsReadOnly();
            PropertyRecords = _propertyRecords.AsReadOnly();
        }

        public int AddFunctionCodeBlock(string name, FunctionCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _functionCodeBlocks, _functionCodeBlockNameToIndex, codeBlock);
        }

        public FunctionCodeBlock GetFunctionCodeBlock(string name)
        {
            return GetSpecializedCodeBlock(name, "function", _functionCodeBlocks, _functionCodeBlockNameToIndex);
        }

        public int AddBulletUpdaterCodeBlock(string name, BulletUpdaterCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _bulletUpdaterCodeBlocks, _bulletUpdaterCodeBlockNameToIndex, codeBlock);
        }

        public BulletUpdaterCodeBlock GetBulletUpdaterCodeBlock(string name)
        {
            return GetSpecializedCodeBlock(name, "bullet updater", _bulletUpdaterCodeBlocks, _bulletUpdaterCodeBlockNameToIndex);
        }

        public int AddEmitterUpdaterCodeBlock(string name, EmitterUpdaterCodeBlock codeBlock)
        {
            return AddSpecializedCodeBlock(name, _emitterUpdaterCodeBlocks, _emitterUpdaterCodeBlockNameToIndex, codeBlock);
        }

        public EmitterUpdaterCodeBlock GetEmitterUpdaterCodeBlock(string name)
        {
            return GetSpecializedCodeBlock(name, "emitter updater", _emitterUpdaterCodeBlocks, _emitterUpdaterCodeBlockNameToIndex);
        }

        public int AddProperty(string name)
        {
            // TODO: make method thread-safe
            if (_propertyNameToIndex.ContainsKey(name))
                throw new ArgumentException("A property with the name \"" + name + "\" already exists in this virtual machine.", "name");

            var identifier = new IdentifierRecord(name, _propertyRecords.Count); // this is so bad and unsafe
            _propertyRecords.Add(identifier);
            _propertyNameToIndex[name] = identifier.Index;

            return identifier.Index;
        }

        public IdentifierRecord GetProperty(string name)
        {
            return GetSpecializedCodeBlock(name, "property", _propertyRecords, _propertyNameToIndex);
        }

        private int AddSpecializedCodeBlock<TCodeBlockType>(string name, ICollection<TCodeBlockType> codeBlockList, IDictionary<string,int> codeBlockNameToIndexMap, TCodeBlockType codeBlock) where TCodeBlockType : CodeBlock
        {
            // TODO: make method thread-safe
            ThrowIfCodeBlockWithNameAlreadyExists(name);
            ThrowIfCodeBlockAlreadyExists(codeBlock);

            codeBlockList.Add(codeBlock);
            codeBlock.Index = codeBlockList.Count;
            codeBlockNameToIndexMap[name] = codeBlock.Index;

            return codeBlock.Index;
        }

        private TCodeBlockType GetSpecializedCodeBlock<TCodeBlockType>(string name, string nameOfCodeBlockForExceptionMessage, IReadOnlyList<TCodeBlockType> specializedCodeBlocks, Dictionary<string, int> specializedCodeBlockNameToIndex)
        {
            if(!specializedCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("A " + nameOfCodeBlockForExceptionMessage + " with the name \"" + name + "\" could not be found.", name);

            return specializedCodeBlocks[specializedCodeBlockNameToIndex[name]];
        }

        private void ThrowIfCodeBlockWithNameAlreadyExists(string name)
        {
            if (_functionCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("A function with the name \"" + name + "\" already exists in this script.", "name");
            if (_bulletUpdaterCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("A bullet updater with the name \"" + name + "\" already exists in this script.", "name");
            if (_emitterUpdaterCodeBlockNameToIndex.ContainsKey(name))
                throw new ArgumentException("An emitter updater with the name \"" + name + "\" already exists in this script.", "name");
        }

        private void ThrowIfCodeBlockAlreadyExists(CodeBlock codeBlock)
        {
            if (_functionCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as a function).", "codeBlock");
            if (_bulletUpdaterCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as a bullet updater).", "codeBlock");
            if (_emitterUpdaterCodeBlocks.Contains(codeBlock))
                throw new ArgumentException("This code block has already been added, but with a different name (as an emitter updater).", "codeBlock");
        }
    }
}

{'fraction_non_alphanumeric': 0.08214862903004903, 'fraction_numerical': 0.000273920070123538, 'mean_word_length': 2.6431493862887936, 'pattern_counts': {'":': 0, '<': 298, '<?xml version=': 0, '>': 298, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '514233', 'n_tokens_mistral': 8850, 'n_tokens_neox': 8683, 'n_words': 2622}