Click here to Skip to main content
15,885,920 members
Articles / Containers / Virtual Machine

Conscript: An embeddable, compiled scripting language for .NET

Rate me:
Please Sign up or sign in to vote.
4.97/5 (58 votes)
5 Sep 2008CPOL15 min read 158.4K   1.4K   141  
An API for enhancing any .NET application with a scripting language
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;

using Conscript.Collections;
using Conscript.Runtime;

namespace Conscript.Compiler
{
    /// <summary>
    /// Represents the source and compiled form of a script.
    /// </summary>
    public class Script
    {
        #region Private Variables

        private ScriptManager m_scriptManager;
        private String m_strName;
        private List<String> m_listSourceLines;
        private ScriptExecutable m_scriptExecutable;

        #endregion

        #region Private Methods

        private void LoadScript(String strScriptName)
        {
            ScriptLoader scriptLoader = m_scriptManager.Loader;
            m_listSourceLines = scriptLoader.LoadScript(strScriptName);
            m_listSourceLines.Add(" "); // add extra space to aid lexer

            Dictionary<String, bool> dictIncluedScripts
                = new Dictionary<string, bool>();

            for (int iIndex = 0; iIndex < m_listSourceLines.Count; iIndex++)
            {
                String strSourceLine = m_listSourceLines[iIndex];
                List<String> listSourceLinesSingle = new List<string>();
                listSourceLinesSingle.Add(strSourceLine);

                ScriptLexer scriptLexer = new ScriptLexer(listSourceLinesSingle);
                List<Token> listTokens = null;
                try
                {
                    listTokens = scriptLexer.GetTokens();
                }
                catch (Exception)
                {
                    // if unexpected end of stream, ignore line
                    continue;
                }

                // ignore if empty line
                if (listTokens.Count == 0) continue;

                // ignore if first token is not include
                if (listTokens[0].Type != TokenType.Include) continue;

                // expect more tokens after include
                if (listTokens.Count < 2)
                    throw new ParserException(
                        "Include path expected in include statement.");

                // expect string literal
                if (listTokens[1].Type != TokenType.String)
                    throw new ParserException(
                        "String literal expected after 'include' keyword.");

                // expect semicolon
                if (listTokens.Count < 3)
                    throw new ParserException(
                        "Semicolon ';' expected at the end of the include statement.");
                if (listTokens[2].Type != TokenType.SemiColon)
                    throw new ParserException(
                        "Semicolon ';' expected at the end of the include statement.");
                if (listTokens.Count > 3)
                    throw new ParserException(
                        "Nothing expected after semicolon ';' at the end of the include statement.");

                // get include name
                String strScriptInclude = (String)listTokens[1].Lexeme;

                // remove include statement
                m_listSourceLines.RemoveAt(iIndex);

                // do not include script more than once
                if (dictIncluedScripts.ContainsKey(strScriptInclude))
                    continue;

                // load include script source
                List<String> listIncludeLines
                    = scriptLoader.LoadScript(strScriptInclude);

                // insert include source
                m_listSourceLines.InsertRange(iIndex, listIncludeLines);

                // keep track of included scripts
                dictIncluedScripts[strScriptInclude] = true;

                // reposition line index at newly inserted include
                --iIndex;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Conscructs a script associated with the given ScriptManager
        /// and using the given resource name.
        /// </summary>
        /// <param name="scriptManager">ScriptManager associated with
        /// the script.</param>
        /// <param name="strScriptName">Resource name for loading the
        /// script.</param>
        public Script(ScriptManager scriptManager, String strScriptName)
        {
            m_scriptManager = scriptManager;
            m_strName = strScriptName;

            try
            {
                LoadScript(strScriptName);

                ScriptLexer scriptLexer = new ScriptLexer(m_listSourceLines);
                List<Token> listTokens = scriptLexer.GetTokens();

                // parse/compile script
                ScriptParser scriptParser = new ScriptParser(this, listTokens);
                scriptParser.DebugMode = m_scriptManager.DebugMode;
                m_scriptExecutable = scriptParser.Parse();

                // optimise
                if (m_scriptManager.OptimiseCode)
                {
                    ExecutionOptimiser executionOptimiser
                        = new ExecutionOptimiser(m_scriptExecutable);
                    executionOptimiser.OptimiserInfo = false;
                    executionOptimiser.Optimise();
                }
            }
            catch (Exception exception)
            {
                throw new ConscriptException(
                    "Error while loading or compiling script '" + strScriptName
                    + "'.", exception);
            }
        }

        /// <summary>
        /// Checks if the script has a 'main' function defined.
        /// </summary>
        /// <returns>True if 'main' function defined, false otherwise.
        /// </returns>
        public bool HasMainFunction()
        {
            return m_scriptExecutable.HasMainFunction();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Script Manager associated with the script.
        /// </summary>
        public ScriptManager Manager
        {
            get { return m_scriptManager; }
        }

        /// <summary>
        /// Script resource name.
        /// </summary>
        public String Name
        {
            get { return m_strName; }
        }

        /// <summary>
        /// Script source code in line-list form.
        /// </summary>
        public ReadOnlyCollection<String> SourceLines
        {
            get { return m_listSourceLines.AsReadOnly(); } 
        }

        /// <summary>
        /// Script source in continuous string form.
        /// </summary>
        public String Source
        {
            get
            {
                StringBuilder stringBuilder = new StringBuilder();
                foreach (String strLine in m_listSourceLines)
                {
                    stringBuilder.Append(strLine);
                    stringBuilder.Append("\r\n");
                }
                return stringBuilder.ToString();
            }
        }

        /// <summary>
        /// Compiled executable associated with the script.
        /// </summary>
        public ScriptExecutable Executable
        {
            get { return m_scriptExecutable; }
        }

        /// <summary>
        /// Script-level variable dictionary. This dictionary is
        /// accessible to all <see cref="ScriptContext"/>s created
        /// for this script.
        /// </summary>
        public VariableDictionary ScriptDictionary
        {
            get { return m_scriptExecutable.ScriptDictionary; }
        }

        /// <summary>
        /// Function entry points within the script.
        /// </summary>
        public ReadOnlyDictionary<String, ScriptFunction> Functions
        {
            get
            {
                return new ReadOnlyDictionary<String,ScriptFunction>(
                    m_scriptExecutable.Functions);
            }
        }

        /// <summary>
        /// Returns the 'main' <see cref="ScriptFunction"/> if defined
        /// or throws an exception otherwise.
        /// </summary>
        public ScriptFunction MainFunction
        {
            get { return m_scriptExecutable.MainFunction; }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Malta Malta
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions