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()