Implementing Stack Oriented Languages – Part 3

Implementing Stack Oriented Languages – Part 3
This entry is part 3 of 4 in the series Implementing Stack Oriented Languages

Post Stastics

  • This post has 5444 words.
  • Estimated read time is 25.92 minute(s).

Parsing Keywords

Most programming languages include special words used by the language to perform operations on data. These may include I/O operations, data management, etc. Our language is no different. We have included many keywords to perform operations on the stack, perform I/O, and provide flow control. In this installment, we will add keyword handling to our language.

Before we move on to adding more features to our language, I think it is high time we combine our disparate parts into a single entity. This will turn our pieces into a real program! As a real program, we need to consider how the end-user will interact with our program. For example, right now we have test programs hard-coded into the parser. Our users will not want to hard code their programs into the parser. However, they need a method to pass their source code to our language for interpretation. To allow this interaction we will create a file named sol.py. SOL is our stack-oriented-language. This file will pull all the pieces together and allow the end-user to pass in their source file on the command line.

#! usr/bin/env/python3

from lexer import Lexer
from parser import Parser
import argparse


def main(src):
    _lexer = Lexer(src)
    _parser = Parser(_lexer)
    _parser.parse()



if __name__ == '__main__':
    argparse = argparse.ArgumentParser()
    argparse.add_argument("--infile",
                        help="Input source file", required=True)
    argparse.add_argument("-v", "--verbose", help="Echo source file", action="store_true")
    args = argparse.parse_args()

    infile = args.infile
    sorce = ''
    with open(infile, 'r') as ifh:
        source = ifh.read()
        if args.verbose:
            print(source)

    main(source)

Looking at the new file sol.py, we can see that we have the typical python if dunder name, dunder ‘main’ statement used to execute the main routine. First however, we use the argparser to handle command-line parameters. The program supports two possible parameters, “-i”/”–infile’ and “-v” / “–verbose”. The “–infile” parameter is used to pass the input source file to our lexer/parser. The “–verbose” parameter is used to display the input file to the user.

We can easily call the approgram like so:

python3 sol.py --infile test_arith.sol --verbose

Or

python3 sol.py -i test_arith.sol -v

Next, create a directory named “prog” as a child directory of your code. We will place our test programs in this folder.

Now create a file in the new “prog” folder named “test_arith.sol”. This file will hold a small program to test the arithmetic operations.

\ test_arith.sol
\
\ +, -, *, /, **
\
\ Arithmetic operators take the top two items
\ from the stack and performs the operations
\ on them, placing the results back on the
\ stack.

2 3 +
dump
5 *
dump
5 /
dump
3 -
dump
8 **
dump

In this program we place the values 2 and 3 on the stack. Then we use the “+” operator to pull the two values off the stack and add them together. The operator places the result back on the stack. We then add 5 to the stack and use the the “*” operator to multiply the two values on the stack together and replace them with the result of the multiplication. We then divide this value by 5 and continue on to subtract 3 and raise the result (2), to the power of 8.

Before we run this program, we need to add the “power” operator and “dump” command to the parser. First, at the bottom of the Arithmetic operator handlers in the parser’s parse method, we will add the code to handle the raising a number to a power.

# Parser.py
...

    def parse(self):
        # get token and decide what to do with it
        tok = self.current_tok
        ...
         # Arithmetic
         elif tok.type == TT_PLUS:
           right = self.data.pop()
           ...

         elif tok.type == TT_POW:
           right = self.data.pop()
           left = self.data.pop()
           result = left ** right
           self.data.push(result)

        # Logical Operations
        ...

That’s all we need to handle the power operation. However, we still need to add the “dump” command. Dump is a keyword in our language and the keywords are handled a bit differently than we handle the operators. With the operators, we match against the token type. However, all our keywords will have a token type of TT_KEYWORD. To tell which keyword we are dealing with we have to first ensure we are dealing with a keyword token and then match against the token value. All our keywords are handled this way.

# Parser.py
...
   def parse(self):
      ...
      # Handle KEYWORDS
      elif tok.type == TT_KEYWORD:
           # Process keyword
                if tok.value == 'dump':
                    """dump
                    Displays the data stack contents in the console.
                    """
                    print(str(self.data))
                    

As you can see this code simply nests the handling of all keywords under the outer “elif” statement in the parser’s parse method.

If you run this code, passing it our test_arith.sol file as input, you should receive an out put similar to:

\ +, -, *, /, **
\
\ Arithmetic operators take the top two items
\ from the stack and performs the operations
\ on them, placing the results back on the
\ stack.

2 3 +
dump
5 *
dump
5 /
dump
3 -
dump
8 **
dump

[ '5' ]
[ '25' ]
[ '5' ]
[ '2' ]
[ '256' ]

Dump is a great tool to use to see what is on the stack at any point in your program. However, it is meant for the developer’s use and not as an output method for the program. We will work on that later. For now, let’s work on the methods we need to manage and manipulate the stack.

Let’s start with the DROP keyword. Drop simply pops the first value off the stack and discards it. The value will be lost forever. Now let’s see how to implement the Drop operation.

            # Handle Keywords
            elif tok.type == TT_KEYWORD:
                # Process keyword
                if tok.value == 'dump':
                    """dump
                    Display the data stack contents in the console
                    """
                    print(str(self.data))
                    
                elif tok.value == 'drop':
                    """drop
                    Remove the top value from the stack and discard it.
                    """
                    self.data.pop()

I think that’s got to be the simplest operation we have performed to date! Now let’s move on. The next operation we might like to complete on the stack would be to empty the stack. This is done using the CLEAR keyword. If you recall, the stack implementation we created includes a clear method for doing just this. So the code is as simple as it was for the pop keyword:

            # Handle Keywords
            elif tok.type == TT_KEYWORD:
                # Process keyword
                if tok.value == 'dump':
                    """dump
                    Display the data stack contents in the console
                    """
                    print(str(self.data))

                elif tok.value == 'drop':
                    """pop
                    Remove the top value from the stack and discard it.
                    """
                    self.data.pop()

                elif tok.value == 'clear':
                    """clear
                    Empty the stack.
                    """
                    self.data.clear()

One operation we may want is the SWAP operation. The Swap operation takes the top two values on the stack and swaps their order. This can be very helpful when values are in the wrong order for the next operation.

            # Handle Keywords
            elif tok.type == TT_KEYWORD:
                # Process keyword
                if tok.value == 'dump':
                    """dump
                    Display the data stack contents in the console
                    """
                    print(str(self.data))

                elif tok.value == 'drop':
                    """pop
                    Remove the top value from the stack and discard it.
                    """
                    self.data.pop()

                elif tok.value == 'clear':
                    """clear
                    Empty the stack.
                    """
                    self.data.clear()

                elif tok.value.lower() == 'swap':
                    """swap
                    Swaps the position of the top two items on the stack.
                    Ex: x y z --> x z y
                    """
                    if self.data.count() < 2:
                        raise IndexError(
                            f'Attempt to SWAP on stack of size {self.data.count()}!\n\t\t\tStack must contain at least 2 elements to SWAP.')
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    self.data.push(n1)
                    self.data.push(n2)

All we do to swap the values on the stack is to pop the value off and then push them back on in the opposite order.

Sometimes we need to swap the top two pairs of values on the stack. We can use Swap2 for this. The code is as one would expect, we simply pop off the top four values and push them back onto the stack in the new order.

                ...
                 elif tok.value == 'swap2':
                    """swap2
                    Swaps the position of the top two pairs of items on the stack.
                    Ex: w x y z --> y z w x
                    """
                    if self.data.count() < 4:
                        raise IndexError(
                            f'Attempt to SWAP on stack of size {self.data.count()}!\n\t\t\tStack must contain at least 4 elements to SWAP2.')
                    n3 = self.data.pop()
                    n2 = self.data.pop()
                    n1 = self.data.pop()
                    n0 = self.data.pop()
                    self.data.push(n2)
                    self.data.push(n3)
                    self.data.push(n0)
                    self.data.push(n1)

All this swapping is good but sometimes we simply need to copy an item to the top of the stack. For this we use the OVER keyword. Again, we simply pop the value off the stack and push them back on in the order we desire:

                elif tok.value == 'over':
                    """over

                    Copies the second item on the stack to the head position.
                    Ex: x y z --> x y z y
                    """
                    top = self.data.pop()
                    next = self.data.pop()
                    self.data.push(next)
                    self.data.push(top)
                    self.data.push(next)

Sometimes we will need to duplicate the value on the top of the stack. This is helpful because many of the operations we use will consume the value at the top of the stack and replace it with the result of the operation. So if we need to keep the original value for further processing, we need to create a copy. As expected we simply pop the value on the top of the stack and then push it back on the stack two times. Here’s the code:

                ...
                elif tok.value.lower() == 'dup':
                    """dup
                    Duplicates the item on the top of the stack.
                    Ex: x y z --> x y z z
                    """
                    n1 = self.data.pop()
                    self.data.push(n1)
                    self.data.push(n1)

Duplicating items is so common that it is often helpful to have difference versions of dup to duplicate different numbers of items on the stack. We will provide three additional versions Dup2, Dup3, and Dup4:

                elif tok.value == 'dup2':
                    """dup2
                    Duplicates the top two items on the stack.
                    Ex: x y z --> x y z y z
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n2)
                    self.data.push(n1)

                elif tok.value == 'dup3':
                    """dup3
                    Duplicates the top three items on the stack.
                    Ex: x y z --> x y z x y z
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    n3 = self.data.pop()
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)

                elif tok.value == 'dup4':
                    """dup3
                    Duplicates the top three items on the stack.
                    Ex: w x y z --> w x y z w x y z 
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    n3 = self.data.pop()
                    n4 = self.data.pop()
                    self.data.push(n4)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n4)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)

Another operation that can be helpful is the ability to rotate the stack. The ROL keyword rotates the values in the stack by moving the top element to the bottom and shifting every other element up one position. The ROR keyword takes the bottom element and moves it to the top of the stack shifting every other element down one position. Again, these operations are performed using simple calls to the stack methods.

                ... 
                elif tok.value == 'rol':
                    """ror
                    Rotates the values in the stack by moving the top
                    element to the bottom and shifting everything else up 
                    one position. 
                    Ex: rot x y z -> y z x
                    """
                    item = self.data.pop()
                    last = self.data.que(item)
                    
                elif tok.value == 'ror':
                    """rot
                    Rotates the values in the stack by moving the bottom
                    element to the top and pushing everything else down 
                    one position. 
                    Ex: rot x y z -> y z x
                    """
                    last = self.data.tail()
                    self.data.push(last)

Ok, that’s about it for the stack manipulation operations. It’s hard to believe you can accomplish much with these primitive operations on such a simple data structure. But you’ll be surprised what can be achieved. Next, let’s tackle input/output methods.

I/O Operations

A program isn’t much use if you can’t get data into it or result out of it. This is the job of I/O routines. Our language will use a couple very simple I/O routines to display information in the console and read keyboard input.

The first routine we will implement is the EMIT keyword. Emit takes the value at the top of the stack and outputs it to the console as if it is a single ASCII character, using the lower order byte only. It is the programmer’s responsibility to ensure the value is printable as an ASCII character.

                ...
                elif tok.value == 'emit':
                    """emit
                    The word EMIT takes a single ASCII representation on the 
                    stack, using the low-order byte only, and sends the 
                    character to stdout. For example, in decimal:

                        65 EMIT↵ A ok 
                        66 EMIT↵ B ok 
                        67 EMIT↵ C ok 
                    """
                    ch = self.data.pop()
                    if ch > 0x110000 or ch < 0:
                        raise ValueError(f'[Error] character popped by emit: {ch} is out of printable range!')
                    char = chr(ch)
                    print(char, end='')

Next, we need a way to get data into the program. We will use the KEY keyword to enter data. The KEY command simply pauses program execution and waits for a key to be pressed. Once a key is pressed, the key value is pushed onto the stack for use by the program. This is a very primitive input method. We will work on more complex input methods later in this series. For this to work you will need to import the sys module. I place my imports grouped together near the top of the file to keep things organized.

  ...
import sys  

from lexer import Lexer
  ...

class Parser:
   ...

   def parse(self):
      ...
      # Handle Keywords
      elif tok.type == TT_KEYWORD:
         # Process keyword
         if tok.value == 'dump':  
             ...
       
             elif tok.value == 'key':
                    """key
                    Pauses execution until a key is pressed. Once a key
                    is pressed, the key code is pushed onto the stack.
                    Ex: <A> --> 65
                    """
                    getch = sys.stdin.read
                    key = None
                    while not key:
                        key = getch(1)
                    self.data.push(ord(key))

The final keywords we are going to implement this week are constant values used for printing. These values include CR for ASCII 13 which is the carriage return, LF: ASCII 10, line feed. SP ASCII 32 which is the space character. NULL, ASCII 0 which is the ASCII null character. BELL, ASCII 7 which is the bell character. On teletypes and older computers this character used to cause the system to emit a sound. On newer systems that no-longer occurs. BS, ASCII 8 is the backspace character. Further constants are the TAB, VT (Vertical tab), FF (Form feed), and ESC (Escape) characters. Each of these simple push their numeric value onto the stack.

                ...
                # Helpful Constants
                elif tok.value == 'cr':  # Carriage Return
                    self.data.push(13)

                elif tok.value == 'lf':  # Line Feed
                    self.data.push(10)

                elif tok.value == 'sp':
                    self.data.push(32)

                elif tok.value == 'null':  # NUll
                    self.data.push(0)

                elif tok.value == 'bell':  # System Bell
                    self.data.push(7)

                elif tok.value == 'bs':  # Backspace
                    self.data.push(8)

                elif tok.value == 'tab':  # TAB
                    self.data.push(9)

                elif tok.value == 'vt':  # Vertical TAB
                    self.data.push(11)

                elif tok.value == 'ff':  # Form Feed
                    self.data.push(12)

                elif tok.value == 'esc':  # Escape
                    self.data.push(27)

Ok, try writing some sample programs using the keywords and operators we have implemented. Run these programs using the new command-line interface and be sure to dump the stack contents so you can see the results of each operation. Verify that each operation does what you expect.

Ok, that’s it’s for this installment. Next time we will continue to implement features of the language. As always, I have included a complete listing below.

# keywords.py

KEYWORDS = [
    # Stack Operators
    'SWAP', 'SWAP2', 'DUMP', 'DROP', 'ROL', 'ROR', 'OVER',
    'DUP', 'DUP2', 'DUP3', 'DUP4', 'CLEAR', 'DUMP',


    # Conditional Operators
    'IF', 'ELSE', 'THEN', 'NOT',

    # Iterative
    'DO', 'LOOP',
    'WHILE', 'WEND',

    # I/O Operators
    'TYPE',  # Outputs a string to stdout
    'EMIT',  # Outputs a character to stdout
    'KEY',   # Takes input character from stdin

    # Special Character Values
    # See ASCII Character Table for values
    'CR', 'LF', 'NULL', 'BELL', 'BS', 'TAB',
    'VT', 'FF', 'ESC',

    # Logical Operators
    'NEQ', 'AND', 'OR',

]
# lexer.py

from keywords import KEYWORDS
from tokens import *

class Lexer:
    def __init__(self, text, line=None, col=None):
        self.text = text
        self.pos = 0
        self.line = line if line else 1
        self.col = col if col else 1
        self.current_char = self.text[0]


    #############################################
    # Utility Methods                           #
    #-------------------------------------------#
    # These methods help manage the internal    #
    # workings of the lexer.                    #
    #############################################
    def advance(self):
        """advance():
        Advances the current position in the character stream.
        """
        if not self.is_eoi():
            self.pos += 1
            self.current_char = self.text[self.pos]
            self.update_position()
        else:
            self.current_char = ''

    def update_position(self):
        """update_position
        Maintains the line and column positions for the current token scan.
        """
        if self.current_char == '\n':
            self.line += 1
            self.col = 0
        else:
            self.col += 1

    def is_eoi(self):
        """is_eoi():
        :return:
            True if current position is End Of Input stream.
            Returns False otherwise.
        """
        return self.pos >= (len(self.text) - 1)

    def expected(self, expected, found):
        """expected
        Raises error is expected and found are different.
        """
        if expected == found:
            return
        else:
            raise ValueError(f'[Error] Expected {expected} but found {found} in line: {self.line} position: {self.col}')

    def peek(self):
        """peek
        Returns next character in input stream after the current character.
        Note: If we are at EOI (End Of Input) then a space character is returned.
        """
        if not self.is_eoi():
            return self.text[self.pos + 1]
        else:
            return ' '  # space

    #############################################
    # Recognizer Methods                        #
    #-------------------------------------------#
    # These methods collect the characters that #
    # belong to a single multi-character token  #
    # into a single unit, and return that unit  #
    # to the caller.                            #
    #############################################
    def char(self):
        if self.current_char != "'":
            self.expected("'", self.current_char)
        self.advance()  # Consume opening single quote

        char = self.current_char
        self.advance()  # Consume character

        # Now we must have a closing single quote mark
        if self.current_char != "'":
           self.expected("'", self.current_char)
        self.advance()  # Consume closing single quote
        return char

    def string(self):
        if self.current_char != '"':
            self.expected('"', self.peek())
        self.advance()  # Consume leading '"'

        string = ''
        while self.current_char != '"':
            string += self.current_char
            self.advance()
        self.advance()  # Consume trailing '"'
        return string

    def number(self):
        number = ''
        while self.current_char.isdigit():
            number += self.current_char
            self.advance()
        return number

    def is_ident(self):
        return self.current_char.isalpha() or self.current_char == '_'

    def _ident(self):
        _id = ''
        # identifiers begin with alpha or underscores
        if self.current_char.isalpha() or self.current_char == '_':
            while self.current_char.isalnum() or self.current_char == '_':
                _id += self.current_char
                self.advance()
        return _id

    def is_keyword(self, kw):
        return kw.upper() in KEYWORDS

    def eat_comment(self):
        if self.current_char == '(':
            while self.current_char != ')' and not self.is_eoi():
                self.advance()
            self.advance()  # skip trailing )
        elif self.current_char == '\\':
            while self.current_char != '\n' and not self.is_eoi():
                self.advance()
            self.advance()  # skip new line character

    #############################################
    # Tokenization                              #
    #-------------------------------------------#
    # The method next_token() is called by the  #
    # client to obtain the next token in the    #
    # input stream.                             #
    #############################################
    def next_token(self) -> Token:
        if not self.is_eoi():
            if self.current_char.isspace():
                while self.current_char.isspace():
                    self.advance()

            if self.current_char.isdigit():
                return Token(TT_NUMBER, self.number(), self.line, self.col)

            elif self.current_char == '+':
                val = self.current_char
                self.advance()
                return Token(TT_PLUS, val, self.line, self.col)

            elif self.current_char == '-':
                val = self.current_char
                self.advance()
                return Token(TT_MINUS, val, self.line, self.col)

            elif self.current_char == '*':
                val = self.current_char
                if self.peek() == '*':
                    val += '*'
                    self.advance()
                    self.advance()
                    return Token(TT_POW, val, self.line, self.col)
                self.advance()
                return Token(TT_MUL, val, self.line, self.col)

            elif self.current_char == '/':
                val = self.current_char
                self.advance()
                return Token(TT_DIV, val, self.line, self.col)

            elif self.current_char == '&':
                val = self.current_char
                self.advance()
                return Token(TT_AND, val, self.line, self.col)

            elif self.current_char == '|':
                val = self.current_char
                self.advance()
                return Token(TT_OR, val, self.line, self.col)

            elif self.current_char == '^':
                val = self.current_char
                self.advance()
                return Token(TT_XOR, val, self.line, self.col)

            elif self.current_char == '~':
                val = self.current_char
                self.advance()
                return Token(TT_INV, val, self.line, self.col)

            elif self.is_ident():
                _id = self._ident()
                if self.is_keyword(_id):
                    return Token(TT_KEYWORD, _id, self.line, self.col)
                else:
                    return Token(TT_ID, _id, self.line, self.col)

            elif self.current_char == '<':
                val = self.current_char
                if self.peek() == '<':
                    val += '<'
                    self.advance()
                    self.advance()
                    return Token(TT_LSL, val, self.line, self.col)
                self.advance()
                return Token(TT_LESS, val, self.line, self.col)

            elif self.current_char == '>':
                val = self.current_char
                if self.peek() == '>':
                    val += '>'
                    self.advance()
                    self.advance()
                    return Token(TT_LSR, val, self.line, self.col)
                self.advance()
                return Token(TT_GREATER, val, self.line, self.col)

            elif self.current_char == '=':
                val = self.current_char
                if self.peek() == '=':
                    val += '='
                    self.advance()
                    self.advance()
                    return Token(TT_EQ, val, self.line, self.col)
                self.advance()
                return Token(TT_ASSIGN, val, self.line, self.col)




            elif self.current_char == ':':
                val = self.current_char
                self.advance()
                return Token(TT_STARTDEF, val, self.line, self.col)

            elif self.current_char == ';':
                val = self.current_char
                self.advance()
                return Token(TT_ENDDEF, val, self.line, self.col)

            elif self.current_char == '(' or self.current_char == '\\':
                self.eat_comment()
                return self.next_token()


            elif self.current_char == "'":
                return Token(TT_CHAR, self.char(), self.line, self.col)

            elif self.current_char == '"':
                string = self.string()
                return Token(TT_STRING, string, self.line, self.col)

            elif self.is_eoi():
                return Token(TT_EOF, None, self.line, self.col)

            else:
                raise ValueError(f'Unexpected character: {self.current_char} in input stream at position: {str(self.pos)}.')

        else:
            return Token(TT_EOF, None, self.line, self.col)


def main(text: str):
    lexer = Lexer(text)
    tok = lexer.next_token()
    while tok.type != TT_EOF:
        print(tok)
        tok = lexer.next_token()

if __name__ == "__main__":
    text = """ 
    ( this is a multi-line 
       comment. ) 
    \ This ia a line comment." 
    _myIdent """

    main(text)
# parser.py
import sys

from lexer import Lexer
from stack import Stack
from tokens import *

#######################################
# Parser                              #
#######################################

class Parser:

    def __init__(self, lexer: Lexer, data_stack: Stack = None):
        self.lexer = lexer

        # Setup data stack
        if data_stack:
            self.data = data_stack
        else:
            self.data = Stack()

        self.current_tok = Token(TT_START, None)

    def next_token(self):
        if self.current_tok != TT_EOF:
            self.current_tok = self.lexer.next_token()

    def parse(self):
        # get token and decide what to do with it
        tok = self.current_tok

        while tok.type != TT_EOF:
            self.next_token()
            tok = self.current_tok
            #print(tok)
            #print(self.data)

            if tok.type == TT_START:
                continue

            if tok.type == TT_NUMBER:
                self.data.push(int(tok.value))
                continue

            # Arithmetic
            elif tok.type == TT_PLUS:
                right = self.data.pop()
                left = self.data.pop()
                result = left + right
                self.data.push(result)

            elif tok.type == TT_MINUS:
                right = self.data.pop()
                left = self.data.pop()
                result = left - right
                self.data.push(result)

            elif tok.type == TT_MUL:
                right = self.data.pop()
                left = self.data.pop()
                result = left * right
                self.data.push(result)

            elif tok.type == TT_DIV:
                right = self.data.pop()
                left = self.data.pop()
                result = left // right
                self.data.push(result)

            elif tok.type == TT_POW:
                right = self.data.pop()
                left = self.data.pop()
                result = left ** right
                self.data.push(result)

            # Logical Operations
            elif tok.type == TT_INV:
                left = self.data.pop()
                left = ~left
                self.data.push(left)

            elif tok.type == TT_XOR:
                right = self.data.pop()
                left = self.data.pop()
                self.data.push((left ^ right))

            elif tok.type == TT_AND:
                right = self.data.pop()
                left = self.data.pop()
                self.data.push((left & right))

            elif tok.type == TT_OR:
                right = self.data.pop()
                left = self.data.pop()
                self.data.push((left | right))

            elif tok.type == TT_LSL:
                right = self.data.pop()
                left = self.data.pop()
                self.data.push(int(left) << int(right))

            elif tok.type == TT_LSR:
                right = self.data.pop()
                left = self.data.pop()
                self.data.push((left >> right))

            # Handle Keywords
            elif tok.type == TT_KEYWORD:
                # Process keyword
                if tok.value == 'dump':
                    """dump
                    Display the data stack contents in the console
                    """
                    print(str(self.data))

                elif tok.value == 'drop':
                    """pop
                    Remove the top value from the stack and discard it.
                    """
                    self.data.pop()

                elif tok.value == 'clear':
                    """clear
                    Empty the stack.
                    """
                    self.data.clear()

                elif tok.value.lower() == 'swap':
                    """swap
                    Swaps the position of the top two items on the stack.
                    Ex: x y z --> x z y
                    """
                    if self.data.count() < 2:
                        raise IndexError(
                            f'Attempt to SWAP on stack of size {self.data.count()}!\n\t\t\tStack must contain at least 2 elements to SWAP.')
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    self.data.push(n1)
                    self.data.push(n2)

                elif tok.value == 'swap2':
                    """swap2
                    Swaps the position of the top two pairs of items on the stack.
                    Ex: w x y z --> y z w x
                    """
                    if self.data.count() < 4:
                        raise IndexError(
                            f'Attempt to SWAP on stack of size {self.data.count()}!\n\t\t\tStack must contain at least 4 elements to SWAP2.')
                    n3 = self.data.pop()
                    n2 = self.data.pop()
                    n1 = self.data.pop()
                    n0 = self.data.pop()
                    self.data.push(n2)
                    self.data.push(n3)
                    self.data.push(n0)
                    self.data.push(n1)

                elif tok.value == 'over':
                    """over
                    Copies the second item on the stack to the head position.
                    Ex: x y z --> x y z y
                    """
                    top = self.data.pop()
                    next = self.data.pop()
                    self.data.push(next)
                    self.data.push(top)
                    self.data.push(next)

                elif tok.value == 'over2':
                    """over
                    Copies the second item on the stack to the head position.
                    Ex: x y z --> x y z y
                    """
                    top = self.data.pop()
                    sec = self.data.pop()
                    next = self.data.pop()
                    self.data.push(next)
                    self.data.push(top)
                    self.data.push(next)

                elif tok.value == 'dup2':
                    """dup2
                    Duplicates the top two items on the stack.
                    Ex: x y z --> x y z y z
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n2)
                    self.data.push(n1)

                elif tok.value == 'dup3':
                    """dup3
                    Duplicates the top three items on the stack.
                    Ex: x y z --> x y z x y z
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    n3 = self.data.pop()
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)

                elif tok.value == 'dup4':
                    """dup3
                    Duplicates the top three items on the stack.
                    Ex: w x y z --> w x y z w x y z 
                    """
                    n1 = self.data.pop()
                    n2 = self.data.pop()
                    n3 = self.data.pop()
                    n4 = self.data.pop()
                    self.data.push(n4)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)
                    self.data.push(n4)
                    self.data.push(n3)
                    self.data.push(n2)
                    self.data.push(n1)

                elif tok.value == 'rol':
                    """ror
                    Rotates the values in the stack by moving the top
                    element to the bottom and shifting everything else up 
                    one position. 
                    Ex: rot x y z -> y z x
                    """
                    item = self.data.pop()
                    last = self.data.que(item)

                elif tok.value == 'ror':
                    """rot
                    Rotates the values in the stack by moving the bottom
                    element to the top and pushing everything else down 
                    one position. 
                    Ex: rot x y z -> y z x
                    """
                    last = self.data.tail()
                    self.data.push(last)

                # Handle I/O Routines
                elif tok.value == 'emit':
                    """emit
                    The word EMIT takes a single ASCII representation on the 
                    stack, using the low-order byte only, and sends the 
                    character to stdout. For example, in decimal:

                        65 EMIT↵ A ok 
                        66 EMIT↵ B ok 
                        67 EMIT↵ C ok 
                    """
                    ch = self.data.pop()
                    if ch > 0x110000 or ch < 0:
                        raise ValueError(f'[Error] character popped by emit: {ch} is out of printable range!')
                    char = chr(ch)
                    print(char, end='')

                elif tok.value == 'key':
                    """key
                    Pauses execution until a key is pressed. Once a key 
                    is pressed, the key code is pushed onto the stack.
                    Ex: <A> --> 65
                    """
                    getch = sys.stdin.read
                    key = None
                    while not key:
                        key = getch(1)
                    self.data.push(ord(key))

                # Helpful Constants
                elif tok.value == 'cr':  # Carriage Return
                    self.data.push(13)

                elif tok.value == 'lf':  # Line Feed
                    self.data.push(10)

                elif tok.value == 'sp':
                    self.data.push(32)

                elif tok.value == 'null':  # NUll
                    self.data.push(0)

                elif tok.value == 'bell':  # System Bell
                    self.data.push(7)

                elif tok.value == 'bs':  # Backspace
                    self.data.push(8)

                elif tok.value == 'tab':  # TAB
                    self.data.push(9)

                elif tok.value == 'vt':  # Vertical TAB
                    self.data.push(11)

                elif tok.value == 'ff':  # Form Feed
                    self.data.push(12)

                elif tok.value == 'esc':  # Escape
                    self.data.push(27)


if __name__ == '__main__':
    source = """ 16 2 >> """
    lexer = Lexer(source)
    parser = Parser(lexer)
    parser.parse()
    print(parser.data)
#! usr/bin/env/python3

# sol.py

from lexer import Lexer
from parser import Parser
import argparse


def main(src):
    _lexer = Lexer(src)
    _parser = Parser(_lexer)
    _parser.parse()



if __name__ == '__main__':
    argparse = argparse.ArgumentParser()
    argparse.add_argument("-i", "--infile",
                        help="Input source file", required=True)
    argparse.add_argument("-v", "--verbose", help="Echo source file", action="store_true")
    args = argparse.parse_args()

    infile = args.infile
    sorce = ''
    with open(infile, 'r') as ifh:
        source = ifh.read()
        if args.verbose:
            print(source)

    main(source)
# stack.py

class Stack:
    def __init__(self, report_errors=False):
        self.store = []
        self.report_error = report_errors

    def push(self, item):
        self.store.append(item)

    def pop(self):
        if self.store:
            return self.store.pop()
        else:
            if self.report_error:
                raise IndexError('Attempt to pop a value from empty stack!')
            else:
                return None

    def tail(self):
        if self.store:
            return self.store.pop(0)
        else:
            return None

    def clear(self):
        self.store.clear()

    def count(self):
        return len(self.store)

    def is_empty(self):
        return not bool(self.store)

    def __str__(self):
        s = '[ '
        for v in self.store:
            s += "'" + str(v) + "', "
        s = s[:-2]
        s += ' ]'
        return s

    def __repr__(self):
        return self.__str__()
# tokens.py


# Data
TT_CHAR     = 'CHAR'    # 'c'
TT_STRING   = 'STRING'  # "string"
TT_NUMBER   = 'NUMBER'  # 1234 (integers only
TT_ID       = 'ID'

# Arithmatic Operators
TT_ASSIGN   = 'ASSIGN'  #   '='
TT_PLUS     = 'PLUS'    #   '+'
TT_MINUS    = 'MINUS'   #   '-'
TT_MUL      = 'MUL'     #   '*'
TT_DIV      = 'DIV'     #   '/'
TT_POW      = 'POW'     #   '**"

# Bitwise Operators
TT_LSR      = 'LSR'     #   '>>'
TT_LSL      = 'LSL'     #   '<<'
TT_AND     = 'AND'    #   '&' Bitwise AND
TT_OR      = 'OR'     #   '|' Bitwise OR
TT_XOR     = 'XOR'     #   '^' Bitwise XOR
TT_INV     = 'INV'     #   '~' Bitwise NOT/Invert

# Relational Operators
TT_EQ       = 'EQ'      #   '==' Is Equal?
TT_LESS     = 'LESS'    #   '<'  Is Less?
TT_GREATER  = 'GREATER' #   '>' Is greater?

# Logical Operators
TT_LAND  = 'LAND'   #   AND Logical
TT_LOR   = 'LOR'    #   OR Logical
TT_NEQ   = 'NEQ'    #   NEQ Logical

# DEFINE Functions
TT_STARTDEF = 'STARTDEFINE' #   ':'
TT_ENDDEF   = 'ENDDEFINE'   #   ';'

# INTERNAL USE
TT_KEYWORD  = 'KEYWORD'
TT_START    = "START"
TT_EOF      = 'EOF'


class Token:
    def __init__(self, _type, _value, line=None, col=None):
        self.type = _type
        self.value = _value
        self.col = col
        self.line = line

    def __str__(self):
        return f'''type: {self.type}, value: {self.value}, 
                at line {self.line} in column {self.col}'''

    def __repr__(self):
        return self.__str__()
\ file: test.sol
\
\ Expected Output
\ -----------------------
\ [ '2', '3' ]
\ [ '5' ]
\ [ '5', '5' ]
\ [ '10' ]
\ [ '10', '6' ]
\ 

2 3 dump + dump
5 dump + dump
6 dump * dump
\ tests/test_arith.sol
\
\ +, -, *, /, **
\
\ Arithmetic operators take the top two items
\ from the stack and performs the operations
\ on them, placing the results back on the
\ stack.

2 3 +
dump
5 *
dump
5 /
dump
3 -
dump
8 **
dump
Series Navigation<< Implementing Stack Oriented Languages — Part 2Implementing Stack Oriented Languages – Part 4 >>

Leave a Reply

Your email address will not be published. Required fields are marked *