/*
* 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() { }
}
}