Beruflich Dokumente
Kultur Dokumente
com
PLSims
Introduction
PLSims (short for programmable logic simulators) is an interpreter which uses “Stack” to
provide a high-level, yet simple instruction set for a simple virtual machine. I previously wrote it
in VB for an educational purpose, and now I am re-writing it in C#. Although I am kind of too
lazy to make a proper documentation for the code, I think I should write some theories I have
used and the problems I have faced.
Before going into what is Stack Machine, I think we should look into the so-called recursive
functions. The recursive functions are, in fact, widely used in Mathematics, Science and Logic.
We might probably use recursive functions in our everyday language without noticing it much.
Actually, a recursive function follows two simple rules: it must have an initial condition(s) and
its next state(s) depends on its previous state(s). Mathematically:
F (0) = A
Those who are familiar with Calculus will probably notice that the equations are similar to initial
value problems. Yes, you are correct. In fact, recursive functions, which are also known as
recurrence relations, are used to model the linear dynamical systems, which can be modeled with
initial conditions and state equations.
One good thing about recursive functions is as long as we know the initial conditions and state
equations; we can easily calculate and predict the behavior of the system in the future. However,
in order to realize the recursive functions, there must be some kind of memory to store the past
states. Now, we can get some glimpse of recursive functions. Ok. Let’s extend the ideas a little
bit. How about languages? Well, it is also defined recursively? Am I right? The initial conditions
are letters and numbers. We define the rules of combining different letters to form meaningful
words, and meaningful sentences, paragraphs and so on.
What I have said so far is in one direction – the forward direction: from initial state to the final
state. How about the reverse direction - from final state to the initial state? Yes, it is possible, if
we know the initial conditions, the state equations and the final conditions and if we can use the
“Stack”.
1
Than Lwin Aung PLSims tlaung@gmail.com
In fact, we can consider “Stack” as the storage of our past states. Say suppose, you are a college
student, and then your stack would be like: birth, elementary school, secondary school. We just
need to know your initial condition, state transitions and final condition, and we can figure out
the rest. We can also consider stack as an open-top water tank. We fill the water from the top and
use the water from the top as well.
Probably, I think the most widely used recursive functions to give as examples are Factorial and
the Fibonacci functions.
F (0) = 1
F (n) = F (n-1) * n
F (0) = 0
F (1) = 1
As we can see, both functions must have initial conditions and state equations which define the
change from one state to another. Ok, let’s compute some factorial by using stack and the
recursive function. Say suppose we want to calculate 4!
4! = F (4) = F (3) * 4;
3! = F (3) = F (2) * 3;
2! = F (2) = F (1) * 2;
1! = F (1) = F (0) * 1;
0! = F (0) = 1;
Stack
F (1)
F (2)
F( 3)
F (4)
2
Than Lwin Aung PLSims tlaung@gmail.com
Let’s pop F (1) from the stack and compute F (2). Then compute F (3) and F (4). As from this
example, we can see that we use stack to store the intermediate results before we reach to the
final result. Let us talk about how to evaluate mathematical expressions (formulas) by using
stack.
Expressions
Generally, expressions can be divided into 3 main groups: mathematical, logical and relational
expressions. For example, 4 + 5 is a mathematical expression, and 6 > 5 is a relational
expression.
Also operators can be either unary or binary operators. Unary operators are the operators which
require only one operand. For example unary minus and mathematical functions are unary
operators. In – (343) and Sin (3.4), both “-“and “Sin” are unary operators. Binary operators are
the operators which require two operands. For example, plus +, minus - , time * and divide /
operators are the binary operators.
In addition, the order of evaluating the operations (rules of precedence) is also important. In fact,
unary operators have the highest precedence, which means that they have to be calculated first.
For binary operators, the exponent ^ has the highest precedence. Multiplication and Division has
the second highest, and Addition and Subtraction has the lowest precedence. If the two operators
have the same precedence, they will be calculated from left to right.
Statements
Actually, programming languages are made up of statements, such as if-statement, while-
statement and assignment statement. Let us start with the simplest form of statement – an
assignment statement.
X: = 4 + 6;
X: = 4 > 5;
3
Than Lwin Aung PLSims tlaung@gmail.com
Therefore, assignment statements are the fundamental statements in order for an interpreter to
evaluate and calculate the result.
X: = (3 + 6) * (3 -4);
In the early 1920s, a Polish logician, Jan Lukasiewicz, invented a special notation for
representing the expressions in a different way, known as pre-fix (Polish Notation). For
example, the above expression can be re-written in pre-fix as:
+ 4 6 -3 4 *:= x
Also, the expression can be re-written as post-fix notation (Reverse Polish Notation) as follows:
3 6 + 3 4 - * x: =
The reason why both pre-fix and post-fix notations have become famous is that for computers
(interpreters and virtual machines alike), they are easier to evaluate and calculate the expressions
although it is difficult for humans to understand.
X := Sin (3.4) * 3 + 1.5; from left to right, and parse the expression into appropriate
identifiers and symbols, we will get:
Identifiers and
Symbols
X
:=
Sin
(
3.4
)
*
3
+
1.5
;
4
Than Lwin Aung PLSims tlaung@gmail.com
To make it clear, we can actually build the binary tree called expression tree for our assignment
expression.
:=
X
+
* 1.5
3
Sin
3.4
When we build the expression tree, we first look for the lowest precedence operator. In our case,
it is the “:=”. Then we look for the second lowest precedence operator, and then we look for third
lowest precedence operator and finally the highest precedence operator.
In fact, conversion from infix notation to post-fix notation can be done as follow: First, let us
define the rules of precedence. Then, by using stack, we can transform from Infix Notation to
Postfix Notation.
If IsUnary(Optr) Then
PRECEDENCE = 9
ElseIf (Optr = Chr(DL_PW)) Then
PRECEDENCE = 8
ElseIf ((Optr = Chr(DL_MU)) Or (Optr = Chr(DL_DI))) Then
PRECEDENCE = 7
ElseIf (Optr = Chr(DL_MD)) Then
PRECEDENCE = 6
ElseIf ((Optr = Chr(DL_PL)) Or (Optr = Chr(DL_MI))) Then
PRECEDENCE = 5
ElseIf IsRelational(Optr) Then
PRECEDENCE = 4
ElseIf IsLogical(Optr) Then
PRECEDENCE = 3
ElseIf IsAssignment(Optr) Then
PRECEDENCE = 2
ElseIf IsSeperator(Optr) Then
PRECEDENCE = 1
Else
5
Than Lwin Aung PLSims tlaung@gmail.com
PRECEDENCE = 0
End If
End Function
POX_END = POX_START - 1
PTR = 1
ReDim Preserve PL_STACK(PTR)
PL_STACK(PTR) = Chr(DL_OB)
LX_INDEX = LX_INDEX + 1
ReDim Preserve LEXICONS(LX_INDEX)
LEXICONS(LX_INDEX) = Chr(DL_CB)
If IsOBracket(Opcode) Or IsFunction(Opcode) Or _
IsProcedure(Opcode) Or IsUsrDef(Opcode) Or _
IsUnary(Opcode) Then
PTR = PTR + 1
PL_STACK(PTR) = Opcode
PreOpcode = PRECEDENCE(Opcode)
PreStack = PRECEDENCE(PL_STACK(PTR))
POX_END = POX_END + 1
6
Than Lwin Aung PLSims tlaung@gmail.com
PTR = PTR - 1
ReDim Preserve PL_STACK(PTR)
PreStack = PRECEDENCE(PL_STACK(PTR))
Loop
PTR = PTR + 1
POX_END = POX_END + 1
ReDim Preserve POX(POX_END)
POX(POX_END) = PL_STACK(PTR)
PTR = PTR - 1
ReDim Preserve PL_STACK(PTR)
Loop
PTR = PTR - 1
ReDim Preserve PL_STACK(PTR)
End If
If IsFunction(PL_STACK(PTR)) Or _
IsProcedure(PL_STACK(PTR)) Or _
IsUsrDef(PL_STACK(PTR)) Then
POX_END = POX_END + 1
ReDim Preserve POX(POX_END)
POX(POX_END) = PL_STACK(PTR)
PTR = PTR - 1
ReDim Preserve PL_STACK(PTR)
End If
Else
POX_END = POX_END + 1
ReDim Preserve POX(POX_END)
POX(POX_END) = Opcode
End If
Next
End Sub
7
Than Lwin Aung PLSims tlaung@gmail.com
In PLSims, there is a Postfix module which converts the input infix expressions to the postfix
expressions.
The following diagram will show the organization of PLSims. A set of instructions (program statements)
are first loaded into Program Array. SYS_NEXT_ADDR holds the address of the next program
statement. Actually, the address inside SYS_NEXT_ADDR will change according to branching and
looping. Instruction Register always stores the instruction pointed by SYS_NEXT_ADDR. The
instruction in IR is interpreted and parsed into Lexicons and stored in Lexicons Array. If the current
instruction involves Expressions is then converted to Post-fix notation and stored in Post-fix Array for
further execution. VAR (Global Variable Array) stores the global variables. DEF (Function Address
Array) stores the address of the functions (sub routines), so during the execution, program can jump to
one function to another. OPR stores the actual parameters in the functions so that parameters can be
passed by value between procedure calls. LVR (Local Variable Stack) stores the local variables declared
in the functions (sub routines).
8
Than Lwin Aung PLSims tlaung@gmail.com
OPR
Operand Base Pointer
(Operand Array)
Instruction Register
Operand Stack Pointer
Lexicon
POX_End Pointer
LVR
Control Start Pointer
Load (Local Variable Stack)
o Load the program to Program Array
Fetch
Stack Pointer
o Fetch the instruction pointed by
SYS_NEXT_ADDR to IR
Interpret
End Pointer
o Interpret the instruction in IR, and
determine the instruction type
Execute
Local Variable Index
o Execute the instruction according the
instruction type Array
Exit
o Exit the program
9
Than Lwin Aung PLSims tlaung@gmail.com
In PLSims, the scanning and parsing are performed by the Interpretation module. The current
instruction (programming statement) is fed into Interpretation Module, and then it outputs the
type of instruction and parsed instruction to be executed.
TK_INDEX = 0
If IsWhiteSpace(chr) Then
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
End If
ST = vbNullString
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
End If
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = chr
10
Than Lwin Aung PLSims tlaung@gmail.com
ST = vbNullString
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
End If
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
ST = vbNullString
Index = POS
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
End If
ST = vbNullString
Index = POS + 1
Else
ST = ST + chr
End If
Next Index
TK_INDEX = TK_INDEX + 1
ReDim Preserve TOKENS(TK_INDEX)
TOKENS(TK_INDEX) = ST
End If
End Sub
11
Than Lwin Aung PLSims tlaung@gmail.com
The following procedure further check and parsed to form acceptable lexicons, the fundamental
keywords, identifiers, symbols and operators.
LX_INDEX = 0
Index = Index + 1
If (IsArithmetic(Prev_Lexicon) Or _
IsRelational(Prev_Lexicon) Or _
IsOBracket(Prev_Lexicon)) Then
Lexicon = Chr(DL_UM)
End If
If (IsEquStr(Next_Lexicon, KW_PROC) Or _
IsEquStr(Next_Lexicon, KW_FUNC) Or _
IsEquStr(Next_Lexicon, KW_IF) Or _
IsEquStr(Next_Lexicon, KW_PROG) Or _
IsEquStr(Next_Lexicon, KW_WHILE)) Then
Index = Index + 1
End If
12
Than Lwin Aung PLSims tlaung@gmail.com
Index = Index + 1
End If
End If
LX_INDEX = LX_INDEX + 1
ReDim Preserve LEXICONS(LX_INDEX)
LEXICONS(LX_INDEX) = Lexicon
Next Index
TK_INDEX = 0
ReDim TOKENS(1)
End Sub
Finally, the following procedure determines the type of instructions by analyzing the keywords,
identifiers and symbols.
Exit Function
If (IsEquStr(Lexicon, KW_NUMBER) Or _
IsEquStr(Lexicon, KW_BOOLEAN) Or _
IsEquStr(Lexicon, KW_STRING)) Then
13
Than Lwin Aung PLSims tlaung@gmail.com
Exit Function
Exit Function
ElseIf (IsEquStr(Lexicon, KW_PROC_MAIN)) Then
SEMANTIC_ANALYSIS = INSTR_PROC_MAIN
Exit Function
ElseIf (IsEquStr(Lexicon, KW_END_PROC)) Then
SEMANTIC_ANALYSIS = INSTR_END_PROC
Exit Function
ElseIf (IsEquStr(Lexicon, KW_END_FUNC)) Then
SEMANTIC_ANALYSIS = INSTR_END_FUNC
Exit Function
Exit Function
Else
SEMANTIC_ANALYSIS = INSTR_NOP
Exit Function
End If
If (IsEquStr(Lexicon, KW_NUMBER) Or _
IsEquStr(Lexicon, KW_BOOLEAN) Or _
IsEquStr(Lexicon, KW_STRING)) Then
Exit Function
Exit Function
ElseIf (IsEquStr(Lexicon, KW_END_PROC)) Then
SEMANTIC_ANALYSIS = INSTR_END_PROC_CALL
Exit Function
14
Than Lwin Aung PLSims tlaung@gmail.com
Exit Function
ElseIf (IsEquStr(Lexicon, KW_IF)) Then
SEMANTIC_ANALYSIS = INSTR_IF
Exit Function
ElseIf (IsEquStr(Lexicon, KW_THEN)) Then
SEMANTIC_ANALYSIS = INSTR_THEN
Exit Function
ElseIf (IsEquStr(Lexicon, KW_ELSE)) Then
SEMANTIC_ANALYSIS = INSTR_ELSE
Exit Function
ElseIf (IsEquStr(Lexicon, KW_END_IF)) Then
SEMANTIC_ANALYSIS = INSTR_END_IF
Exit Function
ElseIf (IsEquStr(Lexicon, KW_WHILE)) Then
SEMANTIC_ANALYSIS = INSTR_WHILE
Exit Function
ElseIf (IsEquStr(Lexicon, KW_END_WHILE)) Then
SEMANTIC_ANALYSIS = INSTR_END_WHILE
Exit Function
ElseIf (IsEquStr(Lexicon, KW_REPEAT)) Then
SEMANTIC_ANALYSIS = INSTR_REPEAT
Exit Function
ElseIf (IsEquStr(Lexicon, KW_UNTIL)) Then
SEMANTIC_ANALYSIS = INSTR_UNTIL
Exit Function
ElseIf (IsEquStr(Lexicon, KW_RETURN)) Then
SEMANTIC_ANALYSIS = INSTR_FUNC_RTR
Exit Function
Else
SEMANTIC_ANALYSIS = INSTR_EXP
Exit Function
End If
End If
Next Index
SEMANTIC_ANALYSIS = INSTR_NOP
End Function
15
Than Lwin Aung PLSims tlaung@gmail.com
'Logical Constants
Public Const KW_AND = "AND"
Public Const KW_OR = "OR"
Public Const KW_NOT = "NOT"
Public Const KW_XOR = "XOR"
'Boolean Constants
Public Const KW_TRUE = "TRUE"
Public Const KW_FALSE = "FALSE"
'Procedure Constants
Public Const KW_PROC = "PROCEDURE"
Public Const KW_FUNC = "FUNCTION"
Public Const KW_PROC_MAIN = "PROCEDUREMAIN"
Public Const KW_RETURN = "RETURN"
Public Const KW_END_PROC = "ENDPROCEDURE"
Public Const KW_END_FUNC = "ENDFUNCTION"
16
Than Lwin Aung PLSims tlaung@gmail.com
'Termination Constants
Public Const KW_END = "END"
'Program Constants
Public Const KW_PROG = "PROGRAM"
Public Const KW_END_PROG = "ENDPROGRAM"
Executing
The following will show the sample program instructions to be executed on PLSims:
Program Test
Procedure Main()
Number A,B
A := 10
End Procedure
End Program
The program instructions are made up of different statements. During fetch, interpret and execute
cycle, each statement is analyzed, interpreted and executed.
Once the instruction type is determined, the instruction is then fed into the Execution Module to
be executed. Actually, executing the instruction is pretty tricky and I had to come up with
different solutions.
17
Than Lwin Aung PLSims tlaung@gmail.com
Executing an instruction, in fact, has to be linked with Memory Management and Program
Address Control as there are instructions for branching, procedure calls, and variable
declarations in addition to simple statements which only involve mathematical, logical and
relational expressions.
For procedure calls, SYS_NEXT_ADDR has to change accordingly. Therefore, DEF stores the
addresses of all functions in the program. When a particular function or procedure is called from
another function, the simulator looks for the address of the called procedure in DEF and change
the SYS_NEXT_ADD to the address of the called procedure. Also, when parameters are
necessary to pass between procedures calls, the simulator looks for right parameters in the
parameter array to be passed to the called procedure or function.
For variable declaration, there is a global system variable called SYS_SCOPE, which defines the
current scope of the block of instructions. First, SYS_SCOP is initialized as -2. Then, whenever
the current execution enters a new block of instructions, such as procedure, function, while loop,
for loop, repeat-until loop, it is incremented by one. Also, when the current execution leaves the
current block of instruction, it is decremented by one.
Also, when the current instruction involves an expression, it is then converted to Postfix notation
and evaluated the result.
The following code segment is the Execution Procedure, which accepts the current instruction
type and address of the instruction, and executes the instruction and changes the address of the
instruction when necessary.
Public Sub EXECUTE(ByVal INSTR_TYPE As Integer, ByVal CUR_ADDR As Long)
Dim N As Integer
Dim Index As Integer
Case INSTR_PROG
SYS_SCOPE = -1
SYS_ARG_INDEX = 0
SYS_NX_ADDR = 0
SYS_RETURN = False
18
Than Lwin Aung PLSims tlaung@gmail.com
SYS_EXECUTABLE = True
Case INSTR_END_PROG
SYS_SCOPE = -2
Case INSTR_GLOBAL_DEF
If (SYS_EXECUTABLE) Then
V_ADD(LEXICONS(Index), Result)
Next Index
End If
Case INSTR_LOCAL_DEF
If (SYS_EXECUTABLE) Then
LV_ADD(LEXICONS(Index), Result)
Next Index
End If
Case INSTR_PROC_DEF
SYS_EXECUTABLE = False
N = 0
If (IsEquStr(LEXICONS(Index), KW_NUMBER) Or _
IsEquStr(LEXICONS(Index), KW_BOOLEAN) Or _
IsEquStr(LEXICONS(Index), KW_STRING)) Then
19
Than Lwin Aung PLSims tlaung@gmail.com
N = N + 1
End If
Next Index
Case INSTR_FUNC_DEF
SYS_EXECUTABLE = False
N = 0
If (IsEquStr(LEXICONS(Index), KW_NUMBER) Or _
IsEquStr(LEXICONS(Index), KW_BOOLEAN) Or _
IsEquStr(LEXICONS(Index), KW_STRING)) Then
N = N + 1
End If
Next Index
Case INSTR_PROC_CALL
INC_LC_SCP()
N = 1
N = N + 1
End If
Next Index
SYS_ARG_INDEX = 0
End If
Case INSTR_FUNC_CALL
INC_LC_SCP()
20
Than Lwin Aung PLSims tlaung@gmail.com
N = 1
N = N + 1
End If
Next Index
SYS_ARG_INDEX = 0
End If
Case INSTR_END_PROC_CALL
DEC_LC_SCP()
LV_END = LV_END - 1
ReDim Preserve LVR(LV_END)
NX_ADDR = CLng(Result)
SYS_NX_ADDR = NX_ADDR + 1
End If
End If
Case INSTR_END_FUNC_CALL
DEC_LC_SCP()
21
Than Lwin Aung PLSims tlaung@gmail.com
LV_END = LV_END - 1
ReDim Preserve LVR(LV_END)
NX_ADDR = CLng(Result)
SYS_NX_ADDR = NX_ADDR
SYS_RETURN = True
End If
End If
Case INSTR_END_PROC
SYS_EXECUTABLE = True
Case INSTR_END_FUNC
SYS_EXECUTABLE = True
Case INSTR_FUNC_RTR
If (SYS_RETURN) Then
EVALUATE(Result, NX_ADDR, POX_PTR)
SYS_RETURN = False
Else
POST_FIX(2)
EVALUATE(Result, NX_ADDR)
End If
OPR_PTR = OPR_PTR + 1
ReDim Preserve OPR(OPR_PTR)
OPR(OPR_PTR) = Result
Else
'Error
End If
End If
Case INSTR_PROC_MAIN
SYS_SCOPE = 0
LV_PTR = 1
LV_END = 1
22
Than Lwin Aung PLSims tlaung@gmail.com
LV_START = 1
POX_START = 1
OPR_BASE = 1
Case INSTR_IF
If (SYS_EXECUTABLE) Then
If (SYS_RETURN) Then
EVALUATE(Result, NX_ADDR, POX_PTR)
SYS_RETURN = False
Else
POST_FIX(2)
EVALUATE(Result, NX_ADDR)
End If
INC_BL_SCP()
LV_ADD(SYS_NULL + KW_IF + SYS_NULL + CStr(SYS_SCOPE), Result)
End If
Else
SYS_SCOPE = SYS_SCOPE + 1
End If
Case INSTR_THEN
Case INSTR_ELSE
Case INSTR_END_IF
23
Than Lwin Aung PLSims tlaung@gmail.com
DEC_BL_SCP()
Else
SYS_SCOPE = SYS_SCOPE - 1
End If
Case INSTR_WHILE
If (SYS_EXECUTABLE) Then
If (SYS_RETURN) Then
EVALUATE(Result, NX_ADDR, POX_PTR)
SYS_RETURN = False
Else
POST_FIX(2)
EVALUATE(Result, NX_ADDR)
End If
INC_BL_SCP()
If (CBool(Result)) Then
Case INSTR_END_WHILE
SYS_EXECUTABLE = True
SYS_NX_ADDR = CLng(Result)
DEC_BL_SCP()
Else
SYS_SCOPE = SYS_SCOPE - 1
End If
Case INSTR_REPEAT
If (SYS_EXECUTABLE) Then
INC_BL_SCP()
24
Than Lwin Aung PLSims tlaung@gmail.com
Case INSTR_UNTIL
If (SYS_RETURN) Then
EVALUATE(EXP, NX_ADDR, POX_PTR)
SYS_RETURN = False
Else
POST_FIX(2)
EVALUATE(EXP, NX_ADDR)
End If
SYS_EXECUTABLE = True
DEC_BL_SCP()
Else
SYS_SCOPE = SYS_SCOPE - 1
End If
Case INSTR_EXP
If (SYS_EXECUTABLE) Then
If (SYS_RETURN) Then
EVALUATE(Result, NX_ADDR, POX_PTR)
SYS_RETURN = False
Else
POST_FIX(1)
EVALUATE(Result, NX_ADDR)
End If
End If
End If
Case Else
25
Than Lwin Aung PLSims tlaung@gmail.com
SYS_NX_ADDR = 0
End Select
End Sub
PAR = 1
SYS_SCOPE = -2
IR = PRO(PAR)
INST = INTERPRET(IR)
EXECUTE(INST, PAR)
If (SYS_NX_ADDR = 0) Then
PAR = PAR + 1
Else
PAR = SYS_NX_ADDR
SYS_NX_ADDR = 0
End If
End While
End Sub
Secondly, PLSims does not have a debugger and it cannot be used for debugging purpose. And
finally, PLSims only provides 3 intrinsic data types: Number, Boolean and String. So, it will be a
good idea to add ADT (Abstract Data Types) and other data structures, such as Array, Record
etc.
26