This is my first attempt of writing simple script interpreter.
Part 1: The State & Engine core
Part 2: Command Routines & Forms
CODE
' A Generic Scripting engine.
' By AdamSpeight2008
' Inspiration: Logo by Daniel G. Bobrow, Wally Feurzeig and Seymour Papert.
#Region "THE SCRIPT ENGINE"
Public Class ScriptEngine
Private Const ScriptName = "PenScript v1.00"
Private Tokens_Comment() As String = {"'"}
Private Tokens_Delimiter() As String = {",", " "}
Protected Friend mState As New ScriptState
#Region "PenScript Commands"
' PEN UP
' PEN DOWN
' PEN COLOR RED
' PEN COLOR GREEN
' PEN COLOR BLUE
' PEN COLOR BLACK
' PEN COLOR <RED>,<GREEN>,<BLUE>
' PEN WIDTH NORMAL
' PEN WIDTH BIG
' PEN WIDTH THIN
' PEN WIDTH <WIDTH>
' MOVE <X>,<Y>
' FORWARD <DISTANCE>
' TURN LEFT <DEGREES>
' TURN RIGHT <DEGREES>
' ROTATE <DEGREES>
' MAKESHAPE <SHAPE NAME>
' ENDSHAPE
' DRAWSHAPE <SHAPE NAME>
#End Region
#Region "States used by script engine"
' #### Start of Properties used by the script. ####
Partial Class ScriptState
Public myShapes As New List(Of LogoShape)
Public ShapeStack As New Stack(Of String)
Private mCurrentInstruction As String
Private mMakingShape As Boolean
Private mWidth As Single = 1
Private mAngle As Double = 180
Private mPenDown As Boolean
Private mShapeNumber As Integer
Private mX As Double = 0
Private mY As Double = 0
Private mLineNumber As Integer
Private mColor As Color = Drawing.Color.Black
Public Property CurrentInstruction() As String
Get
Return mCurrentInstruction
End Get
Set(ByVal value As String)
mCurrentInstruction = value
End Set
End Property
Public Property MakingShape() As Boolean
Get
Return mMakingShape
End Get
Set(ByVal value As Boolean)
mMakingShape = value
End Set
End Property
Public Property Width() As Single
Get
Return mWidth
End Get
Set(ByVal value As Single)
mWidth = value
End Set
End Property
Public Property Angle() As Double
Get
Return mAngle
End Get
Set(ByVal value As Double)
mAngle = value
End Set
End Property
Public Property PenDown() As Boolean
Get
Return mPenDown
End Get
Set(ByVal value As Boolean)
mPenDown = value
End Set
End Property
Public Property X() As Double
Get
Return mX
End Get
Set(ByVal value As Double)
mX = value
End Set
End Property
Public Property Y() As Double
Get
Return mY
End Get
Set(ByVal value As Double)
mY = value
End Set
End Property
Public Property ShapeNumber() As Integer
Get
Return mShapeNumber
End Get
Set(ByVal value As Integer)
mShapeNumber = value
End Set
End Property
Public Property LineNumber() As Integer
Get
Return mLineNumber
End Get
Set(ByVal value As Integer)
mLineNumber = value
End Set
End Property
Public Property Color() As Color
Get
Return mColor
End Get
Set(ByVal value As Color)
mColor = value
End Set
End Property
'### End of properties ###
'### Start of Functions ###
Public Function ContainsShapeName(ByRef ThisShapeName As String) As Boolean
If myShapes.Count = 0 Then Return False
For i As Integer = 0 To myShapes.Count - 1
If myShapes(i).ShapeName = ThisShapeName Then Return True
Next
Return False
End Function
Public Function IndexOfShapeName(ByRef ThisShapeName As String) As Integer
For i As Integer = 0 To myShapes.Count - 1
If myShapes(i).ShapeName = ThisShapeName Then Return i
Next
End Function
'### End of functions ###
End Class
#End Region
#Region "Logo Shape class"
' The logo shape class
Public Class LogoShape
Private mShapeData As New List(Of String)
Private mShapeName As String
Public Property ShapeName() As String
Get
Return mShapeName
End Get
Set(ByVal value As String)
mShapeName = value
End Set
End Property
Public ReadOnly Property ShapeData() As List(Of String)
Get
Return mShapeData
End Get
End Property
Public Sub AddShapeData(ByRef shapeText As String)
mShapeData.Add(shapeText)
End Sub
End Class
#End Region
Private Function RecombineInstructionFields(ByRef a() As String) As String
Dim c As String = ""
For Each p As String In a
c &= p & " "
Next
Return c
End Function
#Region "Enumerations"
Public Enum ExitStates As Integer
OK = True
HasError = False
End Enum
#End Region
Private COutput As Control
#Region "ENGINE CORE"
#Region "CORE: EXECUTING THE SCRIPT"
Public Function ExcuteScript(ByRef Commands As List(Of String), ByRef g As Graphics, ByRef OutputCtrl As Control, Optional ByRef SelfCalling As Boolean = False)
' Check that is something to execute.
If Commands.Count = 0 Then Return False
'Is the output control a textbox?
If (TypeOf OutputCtrl Is TextBox) = False Then
' nope
Return False
End If
' If the ExecuteScript function is not being selfcalled
If Not SelfCalling Then
' Set a local reference to the "console" control
COutput = OutputCtrl
' clear the consol
COutput.Text = ""
COutput.Text &= ScriptName & vbNewLine
' also initialise the script states.
InitialiseScriptEngineStates()
End If
Dim CommandStream As New IO.MemoryStream()
Dim buffer() As Byte
' Trim Spaces & Remove the end of line character from each command
For Each ThisString As String In Commands
buffer = System.Text.Encoding.UTF8.GetBytes(ThisString.Trim(" "))
CommandStream.Write(buffer, 0, buffer.Length)
CommandStream.WriteByte(13)
Next
' Set the CommandStream position back to the start.
CommandStream.Position = 0
' Create the script parser for the command stream.
Dim ScriptParser As New Microsoft.VisualBasic.FileIO.TextFieldParser(CommandStream)
Dim CommandFields() As String = {}
' Initialise the Execution state to OK
Dim ExecutionState As ExitStates = ExitStates.OK
' Tell the Script Parser what are the tokens for comments.
ScriptParser.CommentTokens = Tokens_Comment
' Tell the Script Parser what the tokens are for delimiting he command.
ScriptParser.Delimiters = Tokens_Delimiter
' Tell the Script Parsers that delimiters in strings are allowed.
ScriptParser.HasFieldsEnclosedInQuotes = True
' While the script parser has something to parse.
While Not ScriptParser.EndOfData
' Set the State(LineNumber) to the current line Number of the parser commands.
mState.LineNumber = ScriptParser.LineNumber
' TRY & Read the next command, parse it into fields.
Try
CommandFields = ScriptParser.ReadFields
Catch ex As Exception
ErrorMessage("Error Message: " & ex.Message & vbNewLine)
' Execution Failed, so indicate error
ExecutionState = ExitStates.HasError
' Close the Command Stream
CommandStream.Close()
CommandStream = Nothing
' Exit execution
Exit While
End Try
' Record the Current Instruction in the State(CurrentInstruction)
mState.CurrentInstruction = RecombineInstructionFields(CommandFields)
' Try and execute the CommandFields
' The processing and execution is passed to ExecuteCommand() function
' - This is to keep the code simple
' - Makes implementing new easier.
If ExcuteCommand(CommandFields, g, COutput) = False Then
' Execution Failed, so indicate error
ExecutionState = ExitStates.HasError
' Exit execution
Exit While
End If
End While
' Close the Command Stream
CommandStream.Close()
CommandStream = Nothing
' Check the state of execution
Select Case True
Case mState.MakingShape = False And ExecutionState = ExitStates.OK
If Not SelfCalling Then ErrorMessage("Executed Successfully.")
Return True
Case mState.MakingShape = False And ExecutionState = ExitStates.HasError
If Not SelfCalling Then ErrorMessage("Unsuccessful Execution")
Return True
Case mState.MakingShape = True And ExecutionState = ExitStates.OK
If Not SelfCalling Then ErrorMessage("Missing ENDSHAPE")
Return False
Case mState.MakingShape = True And ExecutionState = ExitStates.HasError
If Not SelfCalling Then ErrorMessage("Error in Execution (whilst Making Shape)")
Return True
Case Else
Return False
End Select
End Function
#End Region
#Region "CORE: EXECUTING THE COMMAND"
Private Function ExcuteCommand(ByRef command() As String, ByRef g As Graphics, ByRef OutputC As Control) As Boolean
' Define the exitState to be true (Assumes the command will execute successfully, until an error occurs.)
Dim ExitState As Boolean = True
' Shall I record this instruction. (Used for stord procedures)
Dim RecordInstruction As Boolean = True
' Which command is it?
Select Case command(0)
' -- Start of Known Instructions --
Case "PRINT" : ExitState = Command_Print(mState, command)
Case "PEN" : ExitState = Command_Pen(mState, command)
Case "MOVE" : ExitState = Command_Move(mState, command, g)
Case "ROTATE" : ExitState = Command_Rotate(mState, command)
Case "TURN" : ExitState = Command_Turn(mState, command)
Case "FORWARD" : ExitState = Command_Forward(mState, command, g)
Case "MAKESHAPE" : RecordInstruction = False : ExitState = Command_MakeShape(mState, command)
Case "ENDSHAPE" : RecordInstruction = False : ExitState = Command_EndShape(mState, command)
Case "DRAWSHAPE" : ExitState = Command_DrawShape(mState, command, g)
' -- End of known instructions --
Case Else
' Unknown instruction
ErrorMessage("Line: " & mState.LineNumber.ToString & vbCrLf & "Unknown Command." & vbCrLf & command(0))
ExitState = False
End Select
' Should I record this instruction
If mState.MakingShape And RecordInstruction Then mState.myShapes(mState.ShapeNumber).AddShapeData(mState.CurrentInstruction)
Return ExitState
End Function
#End Region
#End Region
Private Sub ErrorMessage(ByRef Message As String)
' Append the error message onto end of text.
COutput.Text &= Message & vbNewLine
End Sub
Private Sub InitialiseScriptEngineStates()
' Initialise the states with there default values
With mState
.Angle = 0
.X = 0
.Y = 0
.PenDown = False
.MakingShape = False
.LineNumber = 0
.ShapeNumber = -1
.ShapeStack.Clear()
.myShapes.Clear()
.Color = Color.Black
End With
End Sub
Continue in part2
This post has been edited by AdamSpeight2008: 11 Jul, 2008 - 05:38 AM