/* * Script.cs - Manages Level9.Net script files. * * Copyright (C) 2004 - 2011 Andreas Scherrer * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ using System; using System.Collections.Generic; using System.Text; using Antlr.Runtime; namespace Level9 { /// /// Manages Level9.Net script files. /// public sealed class Script { // -- Constants ------------------------------------------------------ /// /// The line length in Level9.Net script files. /// const int SCRIPT_LINE_LENGTH = 80; /// /// The title prefix in Level9.Net script files. /// const string SCRIPT_TITLE = "#title: "; /// /// The comment prefix in Level9.Net script files. /// const string SCRIPT_COMMENT = "#comment: "; /// /// The newline characters in Level9.Net script files. /// const string SCRIPT_NEWLINE = "\r\n"; /// /// The command separator in Level9.Net script files. /// const string SCRIPT_COMMAND_SEPARATOR = ";"; /// /// The starting bracket of comments in Level9.Net script files. /// const string SCRIPT_COMMAND_BRACKET_BEGIN = "["; /// /// The ending bracket of comments in Level9.Net script files. /// const string SCRIPT_COMMAND_BRACKET_END = "]"; // -- Attributes ----------------------------------------------------- /// /// The one and only Script instance (singleton). /// static Script instance = null; /// /// This property indicates whether script recording is active. /// bool isrecording = false; /// /// This property indicates whether script playback is active. /// bool isplaying = false; /// /// The current script's title. /// string title = null; /// /// The current script's comment. /// string comment = null; /// /// A list of script entries. A script entry consists of a command and /// a comment. /// List entries = new List(); /// /// A list of errors that occurred while parsing the current script. /// List errors = new List(); /// /// The start index of the script's playback range. /// int startIndex = -1; /// /// The end index of the script's playback range. /// int endIndex = int.MaxValue; /// /// The script's current playback position. /// int playbackPos = 0; /// /// This property indicates whether ScriptChanged events are fired or /// not. /// bool fireEvents = true; // -- Events --------------------------------------------------------- /// Occurs when the script has changed. public event ScriptChangedEventHandler ScriptChanged; // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the Script class. /// private Script() { Interpreter.InputFinished += new InputFinishedEventHandler(InterpreterInputFinished); } // -- Methods -------------------------------------------------------- /// /// Notifies the listeners that the script has changed. /// public void FireScriptChanged() { if (ScriptChanged != null && fireEvents) { ScriptChanged(new ScriptChangedEventArgs()); } } /// /// Invoked when the user has finished an input line. /// /// /// a InputFinishedEventArgs that contains the event data /// void InterpreterInputFinished(InputFinishedEventArgs e) { if (isrecording && e.Input != null && e.Input.Length > 0) { entries.Add(new ScriptEntry(e.Input, null)); FireScriptChanged(); } } /// /// Adds a script entry. /// /// the script entry /// /// false if script playback is active or an error occurred, else true /// public bool AddEntry(ScriptEntry entry) { if (isplaying || entry == null) { return false; } entries.Add(new ScriptEntry(entry.Input, entry.Comment)); FireScriptChanged(); return true; } /// /// Inserts a script entry before or after the given entry. /// /// the script entry's index /// /// false if script playback is active or an error occurred, else true /// public bool InsertEntry(int index) { if (isplaying || index < 0 || (index > 0 && index > entries.Count)) { return false; } entries.Insert(index, new ScriptEntry()); FireScriptChanged(); return true; } /// /// Removes script entries. /// /// the script entries' index /// /// false if script playback is active or an error occurred, else true /// public bool RemoveEntry(int index) { if (isplaying || index < 0 || (index > 0 && index > entries.Count)) { return false; } entries.RemoveAt(index); FireScriptChanged(); return true; } /// /// Removes the control mark from a script entry. /// /// the script entries' index /// /// false if script playback is active or an error occurred, else true /// public bool RemoveControlMark(int index) { if (isplaying || index < 0 || index >= entries.Count) { return false; } if (index == startIndex) { startIndex = -1; } if (index == endIndex) { endIndex = int.MaxValue; } entries[index].Pause = false; FireScriptChanged(); return true; } /// /// Provides a copy of the script entry at the given index. /// /// the script entry's index /// a script entry or null if an error occurred public ScriptEntry GetEntryAt(int index) { if (index < 0 || index >= entries.Count) { return null; } ScriptEntry entry = new ScriptEntry(entries[index].Input, entries[index].Comment); entry.Pause = entries[index].Pause; return entry; } /// /// Sets the input of a script entry at the given index. /// /// the entry's index /// the entry's input /// /// false if script playback is active or an error occurred, else true /// public bool SetInputAt(int index, string input) { if (isplaying || index < 0 || index >= entries.Count) { return false; } entries[index].Input = input; FireScriptChanged(); return true; } /// /// Sets the comment of a script entry at the given index. /// /// the script entry's index /// the script entry's comment /// /// false if script playback is active or an error occurred, else true /// public bool SetCommentAt(int index, string comment) { if (isplaying || index < 0 || index >= entries.Count) { return false; } entries[index].Comment = comment; FireScriptChanged(); return true; } /// /// Sets the 'pause' property of a script entry at the given index. /// /// the script entry's index /// /// false if script playback is active or an error occurred, else true /// public bool SetPauseAt(int index) { if (isplaying || index < 0 || index >= entries.Count) { return false; } entries[index].Pause = true; FireScriptChanged(); return true; } /// /// Searches the script entries for the occurrence of a search /// term. /// /// the search term /// the start index /// /// if set to true the search looks for previous occurrences of the /// search term /// /// /// the index of a script entry if the search term has been found, /// else -1 /// public int FindText(string text, int start, bool previous) { if (start < 0 || start >= entries.Count || text == null || (text != null && text.Length == 0)) { return -1; } int i = start; int count = entries.Count; for (; count >= 0; count--) { if (EntryMatch(text, entries[i])) { return i; } if (previous) { i = (i == 0) ? entries.Count - 1 : i - 1; } else { i = (i == entries.Count - 1) ? 0 : i + 1; } } return -1; } /// /// Checks if a given text is contained in the input or comment of a /// script entry. /// /// the search term /// the script entry /// /// true if the input or comment of the script entry contains the /// given text, else false /// bool EntryMatch(string text, ScriptEntry entry) { string s1 = text.ToLower(); string s2 = null; if (entry.Input != null) { s2 = entry.Input.ToLower(); if (s2.Contains(s1)) { return true; } } if (entry.Comment != null) { s2 = entry.Comment.ToLower(); if (s2.Contains(s1)) { return true; } } return false; } /// /// Returns the number of script entries. /// /// the number of script entries public int GetEntriesCount() { return entries.Count; } /// /// Clears the script content. /// /// false if script playback is active, else true public bool Clear() { if (isplaying) { return false; } title = null; comment = null; startIndex = -1; endIndex = int.MaxValue; errors.Clear(); entries.Clear(); FireScriptChanged(); return true; } /// /// Loads a Level9.Net script. /// /// the script's file name /// false if script playback is active, else true public bool Load(string filename) { if (isplaying) { return false; } fireEvents = false; ICharStream input = new ANTLRFileStream(filename); L9Script lex = new L9Script(input); try { Logger.Log(this, "Load: " + filename); Clear(); while (lex.NextToken() != Token.EOF_TOKEN); if (errors.Count > 0) { ErrorHandler.Handle(this, "Error parsing script file: " + errors[0].Message + ", line: " + errors[0].Line + ", column: " + errors[0].Column); } } catch (Exception ex) { ErrorHandler.Handle(this, "Error parsing script file: " + ex.Message, ex); } fireEvents = true; FireScriptChanged(); return true; } /// /// Creates a string representation of the current script. /// /// /// if set to true the script includes comments for each entry /// /// /// if set to true the script contains one entry per line /// /// a string representation of the current script public string Create(bool includeComments, bool oneEntryPerLine) { StringBuilder script = new StringBuilder(); // // Script header // if (title != null && title.Length > 0) { List lines = TextUtils.SplitParagraph( SCRIPT_TITLE + title + SCRIPT_COMMAND_SEPARATOR, SCRIPT_LINE_LENGTH); for (int i = 0; i < lines.Count; i++) { script.Append(lines[i]); script.Append(SCRIPT_NEWLINE); } } if (comment != null && comment.Length > 0) { List lines = TextUtils.SplitParagraph( SCRIPT_COMMENT + comment + SCRIPT_COMMAND_SEPARATOR, SCRIPT_LINE_LENGTH); for (int i = 0; i < lines.Count; i++) { script.Append(lines[i]); script.Append(SCRIPT_NEWLINE); } } if (script.Length > 0) { script.Append(SCRIPT_NEWLINE); } // // Commands // if (oneEntryPerLine) { for (int i = 0; i < entries.Count; i++) { ScriptEntry entry = entries[i]; if ((entry.Input != null && entry.Input.Length == 0) || entry.Input == null) { continue; } script.Append(entry.Input); if (includeComments && entry.Comment != null && entry.Comment.Length > 0) { script.Append(" "); script.Append(SCRIPT_COMMAND_BRACKET_BEGIN); script.Append(entry.Comment); script.Append(SCRIPT_COMMAND_BRACKET_END); } script.Append(SCRIPT_COMMAND_SEPARATOR); script.Append(SCRIPT_NEWLINE); } } else { StringBuilder entriesStr = new StringBuilder(); for (int i = 0; i < entries.Count; i++) { ScriptEntry entry = entries[i]; if ((entry.Input != null && entry.Input.Length == 0) || entry.Input == null) { continue; } entriesStr.Append(entry.Input); if (includeComments && entry.Comment != null && entry.Comment.Length > 0) { entriesStr.Append(" "); entriesStr.Append(SCRIPT_COMMAND_BRACKET_BEGIN); entriesStr.Append(entry.Comment); entriesStr.Append(SCRIPT_COMMAND_BRACKET_END); } entriesStr.Append(SCRIPT_COMMAND_SEPARATOR); if (i < entries.Count - 1) { entriesStr.Append(" "); } } List lines = TextUtils.SplitParagraph( entriesStr.ToString(), SCRIPT_LINE_LENGTH); for (int i = 0; i < lines.Count; i++) { script.Append(lines[i]); script.Append(SCRIPT_NEWLINE); } } return script.ToString(); } /// /// Starts the script recording. /// /// false if script playback is active, else true public bool StartRecording() { if (isplaying) { return false; } Logger.Log(this, "StartRecording"); isrecording = true; FireScriptChanged(); return true; } /// /// Stops the script recording. /// public void StopRecording() { Logger.Log(this, "StopRecording"); isrecording = false; FireScriptChanged(); } /// /// Starts the script playback. /// /// /// false if script recording is active or an error occurred, else true /// public bool StartPlayback() { if (isrecording || entries.Count == 0) { return false; } playbackPos = StartIndex; Logger.Log(this, "StartPlayback"); isplaying = true; FireScriptChanged(); return true; } /// /// Stops the script playback. /// public void StopPlayback() { Logger.Log(this, "StopPlayback"); isplaying = false; FireScriptChanged(); } /// /// Provides the next script entry in playback mode. /// /// the script entry (return value) /// /// if set to true playback should be paused (return value) /// /// /// the script entry's index or -1 if script playback isn't active /// or the playback range has been exceeded /// public int GetNextInput(out string input, out bool pause) { input = null; pause = false; if (!isplaying) { return -1; } if (playbackPos > EndIndex) { StopPlayback(); return -1; } while (playbackPos <= EndIndex) { input = entries[playbackPos].Input; if (input == null || (input != null && input.Length == 0)) { playbackPos++; } else { pause = entries[playbackPos].Pause; Logger.Log(this, "GetNextInput: [" + playbackPos + "] " + input + ", pause: " + pause); playbackPos++; return playbackPos - 1; } } return -1; } // -- Accessors ------------------------------------------------------ /// /// Gets the one and only Script instance (singleton). /// public static Script Instance { get { if (instance == null) { instance = new Script(); } return instance; } } /// /// Gets a value that indicates whether script recording is active. /// public bool IsRecording { get { return isrecording; } } /// /// Gets a value that indicates whether script playback is active. /// public bool IsPlaying { get { return isplaying; } } /// /// Gets the current playback position. /// public int PlaybackPos { get { return playbackPos; } } /// /// Gets s list of errors that occurred while parsing the current /// script. /// public List Errors { get { return errors; } } /// /// Gets or sets the current script's title. /// public string Title { get { return title; } set { this.title = value; FireScriptChanged(); } } /// /// Gets or sets the current script's comment. /// public string Comment { get { return comment; } set { this.comment = value; FireScriptChanged(); } } /// /// Gets or sets the start index of the script's playback range. /// public int StartIndex { get { return startIndex == -1 ? 0 : startIndex; } set { if (!isplaying) { this.startIndex = value; FireScriptChanged(); } } } /// /// Gets or sets the end index of the script's playback range. /// public int EndIndex { get { if (endIndex == int.MaxValue) { return (entries.Count > 0 ? entries.Count - 1 : 0); } else { return endIndex; } } set { if (!isplaying) { this.endIndex = value; FireScriptChanged(); } } } } /// /// Represents a Level9.Net script entry that consists of user input and /// a comment. /// public class ScriptEntry { // -- Attributes ----------------------------------------------------- /// /// The script entry's user input. /// string input = null; /// /// The script entry's comment. /// string comment = null; /// /// This property indicates whether the script playback should be /// interrupted at this entry. /// bool pause = false; // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the ScriptEntry class. /// public ScriptEntry() {} /// /// Initializes a new instance of the ScriptEntry class. /// /// the user input /// a comment public ScriptEntry(string input, string comment) { this.input = input; this.comment = comment; } // -- Accessors ------------------------------------------------------ /// /// Gets or sets the script entry's user input. /// public string Input { get { return input; } set { this.input = value; } } /// /// Gets or sets the script entry's comment. /// public string Comment { get { return comment; } set { this.comment = value; } } /// /// Gets or sets a value that indicates whether the script playback /// should be interrupted at this entry. /// public bool Pause { get { return pause; } set { this.pause = value; } } } /// /// Represents a grammar error that occurs while parsing a Level9.Net /// script file. /// public class LexerError { // -- Attributes ----------------------------------------------------- /// /// The script line containing the grammar error. /// int line; /// /// The column position of the grammar error. /// int column; /// /// The error message. /// string message = null; // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the ScriptEntry class. /// /// the error message /// /// the script line containing the grammar error /// /// /// the column position of the grammar error /// public LexerError(string message, int line, int column) { this.message = message; this.line = line; this.column = column; } // -- Accessors ------------------------------------------------------ /// /// Gets or sets the script line containing the grammar error. /// public int Line { get { return line; } set { line = value; } } /// /// Gets or sets the column position of the grammar error. /// public int Column { get { return column; } set { column = value; } } /// /// Gets or sets the error message. /// public string Message { get { return message; } set { message = value; } } } /// /// Represents the method that handles a ScriptChanged event. /// public delegate void ScriptChangedEventHandler(ScriptChangedEventArgs e); /// /// Provides data for the ScriptChanged event. /// public class ScriptChangedEventArgs : EventArgs { // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the ScriptChangedEventArgs class. /// public ScriptChangedEventArgs() { } } }