5
\$\begingroup\$

A previous question regarding Linny exists here.

After a wonderful response on my previous question regarding my developing programming language, I worked on it for a couple days, and am returning for more scrutiny. What I added:

  • If Statements ( no else )
  • Functions ( with calls )
  • For Loops
  • Math operations
  • User input (feed)
  • Changed file extension from .linny to .lin
  • Added character datatype
  • All additions to the language are reflected in the interpreter.

I would like feedback on anything possible, as I plan on writing a game with this language in the future (expect that to be reviewed on here as well!). Any and all feedback is welcome, appreciated, and considered!

script.lin

//Variable Assignments
string s = "Hello" ;
integer a = 12 ;
integer b = 13 ;
integer c = 13 ;
string aa = "S" ;
string bb = "S" ;
float x = 7.22 ;
boolean t = false ;
boolean s = true ;
character = "c" ;

// Math operations / type output
add a b => sum ;
out sum ;
type sum ;
subtract a b => sum ;
out sum ;
multiply a b => sum ;
out sum ;
divide a b => sum ;
out sum ;

//User input
feed string pw "Password: " ;
out pw ;

//Function calling
func test()
    out a ;
endfunc

call test ;

//If statements

if a greaterthan b then
    out a ;
    out b ;
endif

if a lessthan b then
    out t ;
endif

if aa equals bb then
    out s ;
endif

//For loops

for 2
    out t ;
endfor

for 3
    out aa ;
endfor

interpreter.py

""" [Linny Interpreter] """

#import json

VARIABLE_CACHE = {}
FUNCTION_CACHE = {}

VARIABLE_KEYWORDS = ["integer", "string", "float", "boolean", "character", "feed"]
LOGIC_KEYWORDS = ["if", "endif", "else", "while", "for", "then", "equals", "greaterthan", "lessthan"]
FUNC_KEYWORDS = ["func", "endfunc"]
MISC_KEYWORDS = ["type"]
ALL_KEYWORDS = VARIABLE_KEYWORDS + LOGIC_KEYWORDS + FUNC_KEYWORDS + MISC_KEYWORDS

def add_to_function_cache(lines_of_code):
    """ Gathers all functions in the program and stores them """
    for line in lines_of_code:
        if line.startswith("func"):
            function_name = parse_function_name(line)
            function_body = parse_function_body(function_name)

            FUNCTION_CACHE[function_name] = {
                'code': function_body
            }

# FUNCTION PARSER METHODS START #

def parse_function_name(line):
    """ Returns the function name """
    return line[5:][:-3]

def parse_function_body(function_name):
    """ Returns all the code in the body of the passed function """
    body = []
    all_lines = get_lines()
    for i in range(len(all_lines)):
        if all_lines[i].startswith("func") and function_name in all_lines[i]:
            for j in range(i + 1, len(all_lines)):
                if all_lines[j].startswith("endfunc"):
                    return body
                body.append(all_lines[j][1:-1])

# FUNCTION PARSER METHODS END #

def add_to_variable_cache(line_of_code):
    """
    Adds a variable to the program cache, after tons of checks, returns
    True if it was able to add to cache, False if it couldn't
    """
    seperated_info = line_of_code.split()

    if len(seperated_info) == 5 and \
       seperated_info[0] in VARIABLE_KEYWORDS and \
       not seperated_info[1] in ALL_KEYWORDS and \
       seperated_info[2] == "=" and \
       not seperated_info[3] in ALL_KEYWORDS and \
       seperated_info[4] == ";":

        data_type = seperated_info[0]
        name = seperated_info[1]
        value = seperated_info[3]

        if data_type == "string":
            value = str(value)
        if data_type == "integer":
            value = int(value)
        if data_type == "float":
            value = float(value)
        if data_type == "boolean":
            if value == "true":
                value = True
            if value == "false":
                value = False
        if data_type == "character":
            value = chr(value)

        VARIABLE_CACHE[name] = {
            'data_type': data_type,
            'value': value
        }

        return True
    return False

SOURCE_FILE = "Code/Python/Linny/script.lin"

def get_lines():
    """ Returns all the lines in the file """
    with open(SOURCE_FILE, "r") as file:
        all_lines = []
        for line_ in file:
            all_lines.append(line_)
        return all_lines

def run_code(line):
    """ Runs the code in passed `line` """

    #Check if line is empty
    if line == "" or line == "\n" or line == []:
        return

    # "out (variable)" case
    # Usage: `out variable_name`, prints the value of the variable
    if line.startswith("out") and has_ending_semicolon(line):
        variable = line.split()[1]
        if variable in VARIABLE_CACHE.keys():
            if isinstance(variable, str):
                print(str(VARIABLE_CACHE[variable]['value']).replace('"', ''))
                return
            print(VARIABLE_CACHE[variable]['value'])
            return

    # "type (variable)" case
    # Usage: `type variable_name`, prints the data_type of the variable_name
    if line.startswith("type") and has_ending_semicolon(line):
        variable = line.split()[1]
        if variable in VARIABLE_CACHE.keys():
            print(VARIABLE_CACHE[variable]['data_type'])
            return

    # "feed (variable)" case
    # Usage: `feed data_type variable_name "prompt"`, gets user input
    if line.startswith("feed") and has_ending_semicolon(line):
        data_type = line.split()[1]
        variable = line.split()[2]
        prompt = line.split()[3].replace('"', '')
        value = input(prompt)

        VARIABLE_CACHE[variable] = {
            'data_type': data_type,
            'value': value
        }
        return

    # "call (function)" case
    # Usage: `call function_name`, runsfunction
    if line.startswith("call") and has_ending_semicolon(line):
        function_name = line.split()[1]
        if function_name in FUNCTION_CACHE.keys():
            for line_of_code in FUNCTION_CACHE[function_name]['code']:
                run_code(line_of_code)

    ###### MATH COMPARISONS START #####

    # "add/subtract/times/divide v1 v2 => v3" case
    if line.split()[0] in ["add", "subtract", "multiply", "divide"] and has_ending_semicolon(line):
        operator = line.split()[0]
        value_one = line.split()[1]
        value_two = line.split()[2]
        product = line.split()[4]

        #SyntaxError handling real quick

        if VARIABLE_CACHE[value_one] and VARIABLE_CACHE[value_two]:
            if VARIABLE_CACHE[value_one]['data_type'] == VARIABLE_CACHE[value_two]['data_type']:

                if operator == "add":
                    VARIABLE_CACHE[product] = {
                        'data_type': VARIABLE_CACHE[value_one]['data_type'],
                        'value': VARIABLE_CACHE[value_one]['value'] + VARIABLE_CACHE[value_two]['value']
                    }
                    return

                if operator == "subtract":
                    VARIABLE_CACHE[product] = {
                        'data_type': VARIABLE_CACHE[value_one]['data_type'],
                        'value': VARIABLE_CACHE[value_one]['value'] - VARIABLE_CACHE[value_two]['value']
                    }
                    return

                if operator == "multiply":
                    VARIABLE_CACHE[product] = {
                        'data_type': VARIABLE_CACHE[value_one]['data_type'],
                        'value': VARIABLE_CACHE[value_one]['value'] * VARIABLE_CACHE[value_two]['value']
                    }
                    return

                if operator == "divide":
                    VARIABLE_CACHE[product] = {
                        'data_type': VARIABLE_CACHE[value_one]['data_type'],
                        'value': VARIABLE_CACHE[value_one]['value'] / VARIABLE_CACHE[value_two]['value']
                    }
                    return


    ##### MATH COMPARISONS END #####

    ##### IF STATEMENTS START #####

    if line.startswith("if"):
        expressions = gather_expressions(line)
        inside_code = gather_inside_code(line, "endif")

        #Evaluate `if not variable then`
        if len(expressions) == 2:
            if expressions[1] in list(VARIABLE_CACHE.keys()):
                data_type = VARIABLE_CACHE[expressions[1]]['data_type']
                if data_type == "boolean":
                    #Now check for not
                    if expressions[0] == "not":
                        if not VARIABLE_CACHE[expressions[1]]['value']:
                            for code in inside_code:
                                run_code(code)
                    else:
                        exit(f"SyntaxError: {expressions[0]}")

        #Evaluate `if variable then`
        if expressions[0] in list(VARIABLE_CACHE.keys()):
            data_type = VARIABLE_CACHE[expressions[0]]['data_type']
            if data_type == "boolean":
                if VARIABLE_CACHE[expressions[0]]['value']:
                    for code in inside_code:
                        run_code(code)


        #Evaluate greaterthan
        if expressions[1] == "greaterthan":
            larger = VARIABLE_CACHE[expressions[0]]
            smaller = VARIABLE_CACHE[expressions[2]]

            if larger['data_type'] == smaller['data_type']:
                if larger['value'] > smaller['value']:
                    for code in inside_code:
                        run_code(code)

        #Evaluate lessthan
        if expressions[1] == "lessthan":
            smaller = VARIABLE_CACHE[expressions[0]]
            larger = VARIABLE_CACHE[expressions[2]]

            if smaller['data_type'] == smaller['data_type']:
                if smaller['value'] < larger['value']:
                    for code in inside_code:
                        run_code(code)

        #Evaluate equals
        if expressions[1] == "equals":
            var_one = VARIABLE_CACHE[expressions[0]]
            var_two = VARIABLE_CACHE[expressions[2]]

            if var_one['data_type'] == var_two['data_type']:
                if var_one['value'] == var_two['value']:
                    for code in inside_code:
                        run_code(code)

    ##### IF STATEMENTS END #####

    ##### FOR STATEMENTS START #####

    if line.startswith("for"):
        iterations = int(line.split()[1])
        inside_code = gather_inside_code(line, "endfor")

        for _ in range(iterations):
            for code in inside_code:
                run_code(code)

    ##### FOR STATEMENTS END #####

def gather_expressions(line):
    """ Gathers all the expressions in the if statement """
    expressions = []
    for word in line.split():
        if word not in ["if", "then"]:
            expressions.append(word)
    return expressions

def gather_inside_code(main_line, ending):
    """ Gathers all the code inside the statement/loop """
    all_lines = get_lines()
    inside_code = []
    for i in range(len(all_lines)):
        if all_lines[i] == main_line:
            for j in range(i + 1, len(all_lines)):
                if all_lines[j].startswith(ending):
                    return inside_code
                inside_code.append(all_lines[j][1:-1])
    return None

def has_ending_semicolon(line):
    """ Returns True if the line ends with a semicolon, else returning False """
    return False if line == "" else line.split()[-1] == ";"

def main():
    """ Combines all the steps for the interpreter """

    #Gathers all the declared variables
    for line in get_lines():
        add_to_variable_cache(line)

    #Gathers all the created functions
    add_to_function_cache(get_lines())

    #Runs the code
    for line in get_lines():
        run_code(line)

if __name__ == '__main__':
    main()
\$\endgroup\$
2
  • \$\begingroup\$ Is there anything else required to execute and test the interpreter? Do you have a language specification? Any documentation? \$\endgroup\$
    – AMC
    Commented Nov 6, 2019 at 3:27
  • \$\begingroup\$ Some resources that may be of use: aosabook.org/en/500L/…. I started writing up an answer only to realize that there's no point in my speculating, I will eagerly return once I know more about the design of the language. \$\endgroup\$
    – AMC
    Commented Nov 6, 2019 at 3:45

0